diff options
Diffstat (limited to 'src/crypto/ecdh/nist.go')
-rw-r--r-- | src/crypto/ecdh/nist.go | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/src/crypto/ecdh/nist.go b/src/crypto/ecdh/nist.go new file mode 100644 index 0000000..01354fa --- /dev/null +++ b/src/crypto/ecdh/nist.go @@ -0,0 +1,275 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ecdh + +import ( + "crypto/internal/boring" + "crypto/internal/nistec" + "crypto/internal/randutil" + "encoding/binary" + "errors" + "io" + "math/bits" +) + +type nistCurve[Point nistPoint[Point]] struct { + name string + newPoint func() Point + scalarOrder []byte +} + +// nistPoint is a generic constraint for the nistec Point types. +type nistPoint[T any] interface { + Bytes() []byte + BytesX() ([]byte, error) + SetBytes([]byte) (T, error) + ScalarMult(T, []byte) (T, error) + ScalarBaseMult([]byte) (T, error) +} + +func (c *nistCurve[Point]) String() string { + return c.name +} + +var errInvalidPrivateKey = errors.New("crypto/ecdh: invalid private key") + +func (c *nistCurve[Point]) GenerateKey(rand io.Reader) (*PrivateKey, error) { + if boring.Enabled && rand == boring.RandReader { + key, bytes, err := boring.GenerateKeyECDH(c.name) + if err != nil { + return nil, err + } + return newBoringPrivateKey(c, key, bytes) + } + + key := make([]byte, len(c.scalarOrder)) + randutil.MaybeReadByte(rand) + for { + if _, err := io.ReadFull(rand, key); err != nil { + return nil, err + } + + // Mask off any excess bits if the size of the underlying field is not a + // whole number of bytes, which is only the case for P-521. We use a + // pointer to the scalarOrder field because comparing generic and + // instantiated types is not supported. + if &c.scalarOrder[0] == &p521Order[0] { + key[0] &= 0b0000_0001 + } + + // In tests, rand will return all zeros and NewPrivateKey will reject + // the zero key as it generates the identity as a public key. This also + // makes this function consistent with crypto/elliptic.GenerateKey. + key[1] ^= 0x42 + + k, err := c.NewPrivateKey(key) + if err == errInvalidPrivateKey { + continue + } + return k, err + } +} + +func (c *nistCurve[Point]) NewPrivateKey(key []byte) (*PrivateKey, error) { + if len(key) != len(c.scalarOrder) { + return nil, errors.New("crypto/ecdh: invalid private key size") + } + if isZero(key) || !isLess(key, c.scalarOrder) { + return nil, errInvalidPrivateKey + } + if boring.Enabled { + bk, err := boring.NewPrivateKeyECDH(c.name, key) + if err != nil { + return nil, err + } + return newBoringPrivateKey(c, bk, key) + } + k := &PrivateKey{ + curve: c, + privateKey: append([]byte{}, key...), + } + return k, nil +} + +func newBoringPrivateKey(c Curve, bk *boring.PrivateKeyECDH, privateKey []byte) (*PrivateKey, error) { + k := &PrivateKey{ + curve: c, + boring: bk, + privateKey: append([]byte(nil), privateKey...), + } + return k, nil +} + +func (c *nistCurve[Point]) privateKeyToPublicKey(key *PrivateKey) *PublicKey { + boring.Unreachable() + if key.curve != c { + panic("crypto/ecdh: internal error: converting the wrong key type") + } + p, err := c.newPoint().ScalarBaseMult(key.privateKey) + if err != nil { + // This is unreachable because the only error condition of + // ScalarBaseMult is if the input is not the right size. + panic("crypto/ecdh: internal error: nistec ScalarBaseMult failed for a fixed-size input") + } + publicKey := p.Bytes() + if len(publicKey) == 1 { + // The encoding of the identity is a single 0x00 byte. This is + // unreachable because the only scalar that generates the identity is + // zero, which is rejected by NewPrivateKey. + panic("crypto/ecdh: internal error: nistec ScalarBaseMult returned the identity") + } + return &PublicKey{ + curve: key.curve, + publicKey: publicKey, + } +} + +// isZero returns whether a is all zeroes in constant time. +func isZero(a []byte) bool { + var acc byte + for _, b := range a { + acc |= b + } + return acc == 0 +} + +// isLess returns whether a < b, where a and b are big-endian buffers of the +// same length and shorter than 72 bytes. +func isLess(a, b []byte) bool { + if len(a) != len(b) { + panic("crypto/ecdh: internal error: mismatched isLess inputs") + } + + // Copy the values into a fixed-size preallocated little-endian buffer. + // 72 bytes is enough for every scalar in this package, and having a fixed + // size lets us avoid heap allocations. + if len(a) > 72 { + panic("crypto/ecdh: internal error: isLess input too large") + } + bufA, bufB := make([]byte, 72), make([]byte, 72) + for i := range a { + bufA[i], bufB[i] = a[len(a)-i-1], b[len(b)-i-1] + } + + // Perform a subtraction with borrow. + var borrow uint64 + for i := 0; i < len(bufA); i += 8 { + limbA, limbB := binary.LittleEndian.Uint64(bufA[i:]), binary.LittleEndian.Uint64(bufB[i:]) + _, borrow = bits.Sub64(limbA, limbB, borrow) + } + + // If there is a borrow at the end of the operation, then a < b. + return borrow == 1 +} + +func (c *nistCurve[Point]) NewPublicKey(key []byte) (*PublicKey, error) { + // Reject the point at infinity and compressed encodings. + if len(key) == 0 || key[0] != 4 { + return nil, errors.New("crypto/ecdh: invalid public key") + } + k := &PublicKey{ + curve: c, + publicKey: append([]byte{}, key...), + } + if boring.Enabled { + bk, err := boring.NewPublicKeyECDH(c.name, k.publicKey) + if err != nil { + return nil, err + } + k.boring = bk + } else { + // SetBytes also checks that the point is on the curve. + if _, err := c.newPoint().SetBytes(key); err != nil { + return nil, err + } + } + return k, nil +} + +func (c *nistCurve[Point]) ecdh(local *PrivateKey, remote *PublicKey) ([]byte, error) { + // Note that this function can't return an error, as NewPublicKey rejects + // invalid points and the point at infinity, and NewPrivateKey rejects + // invalid scalars and the zero value. BytesX returns an error for the point + // at infinity, but in a prime order group such as the NIST curves that can + // only be the result of a scalar multiplication if one of the inputs is the + // zero scalar or the point at infinity. + + if boring.Enabled { + return boring.ECDH(local.boring, remote.boring) + } + + boring.Unreachable() + p, err := c.newPoint().SetBytes(remote.publicKey) + if err != nil { + return nil, err + } + if _, err := p.ScalarMult(p, local.privateKey); err != nil { + return nil, err + } + return p.BytesX() +} + +// P256 returns a Curve which implements NIST P-256 (FIPS 186-3, section D.2.3), +// also known as secp256r1 or prime256v1. +// +// Multiple invocations of this function will return the same value, which can +// be used for equality checks and switch statements. +func P256() Curve { return p256 } + +var p256 = &nistCurve[*nistec.P256Point]{ + name: "P-256", + newPoint: nistec.NewP256Point, + scalarOrder: p256Order, +} + +var p256Order = []byte{ + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17, 0x9e, 0x84, + 0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x51} + +// P384 returns a Curve which implements NIST P-384 (FIPS 186-3, section D.2.4), +// also known as secp384r1. +// +// Multiple invocations of this function will return the same value, which can +// be used for equality checks and switch statements. +func P384() Curve { return p384 } + +var p384 = &nistCurve[*nistec.P384Point]{ + name: "P-384", + newPoint: nistec.NewP384Point, + scalarOrder: p384Order, +} + +var p384Order = []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc7, 0x63, 0x4d, 0x81, 0xf4, 0x37, 0x2d, 0xdf, + 0x58, 0x1a, 0x0d, 0xb2, 0x48, 0xb0, 0xa7, 0x7a, + 0xec, 0xec, 0x19, 0x6a, 0xcc, 0xc5, 0x29, 0x73} + +// P521 returns a Curve which implements NIST P-521 (FIPS 186-3, section D.2.5), +// also known as secp521r1. +// +// Multiple invocations of this function will return the same value, which can +// be used for equality checks and switch statements. +func P521() Curve { return p521 } + +var p521 = &nistCurve[*nistec.P521Point]{ + name: "P-521", + newPoint: nistec.NewP521Point, + scalarOrder: p521Order, +} + +var p521Order = []byte{0x01, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, + 0x51, 0x86, 0x87, 0x83, 0xbf, 0x2f, 0x96, 0x6b, + 0x7f, 0xcc, 0x01, 0x48, 0xf7, 0x09, 0xa5, 0xd0, + 0x3b, 0xb5, 0xc9, 0xb8, 0x89, 0x9c, 0x47, 0xae, + 0xbb, 0x6f, 0xb7, 0x1e, 0x91, 0x38, 0x64, 0x09} |