summaryrefslogtreecommitdiffstats
path: root/src/crypto/ecdh
diff options
context:
space:
mode:
Diffstat (limited to 'src/crypto/ecdh')
-rw-r--r--src/crypto/ecdh/ecdh.go188
-rw-r--r--src/crypto/ecdh/ecdh_test.go525
-rw-r--r--src/crypto/ecdh/nist.go275
-rw-r--r--src/crypto/ecdh/x25519.go136
4 files changed, 1124 insertions, 0 deletions
diff --git a/src/crypto/ecdh/ecdh.go b/src/crypto/ecdh/ecdh.go
new file mode 100644
index 0000000..b86f521
--- /dev/null
+++ b/src/crypto/ecdh/ecdh.go
@@ -0,0 +1,188 @@
+// 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 implements Elliptic Curve Diffie-Hellman over
+// NIST curves and Curve25519.
+package ecdh
+
+import (
+ "crypto"
+ "crypto/internal/boring"
+ "crypto/subtle"
+ "errors"
+ "io"
+ "sync"
+)
+
+type Curve interface {
+ // GenerateKey generates a random PrivateKey.
+ //
+ // Most applications should use [crypto/rand.Reader] as rand. Note that the
+ // returned key does not depend deterministically on the bytes read from rand,
+ // and may change between calls and/or between versions.
+ GenerateKey(rand io.Reader) (*PrivateKey, error)
+
+ // NewPrivateKey checks that key is valid and returns a PrivateKey.
+ //
+ // For NIST curves, this follows SEC 1, Version 2.0, Section 2.3.6, which
+ // amounts to decoding the bytes as a fixed length big endian integer and
+ // checking that the result is lower than the order of the curve. The zero
+ // private key is also rejected, as the encoding of the corresponding public
+ // key would be irregular.
+ //
+ // For X25519, this only checks the scalar length.
+ NewPrivateKey(key []byte) (*PrivateKey, error)
+
+ // NewPublicKey checks that key is valid and returns a PublicKey.
+ //
+ // For NIST curves, this decodes an uncompressed point according to SEC 1,
+ // Version 2.0, Section 2.3.4. Compressed encodings and the point at
+ // infinity are rejected.
+ //
+ // For X25519, this only checks the u-coordinate length. Adversarially
+ // selected public keys can cause ECDH to return an error.
+ NewPublicKey(key []byte) (*PublicKey, error)
+
+ // ecdh performs a ECDH exchange and returns the shared secret. It's exposed
+ // as the PrivateKey.ECDH method.
+ //
+ // The private method also allow us to expand the ECDH interface with more
+ // methods in the future without breaking backwards compatibility.
+ ecdh(local *PrivateKey, remote *PublicKey) ([]byte, error)
+
+ // privateKeyToPublicKey converts a PrivateKey to a PublicKey. It's exposed
+ // as the PrivateKey.PublicKey method.
+ //
+ // This method always succeeds: for X25519, the zero key can't be
+ // constructed due to clamping; for NIST curves, it is rejected by
+ // NewPrivateKey.
+ privateKeyToPublicKey(*PrivateKey) *PublicKey
+}
+
+// PublicKey is an ECDH public key, usually a peer's ECDH share sent over the wire.
+//
+// These keys can be parsed with [crypto/x509.ParsePKIXPublicKey] and encoded
+// with [crypto/x509.MarshalPKIXPublicKey]. For NIST curves, they then need to
+// be converted with [crypto/ecdsa.PublicKey.ECDH] after parsing.
+type PublicKey struct {
+ curve Curve
+ publicKey []byte
+ boring *boring.PublicKeyECDH
+}
+
+// Bytes returns a copy of the encoding of the public key.
+func (k *PublicKey) Bytes() []byte {
+ // Copy the public key to a fixed size buffer that can get allocated on the
+ // caller's stack after inlining.
+ var buf [133]byte
+ return append(buf[:0], k.publicKey...)
+}
+
+// Equal returns whether x represents the same public key as k.
+//
+// Note that there can be equivalent public keys with different encodings which
+// would return false from this check but behave the same way as inputs to ECDH.
+//
+// This check is performed in constant time as long as the key types and their
+// curve match.
+func (k *PublicKey) Equal(x crypto.PublicKey) bool {
+ xx, ok := x.(*PublicKey)
+ if !ok {
+ return false
+ }
+ return k.curve == xx.curve &&
+ subtle.ConstantTimeCompare(k.publicKey, xx.publicKey) == 1
+}
+
+func (k *PublicKey) Curve() Curve {
+ return k.curve
+}
+
+// PrivateKey is an ECDH private key, usually kept secret.
+//
+// These keys can be parsed with [crypto/x509.ParsePKCS8PrivateKey] and encoded
+// with [crypto/x509.MarshalPKCS8PrivateKey]. For NIST curves, they then need to
+// be converted with [crypto/ecdsa.PrivateKey.ECDH] after parsing.
+type PrivateKey struct {
+ curve Curve
+ privateKey []byte
+ boring *boring.PrivateKeyECDH
+ // publicKey is set under publicKeyOnce, to allow loading private keys with
+ // NewPrivateKey without having to perform a scalar multiplication.
+ publicKey *PublicKey
+ publicKeyOnce sync.Once
+}
+
+// ECDH performs a ECDH exchange and returns the shared secret. The PrivateKey
+// and PublicKey must use the same curve.
+//
+// For NIST curves, this performs ECDH as specified in SEC 1, Version 2.0,
+// Section 3.3.1, and returns the x-coordinate encoded according to SEC 1,
+// Version 2.0, Section 2.3.5. The result is never the point at infinity.
+//
+// For X25519, this performs ECDH as specified in RFC 7748, Section 6.1. If
+// the result is the all-zero value, ECDH returns an error.
+func (k *PrivateKey) ECDH(remote *PublicKey) ([]byte, error) {
+ if k.curve != remote.curve {
+ return nil, errors.New("crypto/ecdh: private key and public key curves do not match")
+ }
+ return k.curve.ecdh(k, remote)
+}
+
+// Bytes returns a copy of the encoding of the private key.
+func (k *PrivateKey) Bytes() []byte {
+ // Copy the private key to a fixed size buffer that can get allocated on the
+ // caller's stack after inlining.
+ var buf [66]byte
+ return append(buf[:0], k.privateKey...)
+}
+
+// Equal returns whether x represents the same private key as k.
+//
+// Note that there can be equivalent private keys with different encodings which
+// would return false from this check but behave the same way as inputs to ECDH.
+//
+// This check is performed in constant time as long as the key types and their
+// curve match.
+func (k *PrivateKey) Equal(x crypto.PrivateKey) bool {
+ xx, ok := x.(*PrivateKey)
+ if !ok {
+ return false
+ }
+ return k.curve == xx.curve &&
+ subtle.ConstantTimeCompare(k.privateKey, xx.privateKey) == 1
+}
+
+func (k *PrivateKey) Curve() Curve {
+ return k.curve
+}
+
+func (k *PrivateKey) PublicKey() *PublicKey {
+ k.publicKeyOnce.Do(func() {
+ if k.boring != nil {
+ // Because we already checked in NewPrivateKey that the key is valid,
+ // there should not be any possible errors from BoringCrypto,
+ // so we turn the error into a panic.
+ // (We can't return it anyhow.)
+ kpub, err := k.boring.PublicKey()
+ if err != nil {
+ panic("boringcrypto: " + err.Error())
+ }
+ k.publicKey = &PublicKey{
+ curve: k.curve,
+ publicKey: kpub.Bytes(),
+ boring: kpub,
+ }
+ } else {
+ k.publicKey = k.curve.privateKeyToPublicKey(k)
+ }
+ })
+ return k.publicKey
+}
+
+// Public implements the implicit interface of all standard library private
+// keys. See the docs of crypto.PrivateKey.
+func (k *PrivateKey) Public() crypto.PublicKey {
+ return k.PublicKey()
+}
diff --git a/src/crypto/ecdh/ecdh_test.go b/src/crypto/ecdh/ecdh_test.go
new file mode 100644
index 0000000..10da95a
--- /dev/null
+++ b/src/crypto/ecdh/ecdh_test.go
@@ -0,0 +1,525 @@
+// 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_test
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/cipher"
+ "crypto/ecdh"
+ "crypto/rand"
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+ "internal/testenv"
+ "io"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "testing"
+
+ "golang.org/x/crypto/chacha20"
+)
+
+// Check that PublicKey and PrivateKey implement the interfaces documented in
+// crypto.PublicKey and crypto.PrivateKey.
+var _ interface {
+ Equal(x crypto.PublicKey) bool
+} = &ecdh.PublicKey{}
+var _ interface {
+ Public() crypto.PublicKey
+ Equal(x crypto.PrivateKey) bool
+} = &ecdh.PrivateKey{}
+
+func TestECDH(t *testing.T) {
+ testAllCurves(t, func(t *testing.T, curve ecdh.Curve) {
+ aliceKey, err := curve.GenerateKey(rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ bobKey, err := curve.GenerateKey(rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ alicePubKey, err := curve.NewPublicKey(aliceKey.PublicKey().Bytes())
+ if err != nil {
+ t.Error(err)
+ }
+ if !bytes.Equal(aliceKey.PublicKey().Bytes(), alicePubKey.Bytes()) {
+ t.Error("encoded and decoded public keys are different")
+ }
+ if !aliceKey.PublicKey().Equal(alicePubKey) {
+ t.Error("encoded and decoded public keys are different")
+ }
+
+ alicePrivKey, err := curve.NewPrivateKey(aliceKey.Bytes())
+ if err != nil {
+ t.Error(err)
+ }
+ if !bytes.Equal(aliceKey.Bytes(), alicePrivKey.Bytes()) {
+ t.Error("encoded and decoded private keys are different")
+ }
+ if !aliceKey.Equal(alicePrivKey) {
+ t.Error("encoded and decoded private keys are different")
+ }
+
+ bobSecret, err := bobKey.ECDH(aliceKey.PublicKey())
+ if err != nil {
+ t.Fatal(err)
+ }
+ aliceSecret, err := aliceKey.ECDH(bobKey.PublicKey())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !bytes.Equal(bobSecret, aliceSecret) {
+ t.Error("two ECDH computations came out different")
+ }
+ })
+}
+
+type countingReader struct {
+ r io.Reader
+ n int
+}
+
+func (r *countingReader) Read(p []byte) (int, error) {
+ n, err := r.r.Read(p)
+ r.n += n
+ return n, err
+}
+
+func TestGenerateKey(t *testing.T) {
+ testAllCurves(t, func(t *testing.T, curve ecdh.Curve) {
+ r := &countingReader{r: rand.Reader}
+ k, err := curve.GenerateKey(r)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // GenerateKey does rejection sampling. If the masking works correctly,
+ // the probability of a rejection is 1-ord(G)/2^ceil(log2(ord(G))),
+ // which for all curves is small enough (at most 2^-32, for P-256) that
+ // a bit flip is more likely to make this test fail than bad luck.
+ // Account for the extra MaybeReadByte byte, too.
+ if got, expected := r.n, len(k.Bytes())+1; got > expected {
+ t.Errorf("expected GenerateKey to consume at most %v bytes, got %v", expected, got)
+ }
+ })
+}
+
+var vectors = map[ecdh.Curve]struct {
+ PrivateKey, PublicKey string
+ PeerPublicKey string
+ SharedSecret string
+}{
+ // NIST vectors from CAVS 14.1, ECC CDH Primitive (SP800-56A).
+ ecdh.P256(): {
+ PrivateKey: "7d7dc5f71eb29ddaf80d6214632eeae03d9058af1fb6d22ed80badb62bc1a534",
+ PublicKey: "04ead218590119e8876b29146ff89ca61770c4edbbf97d38ce385ed281d8a6b230" +
+ "28af61281fd35e2fa7002523acc85a429cb06ee6648325389f59edfce1405141",
+ PeerPublicKey: "04700c48f77f56584c5cc632ca65640db91b6bacce3a4df6b42ce7cc838833d287" +
+ "db71e509e3fd9b060ddb20ba5c51dcc5948d46fbf640dfe0441782cab85fa4ac",
+ SharedSecret: "46fc62106420ff012e54a434fbdd2d25ccc5852060561e68040dd7778997bd7b",
+ },
+ ecdh.P384(): {
+ PrivateKey: "3cc3122a68f0d95027ad38c067916ba0eb8c38894d22e1b15618b6818a661774ad463b205da88cf699ab4d43c9cf98a1",
+ PublicKey: "049803807f2f6d2fd966cdd0290bd410c0190352fbec7ff6247de1302df86f25d34fe4a97bef60cff548355c015dbb3e5f" +
+ "ba26ca69ec2f5b5d9dad20cc9da711383a9dbe34ea3fa5a2af75b46502629ad54dd8b7d73a8abb06a3a3be47d650cc99",
+ PeerPublicKey: "04a7c76b970c3b5fe8b05d2838ae04ab47697b9eaf52e764592efda27fe7513272734466b400091adbf2d68c58e0c50066" +
+ "ac68f19f2e1cb879aed43a9969b91a0839c4c38a49749b661efedf243451915ed0905a32b060992b468c64766fc8437a",
+ SharedSecret: "5f9d29dc5e31a163060356213669c8ce132e22f57c9a04f40ba7fcead493b457e5621e766c40a2e3d4d6a04b25e533f1",
+ },
+ // For some reason all field elements in the test vector (both scalars and
+ // base field elements), but not the shared secret output, have two extra
+ // leading zero bytes (which in big-endian are irrelevant). Removed here.
+ ecdh.P521(): {
+ PrivateKey: "017eecc07ab4b329068fba65e56a1f8890aa935e57134ae0ffcce802735151f4eac6564f6ee9974c5e6887a1fefee5743ae2241bfeb95d5ce31ddcb6f9edb4d6fc47",
+ PublicKey: "0400602f9d0cf9e526b29e22381c203c48a886c2b0673033366314f1ffbcba240ba42f4ef38a76174635f91e6b4ed34275eb01c8467d05ca80315bf1a7bbd945f550a5" +
+ "01b7c85f26f5d4b2d7355cf6b02117659943762b6d1db5ab4f1dbc44ce7b2946eb6c7de342962893fd387d1b73d7a8672d1f236961170b7eb3579953ee5cdc88cd2d",
+ PeerPublicKey: "0400685a48e86c79f0f0875f7bc18d25eb5fc8c0b07e5da4f4370f3a9490340854334b1e1b87fa395464c60626124a4e70d0f785601d37c09870ebf176666877a2046d" +
+ "01ba52c56fc8776d9e8f5db4f0cc27636d0b741bbe05400697942e80b739884a83bde99e0f6716939e632bc8986fa18dccd443a348b6c3e522497955a4f3c302f676",
+ SharedSecret: "005fc70477c3e63bc3954bd0df3ea0d1f41ee21746ed95fc5e1fdf90930d5e136672d72cc770742d1711c3c3a4c334a0ad9759436a4d3c5bf6e74b9578fac148c831",
+ },
+ // X25519 test vector from RFC 7748, Section 6.1.
+ ecdh.X25519(): {
+ PrivateKey: "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a",
+ PublicKey: "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a",
+ PeerPublicKey: "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f",
+ SharedSecret: "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742",
+ },
+}
+
+func TestVectors(t *testing.T) {
+ testAllCurves(t, func(t *testing.T, curve ecdh.Curve) {
+ v := vectors[curve]
+ key, err := curve.NewPrivateKey(hexDecode(t, v.PrivateKey))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(key.PublicKey().Bytes(), hexDecode(t, v.PublicKey)) {
+ t.Error("public key derived from the private key does not match")
+ }
+ peer, err := curve.NewPublicKey(hexDecode(t, v.PeerPublicKey))
+ if err != nil {
+ t.Fatal(err)
+ }
+ secret, err := key.ECDH(peer)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(secret, hexDecode(t, v.SharedSecret)) {
+ t.Errorf("shared secret does not match: %x %x %s %x", secret, sha256.Sum256(secret), v.SharedSecret,
+ sha256.Sum256(hexDecode(t, v.SharedSecret)))
+ }
+ })
+}
+
+func hexDecode(t *testing.T, s string) []byte {
+ b, err := hex.DecodeString(s)
+ if err != nil {
+ t.Fatal("invalid hex string:", s)
+ }
+ return b
+}
+
+func TestString(t *testing.T) {
+ testAllCurves(t, func(t *testing.T, curve ecdh.Curve) {
+ s := fmt.Sprintf("%s", curve)
+ if s[:1] != "P" && s[:1] != "X" {
+ t.Errorf("unexpected Curve string encoding: %q", s)
+ }
+ })
+}
+
+func TestX25519Failure(t *testing.T) {
+ identity := hexDecode(t, "0000000000000000000000000000000000000000000000000000000000000000")
+ lowOrderPoint := hexDecode(t, "e0eb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b800")
+ randomScalar := make([]byte, 32)
+ rand.Read(randomScalar)
+
+ t.Run("identity point", func(t *testing.T) { testX25519Failure(t, randomScalar, identity) })
+ t.Run("low order point", func(t *testing.T) { testX25519Failure(t, randomScalar, lowOrderPoint) })
+}
+
+func testX25519Failure(t *testing.T, private, public []byte) {
+ priv, err := ecdh.X25519().NewPrivateKey(private)
+ if err != nil {
+ t.Fatal(err)
+ }
+ pub, err := ecdh.X25519().NewPublicKey(public)
+ if err != nil {
+ t.Fatal(err)
+ }
+ secret, err := priv.ECDH(pub)
+ if err == nil {
+ t.Error("expected ECDH error")
+ }
+ if secret != nil {
+ t.Errorf("unexpected ECDH output: %x", secret)
+ }
+}
+
+var invalidPrivateKeys = map[ecdh.Curve][]string{
+ ecdh.P256(): {
+ // Bad lengths.
+ "",
+ "01",
+ "01010101010101010101010101010101010101010101010101010101010101",
+ "000101010101010101010101010101010101010101010101010101010101010101",
+ strings.Repeat("01", 200),
+ // Zero.
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ // Order of the curve and above.
+ "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551",
+ "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552",
+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+ },
+ ecdh.P384(): {
+ // Bad lengths.
+ "",
+ "01",
+ "0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101",
+ "00010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101",
+ strings.Repeat("01", 200),
+ // Zero.
+ "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ // Order of the curve and above.
+ "ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973",
+ "ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52974",
+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+ },
+ ecdh.P521(): {
+ // Bad lengths.
+ "",
+ "01",
+ "0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101",
+ "00010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101",
+ strings.Repeat("01", 200),
+ // Zero.
+ "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ // Order of the curve and above.
+ "01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409",
+ "01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e9138640a",
+ "11fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409",
+ "03fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4a30d0f077e5f2cd6ff980291ee134ba0776b937113388f5d76df6e3d2270c812",
+ },
+ ecdh.X25519(): {
+ // X25519 only rejects bad lengths.
+ "",
+ "01",
+ "01010101010101010101010101010101010101010101010101010101010101",
+ "000101010101010101010101010101010101010101010101010101010101010101",
+ strings.Repeat("01", 200),
+ },
+}
+
+func TestNewPrivateKey(t *testing.T) {
+ testAllCurves(t, func(t *testing.T, curve ecdh.Curve) {
+ for _, input := range invalidPrivateKeys[curve] {
+ k, err := curve.NewPrivateKey(hexDecode(t, input))
+ if err == nil {
+ t.Errorf("unexpectedly accepted %q", input)
+ } else if k != nil {
+ t.Error("PrivateKey was not nil on error")
+ } else if strings.Contains(err.Error(), "boringcrypto") {
+ t.Errorf("boringcrypto error leaked out: %v", err)
+ }
+ }
+ })
+}
+
+var invalidPublicKeys = map[ecdh.Curve][]string{
+ ecdh.P256(): {
+ // Bad lengths.
+ "",
+ "04",
+ strings.Repeat("04", 200),
+ // Infinity.
+ "00",
+ // Compressed encodings.
+ "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296",
+ "02e2534a3532d08fbba02dde659ee62bd0031fe2db785596ef509302446b030852",
+ // Points not on the curve.
+ "046b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f6",
+ "0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ },
+ ecdh.P384(): {
+ // Bad lengths.
+ "",
+ "04",
+ strings.Repeat("04", 200),
+ // Infinity.
+ "00",
+ // Compressed encodings.
+ "03aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7",
+ "0208d999057ba3d2d969260045c55b97f089025959a6f434d651d207d19fb96e9e4fe0e86ebe0e64f85b96a9c75295df61",
+ // Points not on the curve.
+ "04aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab73617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e60",
+ "04000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ },
+ ecdh.P521(): {
+ // Bad lengths.
+ "",
+ "04",
+ strings.Repeat("04", 200),
+ // Infinity.
+ "00",
+ // Compressed encodings.
+ "030035b5df64ae2ac204c354b483487c9070cdc61c891c5ff39afc06c5d55541d3ceac8659e24afe3d0750e8b88e9f078af066a1d5025b08e5a5e2fbc87412871902f3",
+ "0200c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66",
+ // Points not on the curve.
+ "0400c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16651",
+ "04000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ },
+ ecdh.X25519(): {},
+}
+
+func TestNewPublicKey(t *testing.T) {
+ testAllCurves(t, func(t *testing.T, curve ecdh.Curve) {
+ for _, input := range invalidPublicKeys[curve] {
+ k, err := curve.NewPublicKey(hexDecode(t, input))
+ if err == nil {
+ t.Errorf("unexpectedly accepted %q", input)
+ } else if k != nil {
+ t.Error("PublicKey was not nil on error")
+ } else if strings.Contains(err.Error(), "boringcrypto") {
+ t.Errorf("boringcrypto error leaked out: %v", err)
+ }
+ }
+ })
+}
+
+func testAllCurves(t *testing.T, f func(t *testing.T, curve ecdh.Curve)) {
+ t.Run("P256", func(t *testing.T) { f(t, ecdh.P256()) })
+ t.Run("P384", func(t *testing.T) { f(t, ecdh.P384()) })
+ t.Run("P521", func(t *testing.T) { f(t, ecdh.P521()) })
+ t.Run("X25519", func(t *testing.T) { f(t, ecdh.X25519()) })
+}
+
+func BenchmarkECDH(b *testing.B) {
+ benchmarkAllCurves(b, func(b *testing.B, curve ecdh.Curve) {
+ c, err := chacha20.NewUnauthenticatedCipher(make([]byte, 32), make([]byte, 12))
+ if err != nil {
+ b.Fatal(err)
+ }
+ rand := cipher.StreamReader{
+ S: c, R: zeroReader,
+ }
+
+ peerKey, err := curve.GenerateKey(rand)
+ if err != nil {
+ b.Fatal(err)
+ }
+ peerShare := peerKey.PublicKey().Bytes()
+ b.ResetTimer()
+ b.ReportAllocs()
+
+ var allocationsSink byte
+
+ for i := 0; i < b.N; i++ {
+ key, err := curve.GenerateKey(rand)
+ if err != nil {
+ b.Fatal(err)
+ }
+ share := key.PublicKey().Bytes()
+ peerPubKey, err := curve.NewPublicKey(peerShare)
+ if err != nil {
+ b.Fatal(err)
+ }
+ secret, err := key.ECDH(peerPubKey)
+ if err != nil {
+ b.Fatal(err)
+ }
+ allocationsSink ^= secret[0] ^ share[0]
+ }
+ })
+}
+
+func benchmarkAllCurves(b *testing.B, f func(b *testing.B, curve ecdh.Curve)) {
+ b.Run("P256", func(b *testing.B) { f(b, ecdh.P256()) })
+ b.Run("P384", func(b *testing.B) { f(b, ecdh.P384()) })
+ b.Run("P521", func(b *testing.B) { f(b, ecdh.P521()) })
+ b.Run("X25519", func(b *testing.B) { f(b, ecdh.X25519()) })
+}
+
+type zr struct{}
+
+// Read replaces the contents of dst with zeros. It is safe for concurrent use.
+func (zr) Read(dst []byte) (n int, err error) {
+ for i := range dst {
+ dst[i] = 0
+ }
+ return len(dst), nil
+}
+
+var zeroReader = zr{}
+
+const linkerTestProgram = `
+package main
+import "crypto/ecdh"
+import "crypto/rand"
+func main() {
+ curve := ecdh.P384()
+ key, err := curve.GenerateKey(rand.Reader)
+ if err != nil { panic(err) }
+ _, err = curve.NewPublicKey(key.PublicKey().Bytes())
+ if err != nil { panic(err) }
+ _, err = curve.NewPrivateKey(key.Bytes())
+ if err != nil { panic(err) }
+ _, err = key.ECDH(key.PublicKey())
+ if err != nil { panic(err) }
+ println("OK")
+}
+`
+
+// TestLinker ensures that using one curve does not bring all other
+// implementations into the binary. This also guarantees that govulncheck can
+// avoid warning about a curve-specific vulnerability if that curve is not used.
+func TestLinker(t *testing.T) {
+ if testing.Short() {
+ t.Skip("test requires running 'go build'")
+ }
+ testenv.MustHaveGoBuild(t)
+
+ dir := t.TempDir()
+ hello := filepath.Join(dir, "hello.go")
+ err := os.WriteFile(hello, []byte(linkerTestProgram), 0664)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ run := func(args ...string) string {
+ cmd := exec.Command(args[0], args[1:]...)
+ cmd.Dir = dir
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("%v: %v\n%s", args, err, string(out))
+ }
+ return string(out)
+ }
+
+ goBin := testenv.GoToolPath(t)
+ run(goBin, "build", "-o", "hello.exe", "hello.go")
+ if out := run("./hello.exe"); out != "OK\n" {
+ t.Error("unexpected output:", out)
+ }
+
+ // List all text symbols under crypto/... and make sure there are some for
+ // P384, but none for the other curves.
+ var consistent bool
+ nm := run(goBin, "tool", "nm", "hello.exe")
+ for _, match := range regexp.MustCompile(`(?m)T (crypto/.*)$`).FindAllStringSubmatch(nm, -1) {
+ symbol := strings.ToLower(match[1])
+ if strings.Contains(symbol, "p384") {
+ consistent = true
+ }
+ if strings.Contains(symbol, "p224") || strings.Contains(symbol, "p256") || strings.Contains(symbol, "p521") {
+ t.Errorf("unexpected symbol in program using only ecdh.P384: %s", match[1])
+ }
+ }
+ if !consistent {
+ t.Error("no P384 symbols found in program using ecdh.P384, test is broken")
+ }
+}
+
+func TestMismatchedCurves(t *testing.T) {
+ curves := []struct {
+ name string
+ curve ecdh.Curve
+ }{
+ {"P256", ecdh.P256()},
+ {"P384", ecdh.P384()},
+ {"P521", ecdh.P521()},
+ {"X25519", ecdh.X25519()},
+ }
+
+ for _, privCurve := range curves {
+ priv, err := privCurve.curve.GenerateKey(rand.Reader)
+ if err != nil {
+ t.Fatalf("failed to generate test key: %s", err)
+ }
+
+ for _, pubCurve := range curves {
+ if privCurve == pubCurve {
+ continue
+ }
+ t.Run(fmt.Sprintf("%s/%s", privCurve.name, pubCurve.name), func(t *testing.T) {
+ pub, err := pubCurve.curve.GenerateKey(rand.Reader)
+ if err != nil {
+ t.Fatalf("failed to generate test key: %s", err)
+ }
+ expected := "crypto/ecdh: private key and public key curves do not match"
+ _, err = priv.ECDH(pub.PublicKey())
+ if err.Error() != expected {
+ t.Fatalf("unexpected error: want %q, got %q", expected, err)
+ }
+ })
+ }
+ }
+}
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}
diff --git a/src/crypto/ecdh/x25519.go b/src/crypto/ecdh/x25519.go
new file mode 100644
index 0000000..dbc3ea9
--- /dev/null
+++ b/src/crypto/ecdh/x25519.go
@@ -0,0 +1,136 @@
+// 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/edwards25519/field"
+ "crypto/internal/randutil"
+ "errors"
+ "io"
+)
+
+var (
+ x25519PublicKeySize = 32
+ x25519PrivateKeySize = 32
+ x25519SharedSecretSize = 32
+)
+
+// X25519 returns a Curve which implements the X25519 function over Curve25519
+// (RFC 7748, Section 5).
+//
+// Multiple invocations of this function will return the same value, so it can
+// be used for equality checks and switch statements.
+func X25519() Curve { return x25519 }
+
+var x25519 = &x25519Curve{}
+
+type x25519Curve struct{}
+
+func (c *x25519Curve) String() string {
+ return "X25519"
+}
+
+func (c *x25519Curve) GenerateKey(rand io.Reader) (*PrivateKey, error) {
+ key := make([]byte, x25519PrivateKeySize)
+ randutil.MaybeReadByte(rand)
+ if _, err := io.ReadFull(rand, key); err != nil {
+ return nil, err
+ }
+ return c.NewPrivateKey(key)
+}
+
+func (c *x25519Curve) NewPrivateKey(key []byte) (*PrivateKey, error) {
+ if len(key) != x25519PrivateKeySize {
+ return nil, errors.New("crypto/ecdh: invalid private key size")
+ }
+ return &PrivateKey{
+ curve: c,
+ privateKey: append([]byte{}, key...),
+ }, nil
+}
+
+func (c *x25519Curve) privateKeyToPublicKey(key *PrivateKey) *PublicKey {
+ if key.curve != c {
+ panic("crypto/ecdh: internal error: converting the wrong key type")
+ }
+ k := &PublicKey{
+ curve: key.curve,
+ publicKey: make([]byte, x25519PublicKeySize),
+ }
+ x25519Basepoint := [32]byte{9}
+ x25519ScalarMult(k.publicKey, key.privateKey, x25519Basepoint[:])
+ return k
+}
+
+func (c *x25519Curve) NewPublicKey(key []byte) (*PublicKey, error) {
+ if len(key) != x25519PublicKeySize {
+ return nil, errors.New("crypto/ecdh: invalid public key")
+ }
+ return &PublicKey{
+ curve: c,
+ publicKey: append([]byte{}, key...),
+ }, nil
+}
+
+func (c *x25519Curve) ecdh(local *PrivateKey, remote *PublicKey) ([]byte, error) {
+ out := make([]byte, x25519SharedSecretSize)
+ x25519ScalarMult(out, local.privateKey, remote.publicKey)
+ if isZero(out) {
+ return nil, errors.New("crypto/ecdh: bad X25519 remote ECDH input: low order point")
+ }
+ return out, nil
+}
+
+func x25519ScalarMult(dst, scalar, point []byte) {
+ var e [32]byte
+
+ copy(e[:], scalar[:])
+ e[0] &= 248
+ e[31] &= 127
+ e[31] |= 64
+
+ var x1, x2, z2, x3, z3, tmp0, tmp1 field.Element
+ x1.SetBytes(point[:])
+ x2.One()
+ x3.Set(&x1)
+ z3.One()
+
+ swap := 0
+ for pos := 254; pos >= 0; pos-- {
+ b := e[pos/8] >> uint(pos&7)
+ b &= 1
+ swap ^= int(b)
+ x2.Swap(&x3, swap)
+ z2.Swap(&z3, swap)
+ swap = int(b)
+
+ tmp0.Subtract(&x3, &z3)
+ tmp1.Subtract(&x2, &z2)
+ x2.Add(&x2, &z2)
+ z2.Add(&x3, &z3)
+ z3.Multiply(&tmp0, &x2)
+ z2.Multiply(&z2, &tmp1)
+ tmp0.Square(&tmp1)
+ tmp1.Square(&x2)
+ x3.Add(&z3, &z2)
+ z2.Subtract(&z3, &z2)
+ x2.Multiply(&tmp1, &tmp0)
+ tmp1.Subtract(&tmp1, &tmp0)
+ z2.Square(&z2)
+
+ z3.Mult32(&tmp1, 121666)
+ x3.Square(&x3)
+ tmp0.Add(&tmp0, &z3)
+ z3.Multiply(&x1, &z2)
+ z2.Multiply(&tmp1, &tmp0)
+ }
+
+ x2.Swap(&x3, swap)
+ z2.Swap(&z3, swap)
+
+ z2.Invert(&z2)
+ x2.Multiply(&x2, &z2)
+ copy(dst[:], x2.Bytes())
+}