From 2a0aef58037f94bf1c09e0d615e2932cfef5c791 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Sun, 13 Oct 2019 18:14:54 -0400 Subject: [PATCH] internal/age: use a prototype of X25519 from golang/go#32670 --- cmd/age/age.go | 7 +-- internal/age/x25519.go | 78 +++++++++++++++++++------------ internal/curve25519/x25519.go | 86 +++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 36 deletions(-) create mode 100644 internal/curve25519/x25519.go diff --git a/cmd/age/age.go b/cmd/age/age.go index b17eb2f..43fa119 100644 --- a/cmd/age/age.go +++ b/cmd/age/age.go @@ -7,7 +7,6 @@ package main import ( - "crypto/rand" "flag" "fmt" "io" @@ -47,11 +46,7 @@ func generate() { log.Fatalf("-generate takes no arguments") } - key := make([]byte, 32) - if _, err := rand.Read(key); err != nil { - log.Fatalf("Internal error: %v", err) - } - k, err := age.NewX25519Identity(key) + k, err := age.GenerateX25519Identity() if err != nil { log.Fatalf("Internal error: %v", err) } diff --git a/internal/age/x25519.go b/internal/age/x25519.go index 3d2e8ab..409c286 100644 --- a/internal/age/x25519.go +++ b/internal/age/x25519.go @@ -14,16 +14,16 @@ import ( "io" "strings" + "github.com/FiloSottile/age/internal/curve25519" "github.com/FiloSottile/age/internal/format" "golang.org/x/crypto/chacha20poly1305" - "golang.org/x/crypto/curve25519" "golang.org/x/crypto/hkdf" ) const x25519Label = "age-tool.com X25519" type X25519Recipient struct { - theirPublicKey [32]byte + theirPublicKey []byte } var _ Recipient = &X25519Recipient{} @@ -31,11 +31,13 @@ var _ Recipient = &X25519Recipient{} func (*X25519Recipient) Type() string { return "X25519" } func NewX25519Recipient(publicKey []byte) (*X25519Recipient, error) { - if len(publicKey) != 32 { + if len(publicKey) != curve25519.PointSize { return nil, errors.New("invalid X25519 public key") } - r := &X25519Recipient{} - copy(r.theirPublicKey[:], publicKey) + r := &X25519Recipient{ + theirPublicKey: make([]byte, curve25519.PointSize), + } + copy(r.theirPublicKey, publicKey) return r, nil } @@ -56,24 +58,29 @@ func ParseX25519Recipient(s string) (*X25519Recipient, error) { } func (r *X25519Recipient) Wrap(fileKey []byte) (*format.Recipient, error) { - var ephemeral, ourPublicKey [32]byte - if _, err := rand.Read(ephemeral[:]); err != nil { + ephemeral := make([]byte, curve25519.ScalarSize) + if _, err := rand.Read(ephemeral); err != nil { + return nil, err + } + ourPublicKey, err := curve25519.X25519(ephemeral, curve25519.Basepoint) + if err != nil { return nil, err } - curve25519.ScalarBaseMult(&ourPublicKey, &ephemeral) - var sharedSecret [32]byte - curve25519.ScalarMult(&sharedSecret, &ephemeral, &r.theirPublicKey) + sharedSecret, err := curve25519.X25519(ephemeral, r.theirPublicKey) + if err != nil { + return nil, err + } l := &format.Recipient{ Type: "X25519", - Args: []string{format.EncodeToString(ourPublicKey[:])}, + Args: []string{format.EncodeToString(ourPublicKey)}, } - salt := make([]byte, 0, 32*2) - salt = append(salt, ourPublicKey[:]...) - salt = append(salt, r.theirPublicKey[:]...) - h := hkdf.New(sha256.New, sharedSecret[:], salt, []byte(x25519Label)) + salt := make([]byte, 0, len(ourPublicKey)+len(r.theirPublicKey)) + salt = append(salt, ourPublicKey...) + salt = append(salt, r.theirPublicKey...) + h := hkdf.New(sha256.New, sharedSecret, salt, []byte(x25519Label)) wrappingKey := make([]byte, chacha20poly1305.KeySize) if _, err := io.ReadFull(h, wrappingKey); err != nil { return nil, err @@ -89,11 +96,11 @@ func (r *X25519Recipient) Wrap(fileKey []byte) (*format.Recipient, error) { } func (r *X25519Recipient) String() string { - return "pubkey:" + format.EncodeToString(r.theirPublicKey[:]) + return "pubkey:" + format.EncodeToString(r.theirPublicKey) } type X25519Identity struct { - secretKey, ourPublicKey [32]byte + secretKey, ourPublicKey []byte } var _ Identity = &X25519Identity{} @@ -101,15 +108,25 @@ var _ Identity = &X25519Identity{} func (*X25519Identity) Type() string { return "X25519" } func NewX25519Identity(secretKey []byte) (*X25519Identity, error) { - if len(secretKey) != 32 { + if len(secretKey) != curve25519.ScalarSize { return nil, errors.New("invalid X25519 secret key") } - i := &X25519Identity{} - copy(i.secretKey[:], secretKey) - curve25519.ScalarBaseMult(&i.ourPublicKey, &i.secretKey) + i := &X25519Identity{ + secretKey: make([]byte, curve25519.ScalarSize), + } + copy(i.secretKey, secretKey) + i.ourPublicKey, _ = curve25519.X25519(i.secretKey, curve25519.Basepoint) return i, nil } +func GenerateX25519Identity() (*X25519Identity, error) { + secretKey := make([]byte, 32) + if _, err := rand.Read(secretKey); err != nil { + return nil, fmt.Errorf("internal error: %v", err) + } + return NewX25519Identity(secretKey) +} + func ParseX25519Identity(s string) (*X25519Identity, error) { if !strings.HasPrefix(s, "AGE_SECRET_KEY_") { return nil, fmt.Errorf("malformed secret key: %s", s) @@ -137,18 +154,19 @@ func (i *X25519Identity) Unwrap(block *format.Recipient) ([]byte, error) { if err != nil { return nil, fmt.Errorf("failed to parse X25519 recipient: %v", err) } - if len(publicKey) != 32 { + if len(publicKey) != curve25519.PointSize { return nil, errors.New("invalid X25519 recipient block") } - var sharedSecret, theirPublicKey [32]byte - copy(theirPublicKey[:], publicKey) - curve25519.ScalarMult(&sharedSecret, &i.secretKey, &theirPublicKey) + sharedSecret, err := curve25519.X25519(i.secretKey, publicKey) + if err != nil { + return nil, fmt.Errorf("invalid X25519 recipient: %v", err) + } - salt := make([]byte, 0, 32*2) - salt = append(salt, theirPublicKey[:]...) - salt = append(salt, i.ourPublicKey[:]...) - h := hkdf.New(sha256.New, sharedSecret[:], salt, []byte(x25519Label)) + salt := make([]byte, 0, len(publicKey)+len(i.ourPublicKey)) + salt = append(salt, publicKey...) + salt = append(salt, i.ourPublicKey...) + h := hkdf.New(sha256.New, sharedSecret, salt, []byte(x25519Label)) wrappingKey := make([]byte, chacha20poly1305.KeySize) if _, err := io.ReadFull(h, wrappingKey); err != nil { return nil, err @@ -168,5 +186,5 @@ func (i *X25519Identity) Recipient() *X25519Recipient { } func (i *X25519Identity) String() string { - return "AGE_SECRET_KEY_" + format.EncodeToString(i.secretKey[:]) + return "AGE_SECRET_KEY_" + format.EncodeToString(i.secretKey) } diff --git a/internal/curve25519/x25519.go b/internal/curve25519/x25519.go new file mode 100644 index 0000000..ce44adf --- /dev/null +++ b/internal/curve25519/x25519.go @@ -0,0 +1,86 @@ +// Copyright 2019 Google LLC +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +// Package curve25519 implements the new proposed API for +// golang.org/x/crypto/curve25519 from golang.org/issue/32670. +package curve25519 + +import ( + "crypto/subtle" + "fmt" + + "golang.org/x/crypto/curve25519" +) + +const ( + // ScalarSize is the size of the scalar input to X25519. + ScalarSize = 32 + // PointSize is the size of the point input to X25519. + PointSize = 32 +) + +// Basepoint is the canonical Curve25519 generator. +var Basepoint []byte + +func init() { + Basepoint = []byte{ + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } +} + +func checkBasepoint() { + if subtle.ConstantTimeCompare(Basepoint, []byte{ + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }) != 1 { + panic("curve25519: global Basepoint value was modified") + } +} + +// X25519 returns the result of the scalar multiplication (scalar * point), +// according to RFC 7748, Section 5. scalar, point and the return value are +// slices of 32 bytes. +// +// scalar can be egenrated at random, for example with crypto/rand. point should +// be either Basepoint or the output of an X25519 call. +// +// If point is Basepoint (but not if it's a different slice with the same +// contents) a precomputed implementation might be used for performance. +func X25519(scalar, point []byte) ([]byte, error) { + // Outline the body of function, to let the allocation be inlined in the + // caller, and possibly avoid escaping to the heap. + var dst [32]byte + return x25519(&dst, scalar, point) +} + +func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) { + var in [32]byte + if l := len(scalar); l != 32 { + return nil, fmt.Errorf("bad scalar length: %d, expected %d", l, 32) + } + if l := len(point); l != 32 { + return nil, fmt.Errorf("bad point length: %d, expected %d", l, 32) + } + copy(in[:], scalar) + if &point[0] == &Basepoint[0] { + checkBasepoint() + curve25519.ScalarBaseMult(dst, &in) + } else { + var base, zero [32]byte + copy(base[:], point) + curve25519.ScalarMult(dst, &in, &base) + if subtle.ConstantTimeCompare(dst[:], zero[:]) == 1 { + // TODO: test this codepath with all low order points. + return nil, fmt.Errorf("bad input point: low order point") + } + } + return dst[:], nil +}