runnner: Switch to Go's crypto/ecdh module

This lets us fold X25519 into the ECDH bits, and drop
x/crypto/curve25519, but then means we need to carry a copy of the
crypto/elliptic version for P-224 because crypto/ecdh doesn't support
it.

Change-Id: Ie053679f26462c68c1d553de97e06afdb77c7eed
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/76208
Reviewed-by: Bob Beck <bbe@google.com>
Auto-Submit: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/ssl/test/runner/key_agreement.go b/ssl/test/runner/key_agreement.go
index fa57b9f..a65e771 100644
--- a/ssl/test/runner/key_agreement.go
+++ b/ssl/test/runner/key_agreement.go
@@ -6,12 +6,12 @@
 
 import (
 	"crypto"
+	"crypto/ecdh"
 	"crypto/ecdsa"
 	"crypto/ed25519"
 	"crypto/elliptic"
 	"crypto/mlkem"
 	"crypto/rsa"
-	"crypto/subtle"
 	"crypto/x509"
 	"errors"
 	"fmt"
@@ -20,7 +20,6 @@
 	"slices"
 
 	"boringssl.googlesource.com/boringssl.git/ssl/test/runner/kyber"
-	"golang.org/x/crypto/curve25519"
 )
 
 type keyType int
@@ -249,43 +248,117 @@
 	decap(config *Config, ciphertext []byte) (secret []byte, err error)
 }
 
-// ecdhKEM implements kemImplementation with an elliptic.Curve.
-//
-// TODO(davidben): Move this to Go's crypto/ecdh.
-type ecdhKEM struct {
-	curve      elliptic.Curve
+func applyBugsToECDHPublicKey(config *Config, publicKey []byte) []byte {
+	if config.Bugs.SendCompressedCoordinates {
+		l := (len(publicKey) - 1) / 2
+		tmp := make([]byte, 1+l)
+		// Extract the low-order bit of the y-coordinate.
+		tmp[0] = byte(2 | (publicKey[len(publicKey)-1] & 1))
+		copy(tmp[1:], publicKey[1:1+l])
+		publicKey = tmp
+	}
+	if config.Bugs.ECDHPointNotOnCurve {
+		// Flip a bit, so the point is no longer on the curve. This is
+		// guaranteed to be off the curve because we preserve x. That
+		// means the only other valid y is y' = p - y, but we've kept
+		// y's parity, so we cannot have accidentally reached y'.
+		publicKey[len(publicKey)-1] ^= 0x80
+	}
+	return publicKey
+}
+
+// p224KEM implements kemImplementation with P-224. Go's crypto/ecdh does not
+// support P-224.
+type p224KEM struct {
 	privateKey []byte
 }
 
-func (e *ecdhKEM) encapsulationKeySize() int {
-	fieldBytes := (e.curve.Params().BitSize + 7) / 8
+func (e *p224KEM) encapsulationKeySize() int {
+	fieldBytes := (elliptic.P224().Params().Params().BitSize + 7) / 8
 	return 1 + 2*fieldBytes
 }
 
+func (e *p224KEM) ciphertextSize() int {
+	return e.encapsulationKeySize()
+}
+
+func (e *p224KEM) generate(config *Config) (publicKey []byte, err error) {
+	p224 := elliptic.P224().Params()
+	var x, y *big.Int
+	e.privateKey, x, y, err = elliptic.GenerateKey(p224, config.rand())
+	if err != nil {
+		return nil, err
+	}
+	ret := elliptic.Marshal(p224, x, y)
+	ret = applyBugsToECDHPublicKey(config, ret)
+	return ret, nil
+}
+
+func (e *p224KEM) encap(config *Config, peerKey []byte) (ciphertext []byte, secret []byte, err error) {
+	ciphertext, err = e.generate(config)
+	if err != nil {
+		return nil, nil, err
+	}
+	secret, err = e.decap(config, peerKey)
+	if err != nil {
+		return nil, nil, err
+	}
+	return
+}
+
+func (e *p224KEM) decap(config *Config, ciphertext []byte) (secret []byte, err error) {
+	p224 := elliptic.P224().Params()
+	x, y := elliptic.Unmarshal(p224, ciphertext)
+	if x == nil {
+		return nil, errors.New("tls: invalid peer key")
+	}
+	x, _ = p224.ScalarMult(x, y, e.privateKey)
+	secret = make([]byte, (p224.Params().BitSize+7)>>3)
+	xBytes := x.Bytes()
+	copy(secret[len(secret)-len(xBytes):], xBytes)
+	return secret, nil
+}
+
+// ecdhKEM implements kemImplementation with crypto/ecdh.
+type ecdhKEM struct {
+	curve      ecdh.Curve
+	privateKey *ecdh.PrivateKey
+}
+
+func (e *ecdhKEM) encapsulationKeySize() int {
+	switch e.curve {
+	case ecdh.P256():
+		return 1 + 2*32
+	case ecdh.P384():
+		return 1 + 2*48
+	case ecdh.P521():
+		return 1 + 2*66
+	case ecdh.X25519():
+		return 32
+	}
+	panic(fmt.Sprintf("unknown curve %q", e.curve))
+}
+
 func (e *ecdhKEM) ciphertextSize() int {
 	return e.encapsulationKeySize()
 }
 
 func (e *ecdhKEM) generate(config *Config) (publicKey []byte, err error) {
-	var x, y *big.Int
-	e.privateKey, x, y, err = elliptic.GenerateKey(e.curve, config.rand())
+	if e.curve == ecdh.X25519() && config.Bugs.LowOrderX25519Point {
+		publicKey = []byte{0xe0, 0xeb, 0x7a, 0x7c, 0x3b, 0x41, 0xb8, 0xae, 0x16, 0x56, 0xe3, 0xfa, 0xf1, 0x9f, 0xc4, 0x6a, 0xda, 0x09, 0x8d, 0xeb, 0x9c, 0x32, 0xb1, 0xfd, 0x86, 0x62, 0x05, 0x16, 0x5f, 0x49, 0xb8, 0x00}
+		return
+	}
+	e.privateKey, err = e.curve.GenerateKey(config.rand())
 	if err != nil {
 		return nil, err
 	}
-	ret := elliptic.Marshal(e.curve, x, y)
-	if config.Bugs.SendCompressedCoordinates {
-		l := (len(ret) - 1) / 2
-		tmp := make([]byte, 1+l)
-		tmp[0] = byte(2 | y.Bit(0))
-		copy(tmp[1:], ret[1:1+l])
-		ret = tmp
-	}
-	if config.Bugs.ECDHPointNotOnCurve {
-		// Flip a bit, so the point is no longer on the curve. This is
-		// guaranteed to be off the curve because we preserve x. That
-		// means the only other valid y is y' = p - y, but we've kept
-		// y's parity, so we cannot have accidentally reached y'.
-		ret[len(ret)-1] ^= 0x80
+	ret := e.privateKey.PublicKey().Bytes()
+	if e.curve == ecdh.X25519() {
+		if config.Bugs.SetX25519HighBit {
+			ret[31] |= 0x80
+		}
+	} else {
+		ret = applyBugsToECDHPublicKey(config, ret)
 	}
 	return ret, nil
 }
@@ -303,80 +376,15 @@
 }
 
 func (e *ecdhKEM) decap(config *Config, ciphertext []byte) (secret []byte, err error) {
-	x, y := elliptic.Unmarshal(e.curve, ciphertext)
-	if x == nil {
-		return nil, errors.New("tls: invalid peer key")
-	}
-	x, _ = e.curve.ScalarMult(x, y, e.privateKey)
-	secret = make([]byte, (e.curve.Params().BitSize+7)>>3)
-	xBytes := x.Bytes()
-	copy(secret[len(secret)-len(xBytes):], xBytes)
-	return secret, nil
-}
-
-// x25519KEM implements kemImplementation with X25519.
-type x25519KEM struct {
-	privateKey [32]byte
-}
-
-func (e *x25519KEM) encapsulationKeySize() int {
-	return curve25519.PointSize
-}
-
-func (e *x25519KEM) ciphertextSize() int {
-	return curve25519.PointSize
-}
-
-func (e *x25519KEM) generate(config *Config) (publicKey []byte, err error) {
-	if config.Bugs.LowOrderX25519Point {
-		publicKey = []byte{0xe0, 0xeb, 0x7a, 0x7c, 0x3b, 0x41, 0xb8, 0xae, 0x16, 0x56, 0xe3, 0xfa, 0xf1, 0x9f, 0xc4, 0x6a, 0xda, 0x09, 0x8d, 0xeb, 0x9c, 0x32, 0xb1, 0xfd, 0x86, 0x62, 0x05, 0x16, 0x5f, 0x49, 0xb8, 0x00}
-		return
-	}
-
-	_, err = io.ReadFull(config.rand(), e.privateKey[:])
-	if err != nil {
-		return
-	}
-	var out [32]byte
-	curve25519.ScalarBaseMult(&out, &e.privateKey)
-	if config.Bugs.SetX25519HighBit {
-		out[31] |= 0x80
-	}
-	return out[:], nil
-}
-
-func (e *x25519KEM) encap(config *Config, peerKey []byte) (ciphertext []byte, secret []byte, err error) {
-	ciphertext, err = e.generate(config)
-	if err != nil {
-		return nil, nil, err
-	}
-	secret, err = e.decap(config, peerKey)
-	if err != nil {
-		return nil, nil, err
-	}
-	return
-}
-
-func (e *x25519KEM) decap(config *Config, ciphertext []byte) (secret []byte, err error) {
-	if len(ciphertext) != 32 {
-		return nil, errors.New("tls: invalid peer key")
-	}
-
-	if config.Bugs.LowOrderX25519Point {
+	if e.curve == ecdh.X25519() && config.Bugs.LowOrderX25519Point {
 		secret = make([]byte, 32)
 		return
 	}
-
-	var out [32]byte
-	curve25519.ScalarMult(&out, &e.privateKey, (*[32]byte)(ciphertext))
-
-	// Per RFC 7748, reject the all-zero value in constant time.
-	var zeros [32]byte
-	if subtle.ConstantTimeCompare(zeros[:], out[:]) == 1 {
-		return nil, errors.New("tls: X25519 value with wrong order")
+	peerKey, err := e.curve.NewPublicKey(ciphertext)
+	if err != nil {
+		return nil, fmt.Errorf("tls: invalid peer ECDH key: %s", err)
 	}
-
-	return out[:], nil
+	return e.privateKey.ECDH(peerKey)
 }
 
 // kyberKEM implements Kyber-768
@@ -567,21 +575,21 @@
 	var kem kemImplementation
 	switch id {
 	case CurveP224:
-		kem = &ecdhKEM{curve: elliptic.P224()}
+		kem = &p224KEM{}
 	case CurveP256:
-		kem = &ecdhKEM{curve: elliptic.P256()}
+		kem = &ecdhKEM{curve: ecdh.P256()}
 	case CurveP384:
-		kem = &ecdhKEM{curve: elliptic.P384()}
+		kem = &ecdhKEM{curve: ecdh.P384()}
 	case CurveP521:
-		kem = &ecdhKEM{curve: elliptic.P521()}
+		kem = &ecdhKEM{curve: ecdh.P521()}
 	case CurveX25519:
-		kem = &x25519KEM{}
+		kem = &ecdhKEM{curve: ecdh.X25519()}
 	case CurveX25519Kyber768:
 		// draft-tls-westerbaan-xyber768d00-03
-		kem = &concatKEM{kem1: &x25519KEM{}, kem2: &kyberKEM{}}
+		kem = &concatKEM{kem1: &ecdhKEM{curve: ecdh.X25519()}, kem2: &kyberKEM{}}
 	case CurveX25519MLKEM768:
 		// draft-kwiatkowski-tls-ecdhe-mlkem-01
-		kem = &concatKEM{kem1: &mlkem768KEM{}, kem2: &x25519KEM{}}
+		kem = &concatKEM{kem1: &mlkem768KEM{}, kem2: &ecdhKEM{curve: ecdh.X25519()}}
 	default:
 		return nil, false
 	}