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
}