Revamp test coverage for invalid key shares
We weren't testing all kinds of load-bearing checks in the TLS stack
around key shares. Fix this.
- Rework runner's KEM abstraction so that all the operations can get at
config. That saves a lot of manual plumbing.
- Make the bad ECDH point something not on the curve. That's a bit more
interesting of a test case.
- Test X25519 low order point rejection.
- Test truncating and extending the key share for all cases.
- Run X25519 tests in X25519-based hybrids as well.
Bug: 40910498
Change-Id: I93907dbb4bd4177252376c8efb859de6db3c4189
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/70927
Commit-Queue: Bob Beck <bbe@google.com>
Auto-Submit: David Benjamin <davidben@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 2eedd62..a63b3c7 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -671,9 +671,15 @@
// than the negotiated one.
SendCurve CurveID
- // InvalidECDHPoint, if true, causes the ECC points in
- // ServerKeyExchange or ClientKeyExchange messages to be invalid.
- InvalidECDHPoint bool
+ // ECDHPointNotOnCurve, if true, causes the ECDH points to not be on the
+ // curve.
+ ECDHPointNotOnCurve bool
+
+ // TruncateKeyShare, if true, causes key shares to be truncated by one byte.
+ TruncateKeyShare bool
+
+ // PadKeyShare, if true, causes key shares to be truncated to one byte.
+ PadKeyShare bool
// BadECDSAR controls ways in which the 'r' value of an ECDSA signature
// can be invalid.
@@ -1920,6 +1926,14 @@
// high-order bit.
SetX25519HighBit bool
+ // LowOrderX25519Point, if true, causes X25519 key shares to be a low
+ // order point.
+ LowOrderX25519Point bool
+
+ // MLKEMEncapKeyNotReduced, if true, causes the ML-KEM encapsulation key
+ // to not be fully reduced.
+ MLKEMEncapKeyNotReduced bool
+
// DuplicateCompressedCertAlgs, if true, causes two, equal, certificate
// compression algorithm IDs to be sent.
DuplicateCompressedCertAlgs bool
diff --git a/ssl/test/runner/fuzzer_mode.json b/ssl/test/runner/fuzzer_mode.json
index e7c8ad7..6967ae7 100644
--- a/ssl/test/runner/fuzzer_mode.json
+++ b/ssl/test/runner/fuzzer_mode.json
@@ -15,6 +15,7 @@
"UnexpectedUnencryptedExtension-Client-TLS13": "Fuzzer mode will not read the peer's alert as a MAC error",
"UnknownUnencryptedExtension-Client-TLS13": "Fuzzer mode will not read the peer's alert as a MAC error",
"WrongMessageType-TLS13-ServerHello-*": "Fuzzer mode will not read the peer's alert as a MAC error",
+ "CurveTest-Invalid-*-Client-*-TLS13": "Fuzzer mode will not read the peer's alert as a MAC error",
"BadECDSA-*": "Fuzzer mode always accepts a signature.",
"*-InvalidSignature-*": "Fuzzer mode always accepts a signature.",
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index cfac427..e54e284 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -662,7 +662,7 @@
if !ok {
continue
}
- publicKey, err := kem.generate(c.config.rand())
+ publicKey, err := kem.generate(c.config)
if err != nil {
return nil, err
}
@@ -670,9 +670,6 @@
if c.config.Bugs.SendCurve != 0 {
curveID = c.config.Bugs.SendCurve
}
- if c.config.Bugs.InvalidECDHPoint {
- publicKey[0] ^= 0xff
- }
hello.keyShares = append(hello.keyShares, keyShareEntry{
group: curveID,
@@ -1153,7 +1150,7 @@
c.curveID = hs.serverHello.keyShare.group
var err error
- ecdheSecret, err = kem.decap(hs.serverHello.keyShare.keyExchange)
+ ecdheSecret, err = kem.decap(c.config, hs.serverHello.keyShare.keyExchange)
if err != nil {
return err
}
@@ -1562,7 +1559,7 @@
if !ok {
return errors.New("tls: Unable to get curve requested in HelloRetryRequest")
}
- publicKey, err := kem.generate(c.config.rand())
+ publicKey, err := kem.generate(c.config)
if err != nil {
return err
}
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index fa7eb9c..9265edd 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -978,7 +978,7 @@
// avoid crashing.
kem2, _ := kemForCurveID(selectedCurve, config)
var err error
- peerKey, err = kem2.generate(config.rand())
+ peerKey, err = kem2.generate(config)
if err != nil {
return err
}
@@ -986,7 +986,7 @@
peerKey = selectedKeyShare.keyExchange
}
- ciphertext, ecdheSecret, err := kem.encap(config.rand(), peerKey)
+ ciphertext, ecdheSecret, err := kem.encap(config, peerKey)
if err != nil {
c.sendAlert(alertHandshakeFailure)
return err
@@ -999,9 +999,6 @@
if c.config.Bugs.SendCurve != 0 {
curveID = config.Bugs.SendCurve
}
- if c.config.Bugs.InvalidECDHPoint {
- ciphertext[0] ^= 0xff
- }
hs.hello.keyShare = keyShareEntry{
group: curveID,
diff --git a/ssl/test/runner/key_agreement.go b/ssl/test/runner/key_agreement.go
index a4bbfa5..acea236 100644
--- a/ssl/test/runner/key_agreement.go
+++ b/ssl/test/runner/key_agreement.go
@@ -239,23 +239,22 @@
ciphertextSize() int
// generate generates a keypair using rand. It returns the encoded public key.
- generate(rand io.Reader) (publicKey []byte, err error)
+ generate(config *Config) (publicKey []byte, err error)
// encap generates a symmetric, shared secret, encapsulates it with |peerKey|.
// It returns the encapsulated shared secret and the secret itself.
- encap(rand io.Reader, peerKey []byte) (ciphertext []byte, secret []byte, err error)
+ encap(config *Config, peerKey []byte) (ciphertext []byte, secret []byte, err error)
// decap decapsulates |ciphertext| and returns the resulting shared secret.
- decap(ciphertext []byte) (secret []byte, err error)
+ 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
- privateKey []byte
- sendCompressed bool
+ curve elliptic.Curve
+ privateKey []byte
}
func (e *ecdhKEM) encapsulationKeySize() int {
@@ -267,36 +266,43 @@
return e.encapsulationKeySize()
}
-func (e *ecdhKEM) generate(rand io.Reader) (publicKey []byte, err error) {
+func (e *ecdhKEM) generate(config *Config) (publicKey []byte, err error) {
var x, y *big.Int
- e.privateKey, x, y, err = elliptic.GenerateKey(e.curve, rand)
+ e.privateKey, x, y, err = elliptic.GenerateKey(e.curve, config.rand())
if err != nil {
return nil, err
}
ret := elliptic.Marshal(e.curve, x, y)
- if e.sendCompressed {
+ 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
+ }
return ret, nil
}
-func (e *ecdhKEM) encap(rand io.Reader, peerKey []byte) (ciphertext []byte, secret []byte, err error) {
- ciphertext, err = e.generate(rand)
+func (e *ecdhKEM) 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(peerKey)
+ secret, err = e.decap(config, peerKey)
if err != nil {
return nil, nil, err
}
return
}
-func (e *ecdhKEM) decap(ciphertext []byte) (secret []byte, err error) {
+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")
@@ -311,7 +317,6 @@
// x25519KEM implements kemImplementation with X25519.
type x25519KEM struct {
privateKey [32]byte
- setHighBit bool
}
func (e *x25519KEM) encapsulationKeySize() int {
@@ -322,35 +327,46 @@
return curve25519.PointSize
}
-func (e *x25519KEM) generate(rand io.Reader) (publicKey []byte, err error) {
- _, err = io.ReadFull(rand, e.privateKey[:])
+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 e.setHighBit {
+ if config.Bugs.SetX25519HighBit {
out[31] |= 0x80
}
return out[:], nil
}
-func (e *x25519KEM) encap(rand io.Reader, peerKey []byte) (ciphertext []byte, secret []byte, err error) {
- ciphertext, err = e.generate(rand)
+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(peerKey)
+ secret, err = e.decap(config, peerKey)
if err != nil {
return nil, nil, err
}
return
}
-func (e *x25519KEM) decap(ciphertext []byte) (secret []byte, err error) {
+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 {
+ secret = make([]byte, 32)
+ return
+ }
+
var out [32]byte
curve25519.ScalarMult(&out, &e.privateKey, (*[32]byte)(ciphertext))
@@ -376,9 +392,9 @@
return kyber.CiphertextSize
}
-func (e *kyberKEM) generate(rand io.Reader) (publicKey []byte, err error) {
+func (e *kyberKEM) generate(config *Config) (publicKey []byte, err error) {
var kyberEntropy [64]byte
- if _, err := io.ReadFull(rand, kyberEntropy[:]); err != nil {
+ if _, err := io.ReadFull(config.rand(), kyberEntropy[:]); err != nil {
return nil, err
}
var kyberPublic *[kyber.PublicKeySize]byte
@@ -386,7 +402,7 @@
return kyberPublic[:], nil
}
-func (e *kyberKEM) encap(rand io.Reader, peerKey []byte) (ciphertext []byte, secret []byte, err error) {
+func (e *kyberKEM) encap(config *Config, peerKey []byte) (ciphertext []byte, secret []byte, err error) {
if len(peerKey) != kyber.PublicKeySize {
return nil, nil, errors.New("tls: bad length Kyber offer")
}
@@ -397,14 +413,14 @@
}
var kyberShared, kyberEntropy [32]byte
- if _, err := io.ReadFull(rand, kyberEntropy[:]); err != nil {
+ if _, err := io.ReadFull(config.rand(), kyberEntropy[:]); err != nil {
return nil, nil, err
}
kyberCiphertext := kyberPublicKey.Encap(kyberShared[:], &kyberEntropy)
return kyberCiphertext[:], kyberShared[:], nil
}
-func (e *kyberKEM) decap(ciphertext []byte) (secret []byte, err error) {
+func (e *kyberKEM) decap(config *Config, ciphertext []byte) (secret []byte, err error) {
if len(ciphertext) != kyber.CiphertextSize {
return nil, errors.New("tls: bad length Kyber reply")
}
@@ -427,19 +443,26 @@
return mlkem768.CiphertextSize
}
-func (m *mlkem768KEM) generate(rand io.Reader) (publicKey []byte, err error) {
+func (m *mlkem768KEM) generate(config *Config) (publicKey []byte, err error) {
m.decapKey, err = mlkem768.GenerateKey()
if err != nil {
return
}
- return m.decapKey.EncapsulationKey(), nil
+ publicKey = m.decapKey.EncapsulationKey()
+ if config.Bugs.MLKEMEncapKeyNotReduced {
+ // Set the first 12 bits so that the first word is definitely
+ // not reduced.
+ publicKey[0] |= 0xff
+ publicKey[1] |= 0xf
+ }
+ return
}
-func (m *mlkem768KEM) encap(rand io.Reader, peerKey []byte) (ciphertext []byte, secret []byte, err error) {
+func (m *mlkem768KEM) encap(config *Config, peerKey []byte) (ciphertext []byte, secret []byte, err error) {
return mlkem768.Encapsulate(peerKey)
}
-func (m *mlkem768KEM) decap(ciphertext []byte) (secret []byte, err error) {
+func (m *mlkem768KEM) decap(config *Config, ciphertext []byte) (secret []byte, err error) {
return mlkem768.Decapsulate(m.decapKey, ciphertext)
}
@@ -456,74 +479,115 @@
return c.kem1.ciphertextSize() + c.kem2.ciphertextSize()
}
-func (c *concatKEM) generate(rand io.Reader) (publicKey []byte, err error) {
- publicKey1, err := c.kem1.generate(rand)
+func (c *concatKEM) generate(config *Config) (publicKey []byte, err error) {
+ publicKey1, err := c.kem1.generate(config)
if err != nil {
return nil, err
}
- publicKey2, err := c.kem2.generate(rand)
+ publicKey2, err := c.kem2.generate(config)
if err != nil {
return nil, err
}
return slices.Concat(publicKey1, publicKey2), nil
}
-func (c *concatKEM) encap(rand io.Reader, peerKey []byte) (ciphertext []byte, secret []byte, err error) {
+func (c *concatKEM) encap(config *Config, peerKey []byte) (ciphertext []byte, secret []byte, err error) {
encapKeySize1 := c.kem1.encapsulationKeySize()
if len(peerKey) < encapKeySize1 {
return nil, nil, errors.New("tls: invalid peer key")
}
peerKey1, peerKey2 := peerKey[:encapKeySize1], peerKey[encapKeySize1:]
- ciphertext1, secret1, err := c.kem1.encap(rand, peerKey1)
+ ciphertext1, secret1, err := c.kem1.encap(config, peerKey1)
if err != nil {
return nil, nil, err
}
- ciphertext2, secret2, err := c.kem2.encap(rand, peerKey2)
+ ciphertext2, secret2, err := c.kem2.encap(config, peerKey2)
if err != nil {
return nil, nil, err
}
return slices.Concat(ciphertext1, ciphertext2), slices.Concat(secret1, secret2), nil
}
-func (c *concatKEM) decap(ciphertext []byte) (secret []byte, err error) {
+func (c *concatKEM) decap(config *Config, ciphertext []byte) (secret []byte, err error) {
ciphertextSize1 := c.kem1.ciphertextSize()
if len(ciphertext) < ciphertextSize1 {
return nil, errors.New("tls: invalid ciphertext")
}
ciphertext1, ciphertext2 := ciphertext[:ciphertextSize1], ciphertext[ciphertextSize1:]
- secret1, err := c.kem1.decap(ciphertext1)
+ secret1, err := c.kem1.decap(config, ciphertext1)
if err != nil {
return nil, err
}
- secret2, err := c.kem2.decap(ciphertext2)
+ secret2, err := c.kem2.decap(config, ciphertext2)
if err != nil {
return nil, err
}
return slices.Concat(secret1, secret2), nil
}
+type transformKEM struct {
+ kem kemImplementation
+ transform func([]byte) []byte
+}
+
+func (t *transformKEM) encapsulationKeySize() int {
+ return t.kem.encapsulationKeySize()
+}
+
+func (t *transformKEM) ciphertextSize() int {
+ return t.kem.ciphertextSize()
+}
+
+func (t *transformKEM) generate(config *Config) (publicKey []byte, err error) {
+ publicKey, err = t.kem.generate(config)
+ if err == nil {
+ publicKey = t.transform(publicKey)
+ }
+ return
+}
+
+func (t *transformKEM) encap(config *Config, peerKey []byte) (ciphertext []byte, secret []byte, err error) {
+ ciphertext, secret, err = t.kem.encap(config, peerKey)
+ if err == nil {
+ ciphertext = t.transform(ciphertext)
+ }
+ return
+}
+
+func (t *transformKEM) decap(config *Config, ciphertext []byte) (secret []byte, err error) {
+ return t.kem.decap(config, ciphertext)
+}
+
func kemForCurveID(id CurveID, config *Config) (kemImplementation, bool) {
+ var kem kemImplementation
switch id {
case CurveP224:
- return &ecdhKEM{curve: elliptic.P224(), sendCompressed: config.Bugs.SendCompressedCoordinates}, true
+ kem = &ecdhKEM{curve: elliptic.P224()}
case CurveP256:
- return &ecdhKEM{curve: elliptic.P256(), sendCompressed: config.Bugs.SendCompressedCoordinates}, true
+ kem = &ecdhKEM{curve: elliptic.P256()}
case CurveP384:
- return &ecdhKEM{curve: elliptic.P384(), sendCompressed: config.Bugs.SendCompressedCoordinates}, true
+ kem = &ecdhKEM{curve: elliptic.P384()}
case CurveP521:
- return &ecdhKEM{curve: elliptic.P521(), sendCompressed: config.Bugs.SendCompressedCoordinates}, true
+ kem = &ecdhKEM{curve: elliptic.P521()}
case CurveX25519:
- return &x25519KEM{setHighBit: config.Bugs.SetX25519HighBit}, true
+ kem = &x25519KEM{}
case CurveX25519Kyber768:
// draft-tls-westerbaan-xyber768d00-03
- return &concatKEM{kem1: &x25519KEM{setHighBit: config.Bugs.SetX25519HighBit}, kem2: &kyberKEM{}}, true
+ kem = &concatKEM{kem1: &x25519KEM{}, kem2: &kyberKEM{}}
case CurveX25519MLKEM768:
// draft-kwiatkowski-tls-ecdhe-mlkem-01
- return &concatKEM{kem1: &mlkem768KEM{}, kem2: &x25519KEM{setHighBit: config.Bugs.SetX25519HighBit}}, true
+ kem = &concatKEM{kem1: &mlkem768KEM{}, kem2: &x25519KEM{}}
default:
return nil, false
}
+ if config.Bugs.TruncateKeyShare {
+ kem = &transformKEM{kem: kem, transform: func(b []byte) []byte { return b[:len(b)-1] }}
+ }
+ if config.Bugs.PadKeyShare {
+ kem = &transformKEM{kem: kem, transform: func(b []byte) []byte { return slices.Concat(b, []byte{0}) }}
+ }
+ return kem, true
}
// keyAgreementAuthentication is a helper interface that specifies how
@@ -662,7 +726,7 @@
}
func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Credential, clientHello *clientHelloMsg, hello *serverHelloMsg, version uint16) (*serverKeyExchangeMsg, error) {
- var curveid CurveID
+ var curveID CurveID
preferredCurves := config.curvePreferences()
NextCandidate:
@@ -674,23 +738,23 @@
for _, c := range clientHello.supportedCurves {
if candidate == c {
- curveid = c
+ curveID = c
break NextCandidate
}
}
}
- if curveid == 0 {
+ if curveID == 0 {
return nil, errors.New("tls: no supported elliptic curves offered")
}
var ok bool
- if ka.kem, ok = kemForCurveID(curveid, config); !ok {
+ if ka.kem, ok = kemForCurveID(curveID, config); !ok {
return nil, errors.New("tls: preferredCurves includes unsupported curve")
}
- ka.curveID = curveid
+ ka.curveID = curveID
- publicKey, err := ka.kem.generate(config.rand())
+ publicKey, err := ka.kem.generate(config)
if err != nil {
return nil, err
}
@@ -699,15 +763,12 @@
serverECDHParams := make([]byte, 1+2+1+len(publicKey))
serverECDHParams[0] = 3 // named curve
if config.Bugs.SendCurve != 0 {
- curveid = config.Bugs.SendCurve
+ curveID = config.Bugs.SendCurve
}
- serverECDHParams[1] = byte(curveid >> 8)
- serverECDHParams[2] = byte(curveid)
+ serverECDHParams[1] = byte(curveID >> 8)
+ serverECDHParams[2] = byte(curveID)
serverECDHParams[3] = byte(len(publicKey))
copy(serverECDHParams[4:], publicKey)
- if config.Bugs.InvalidECDHPoint {
- serverECDHParams[4] ^= 0xff
- }
return ka.auth.signParameters(config, cert, clientHello, hello, serverECDHParams)
}
@@ -716,7 +777,7 @@
if len(ckx.ciphertext) == 0 || int(ckx.ciphertext[0]) != len(ckx.ciphertext)-1 {
return nil, errClientKeyExchange
}
- return ka.kem.decap(ckx.ciphertext[1:])
+ return ka.kem.decap(config, ckx.ciphertext[1:])
}
func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, key crypto.PublicKey, skx *serverKeyExchangeMsg) error {
@@ -752,7 +813,7 @@
return nil, nil, errors.New("missing ServerKeyExchange message")
}
- ciphertext, secret, err := ka.kem.encap(config.rand(), ka.peerKey)
+ ciphertext, secret, err := ka.kem.encap(config, ka.peerKey)
if err != nil {
return nil, nil, err
}
@@ -761,9 +822,6 @@
ckx.ciphertext = make([]byte, 1+len(ciphertext))
ckx.ciphertext[0] = byte(len(ciphertext))
copy(ckx.ciphertext[1:], ciphertext)
- if config.Bugs.InvalidECDHPoint {
- ckx.ciphertext[1] ^= 0xff
- }
return secret, ckx, nil
}
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 6179ee0..481a65c 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -438,6 +438,16 @@
serverTest
)
+func (t testType) String() string {
+ switch t {
+ case clientTest:
+ return "Client"
+ case serverTest:
+ return "Server"
+ }
+ panic(fmt.Sprintf("bad test type %d", t))
+}
+
type protocol int
const (
@@ -11765,93 +11775,182 @@
return r == CurveX25519Kyber768 || r == CurveX25519MLKEM768
}
+func isECDHGroup(r CurveID) bool {
+ return r == CurveP224 || r == CurveP256 || r == CurveP384 || r == CurveP521
+}
+
+func isX25519Group(r CurveID) bool {
+ return r == CurveX25519 || r == CurveX25519Kyber768 || r == CurveX25519MLKEM768
+}
+
func addCurveTests() {
+ // A set of cipher suites that ensures some curve-using mode is used.
+ // Without this, servers may fall back to RSA key exchange.
+ ecdheCiphers := []uint16{
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ TLS_AES_256_GCM_SHA384,
+ }
+
for _, curve := range testCurves {
for _, ver := range tlsVersions {
if isPqGroup(curve.id) && ver.version < VersionTLS13 {
continue
}
+ for _, testType := range []testType{clientTest, serverTest} {
+ suffix := fmt.Sprintf("%s-%s-%s", testType, curve.name, ver.name)
- suffix := curve.name + "-" + ver.name
-
- testCases = append(testCases, testCase{
- name: "CurveTest-Client-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- CipherSuites: []uint16{
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- TLS_AES_256_GCM_SHA384,
- },
- CurvePreferences: []CurveID{curve.id},
- },
- flags: append(
- []string{"-expect-curve-id", strconv.Itoa(int(curve.id))},
- flagInts("-curves", shimConfig.AllCurves)...,
- ),
- expectations: connectionExpectations{
- curveID: curve.id,
- },
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "CurveTest-Server-" + suffix,
- config: Config{
- MaxVersion: ver.version,
- CipherSuites: []uint16{
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- TLS_AES_256_GCM_SHA384,
- },
- CurvePreferences: []CurveID{curve.id},
- },
- flags: append(
- []string{"-expect-curve-id", strconv.Itoa(int(curve.id))},
- flagInts("-curves", shimConfig.AllCurves)...,
- ),
- expectations: connectionExpectations{
- curveID: curve.id,
- },
- })
-
- if curve.id != CurveX25519 && !isPqGroup(curve.id) {
testCases = append(testCases, testCase{
- name: "CurveTest-Client-Compressed-" + suffix,
+ testType: testType,
+ name: "CurveTest-" + suffix,
config: Config{
- MaxVersion: ver.version,
- CipherSuites: []uint16{
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- TLS_AES_256_GCM_SHA384,
- },
+ MaxVersion: ver.version,
+ CipherSuites: ecdheCiphers,
+ CurvePreferences: []CurveID{curve.id},
+ },
+ flags: append(
+ []string{"-expect-curve-id", strconv.Itoa(int(curve.id))},
+ flagInts("-curves", shimConfig.AllCurves)...,
+ ),
+ expectations: connectionExpectations{
+ curveID: curve.id,
+ },
+ })
+
+ badKeyShareLocalError := "remote error: error decoding message"
+ if testType == clientTest && ver.version >= VersionTLS13 {
+ // If the shim is a TLS 1.3 client and the runner sends a bad
+ // key share, the runner never reads the client's cleartext
+ // alert because the runner has already started encrypting by
+ // the time the client sees it.
+ badKeyShareLocalError = "local error: bad record MAC"
+ }
+
+ testCases = append(testCases, testCase{
+ testType: testType,
+ name: "CurveTest-Invalid-TruncateKeyShare-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ CipherSuites: ecdheCiphers,
CurvePreferences: []CurveID{curve.id},
Bugs: ProtocolBugs{
- SendCompressedCoordinates: true,
+ TruncateKeyShare: true,
},
},
- flags: flagInts("-curves", shimConfig.AllCurves),
- shouldFail: true,
- expectedError: ":BAD_ECPOINT:",
+ flags: flagInts("-curves", shimConfig.AllCurves),
+ shouldFail: true,
+ expectedError: ":BAD_ECPOINT:",
+ expectedLocalError: badKeyShareLocalError,
})
+
testCases = append(testCases, testCase{
- testType: serverTest,
- name: "CurveTest-Server-Compressed-" + suffix,
+ testType: testType,
+ name: "CurveTest-Invalid-PadKeyShare-" + suffix,
config: Config{
- MaxVersion: ver.version,
- CipherSuites: []uint16{
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- TLS_AES_256_GCM_SHA384,
- },
+ MaxVersion: ver.version,
+ CipherSuites: ecdheCiphers,
CurvePreferences: []CurveID{curve.id},
Bugs: ProtocolBugs{
- SendCompressedCoordinates: true,
+ PadKeyShare: true,
},
},
- flags: flagInts("-curves", shimConfig.AllCurves),
- shouldFail: true,
- expectedError: ":BAD_ECPOINT:",
+ flags: flagInts("-curves", shimConfig.AllCurves),
+ shouldFail: true,
+ expectedError: ":BAD_ECPOINT:",
+ expectedLocalError: badKeyShareLocalError,
})
+
+ if isECDHGroup(curve.id) {
+ testCases = append(testCases, testCase{
+ testType: testType,
+ name: "CurveTest-Invalid-Compressed-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ CipherSuites: ecdheCiphers,
+ CurvePreferences: []CurveID{curve.id},
+ Bugs: ProtocolBugs{
+ SendCompressedCoordinates: true,
+ },
+ },
+ flags: flagInts("-curves", shimConfig.AllCurves),
+ shouldFail: true,
+ expectedError: ":BAD_ECPOINT:",
+ expectedLocalError: badKeyShareLocalError,
+ })
+ testCases = append(testCases, testCase{
+ testType: testType,
+ name: "CurveTest-Invalid-NotOnCurve-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ CipherSuites: ecdheCiphers,
+ CurvePreferences: []CurveID{curve.id},
+ Bugs: ProtocolBugs{
+ ECDHPointNotOnCurve: true,
+ },
+ },
+ flags: flagInts("-curves", shimConfig.AllCurves),
+ shouldFail: true,
+ expectedError: ":BAD_ECPOINT:",
+ expectedLocalError: badKeyShareLocalError,
+ })
+ }
+
+ if isX25519Group(curve.id) {
+ // Implementations should mask off the high order bit in X25519.
+ testCases = append(testCases, testCase{
+ testType: testType,
+ name: "CurveTest-SetX25519HighBit-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ CipherSuites: ecdheCiphers,
+ CurvePreferences: []CurveID{curve.id},
+ Bugs: ProtocolBugs{
+ SetX25519HighBit: true,
+ },
+ },
+ flags: flagInts("-curves", shimConfig.AllCurves),
+ expectations: connectionExpectations{
+ curveID: curve.id,
+ },
+ })
+
+ // Implementations should reject low order points.
+ testCases = append(testCases, testCase{
+ testType: testType,
+ name: "CurveTest-Invalid-LowOrderX25519Point-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ CipherSuites: ecdheCiphers,
+ CurvePreferences: []CurveID{curve.id},
+ Bugs: ProtocolBugs{
+ LowOrderX25519Point: true,
+ },
+ },
+ flags: flagInts("-curves", shimConfig.AllCurves),
+ shouldFail: true,
+ expectedError: ":BAD_ECPOINT:",
+ expectedLocalError: badKeyShareLocalError,
+ })
+ }
+
+ if curve.id == CurveX25519MLKEM768 && testType == serverTest {
+ testCases = append(testCases, testCase{
+ testType: testType,
+ name: "CurveTest-Invalid-MLKEMEncapKeyNotReduced-" + suffix,
+ config: Config{
+ MaxVersion: ver.version,
+ CipherSuites: ecdheCiphers,
+ CurvePreferences: []CurveID{curve.id},
+ Bugs: ProtocolBugs{
+ MLKEMEncapKeyNotReduced: true,
+ },
+ },
+ flags: flagInts("-curves", shimConfig.AllCurves),
+ shouldFail: true,
+ expectedError: ":BAD_ECPOINT:",
+ expectedLocalError: badKeyShareLocalError,
+ })
+ }
}
}
}
@@ -11979,60 +12078,6 @@
expectedError: ":WRONG_CURVE:",
})
- // Test invalid curve points.
- testCases = append(testCases, testCase{
- name: "InvalidECDHPoint-Client",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- CurvePreferences: []CurveID{CurveP256},
- Bugs: ProtocolBugs{
- InvalidECDHPoint: true,
- },
- },
- shouldFail: true,
- expectedError: ":BAD_ECPOINT:",
- })
- testCases = append(testCases, testCase{
- name: "InvalidECDHPoint-Client-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- CurvePreferences: []CurveID{CurveP256},
- Bugs: ProtocolBugs{
- InvalidECDHPoint: true,
- },
- },
- shouldFail: true,
- expectedError: ":BAD_ECPOINT:",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "InvalidECDHPoint-Server",
- config: Config{
- MaxVersion: VersionTLS12,
- CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
- CurvePreferences: []CurveID{CurveP256},
- Bugs: ProtocolBugs{
- InvalidECDHPoint: true,
- },
- },
- shouldFail: true,
- expectedError: ":BAD_ECPOINT:",
- })
- testCases = append(testCases, testCase{
- testType: serverTest,
- name: "InvalidECDHPoint-Server-TLS13",
- config: Config{
- MaxVersion: VersionTLS13,
- CurvePreferences: []CurveID{CurveP256},
- Bugs: ProtocolBugs{
- InvalidECDHPoint: true,
- },
- },
- shouldFail: true,
- expectedError: ":BAD_ECPOINT:",
- })
-
// The previous curve ID should be reported on TLS 1.2 resumption.
testCases = append(testCases, testCase{
name: "CurveID-Resume-Client",
@@ -12210,22 +12255,6 @@
expectedError: ":ERROR_PARSING_EXTENSION:",
})
- // Implementations should mask off the high order bit in X25519.
- testCases = append(testCases, testCase{
- name: "SetX25519HighBit",
- config: Config{
- CipherSuites: []uint16{
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
- TLS_AES_128_GCM_SHA256,
- },
- CurvePreferences: []CurveID{CurveX25519},
- Bugs: ProtocolBugs{
- SetX25519HighBit: true,
- },
- },
- })
-
// Post-quantum groups require TLS 1.3.
for _, curve := range testCurves {
if !isPqGroup(curve.id) {