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 }