Adding HelloRetryRequest.
[Tests added by davidben.]
Change-Id: I0d54a4f8b8fe91b348ff22658d95340cdb48b089
Reviewed-on: https://boringssl-review.googlesource.com/8850
Reviewed-by: Steven Valdez <svaldez@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index e20b2d5..b2d31dc 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -206,6 +206,7 @@
TLSUnique []byte // the tls-unique channel binding
SCTList []byte // signed certificate timestamp list
PeerSignatureAlgorithm signatureAlgorithm // algorithm used by the peer in the handshake
+ CurveID CurveID // the curve used in ECDHE
}
// ClientAuthType declares the policy the server will follow for
@@ -438,10 +439,16 @@
// or CertificateVerify message should be invalid.
InvalidSignature bool
- // SendCurve, if non-zero, causes the ServerKeyExchange message to use
- // the specified curve ID rather than the negotiated one.
+ // SendCurve, if non-zero, causes the server to send the specified curve
+ // ID in ServerKeyExchange (TLS 1.2) or ServerHello (TLS 1.3) rather
+ // than the negotiated one.
SendCurve CurveID
+ // SendHelloRetryRequestCurve, if non-zero, causes the server to send
+ // the specified curve in HelloRetryRequest rather than the negotiated
+ // one.
+ SendHelloRetryRequestCurve CurveID
+
// InvalidECDHPoint, if true, causes the ECC points in
// ServerKeyExchange or ClientKeyExchange messages to be invalid.
InvalidECDHPoint bool
@@ -953,6 +960,16 @@
// instead.
MissingKeyShare bool
+ // SecondClientHelloMissingKeyShare, if true, causes the second TLS 1.3
+ // ClientHello to skip sending a key_share extension and use the zero
+ // ECDHE secret instead.
+ SecondClientHelloMissingKeyShare bool
+
+ // MisinterpretHelloRetryRequestCurve, if non-zero, causes the TLS 1.3
+ // client to pretend the server requested a HelloRetryRequest with the
+ // given curve rather than the actual one.
+ MisinterpretHelloRetryRequestCurve CurveID
+
// DuplicateKeyShares, if true, causes the TLS 1.3 client to send two
// copies of each KeyShareEntry.
DuplicateKeyShares bool
@@ -964,6 +981,22 @@
// EncryptedExtensionsWithKeyShare, if true, causes the TLS 1.3 server to
// include the KeyShare extension in the EncryptedExtensions block.
EncryptedExtensionsWithKeyShare bool
+
+ // UnnecessaryHelloRetryRequest, if true, causes the TLS 1.3 server to
+ // send a HelloRetryRequest regardless of whether it needs to.
+ UnnecessaryHelloRetryRequest bool
+
+ // SecondHelloRetryRequest, if true, causes the TLS 1.3 server to send
+ // two HelloRetryRequests instead of one.
+ SecondHelloRetryRequest bool
+
+ // SendServerHelloVersion, if non-zero, causes the server to send the
+ // specified version in ServerHello rather than the true version.
+ SendServerHelloVersion uint16
+
+ // SkipHelloRetryRequest, if true, causes the TLS 1.3 server to not send
+ // HelloRetryRequest.
+ SkipHelloRetryRequest bool
}
func (c *Config) serverInit() {
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index 3789b28..fbd501a 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -53,6 +53,9 @@
// peerSignatureAlgorithm contains the signature algorithm that was used
// by the peer in the handshake, or zero if not applicable.
peerSignatureAlgorithm signatureAlgorithm
+ // curveID contains the curve that was used in the handshake, or zero if
+ // not applicable.
+ curveID CurveID
clientRandom, serverRandom [32]byte
exporterSecret []byte
@@ -1500,6 +1503,7 @@
state.TLSUnique = c.firstFinished[:]
state.SCTList = c.sctList
state.PeerSignatureAlgorithm = c.peerSignatureAlgorithm
+ state.CurveID = c.curveID
}
return state
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index 291fb75..50f3fa8 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -140,7 +140,7 @@
}
if c.config.Bugs.MissingKeyShare {
- hello.keyShares = nil
+ hello.hasKeyShares = false
}
}
@@ -337,6 +337,9 @@
var secondHelloBytes []byte
if haveHelloRetryRequest {
var hrrCurveFound bool
+ if c.config.Bugs.MisinterpretHelloRetryRequestCurve != 0 {
+ helloRetryRequest.selectedGroup = c.config.Bugs.MisinterpretHelloRetryRequestCurve
+ }
group := helloRetryRequest.selectedGroup
for _, curveID := range hello.supportedCurves {
if group == curveID {
@@ -362,6 +365,10 @@
keyExchange: publicKey,
})
+ if c.config.Bugs.SecondClientHelloMissingKeyShare {
+ hello.hasKeyShares = false
+ }
+
hello.hasEarlyData = false
hello.earlyDataContext = nil
hello.raw = nil
@@ -553,7 +560,7 @@
// Resolve ECDHE and compute the handshake secret.
var ecdheSecret []byte
- if hs.suite.flags&suiteECDHE != 0 && !c.config.Bugs.MissingKeyShare {
+ if hs.suite.flags&suiteECDHE != 0 && !c.config.Bugs.MissingKeyShare && !c.config.Bugs.SecondClientHelloMissingKeyShare {
if !hs.serverHello.hasKeyShare {
c.sendAlert(alertMissingExtension)
return errors.New("tls: server omitted the key share extension")
@@ -564,6 +571,7 @@
c.sendAlert(alertHandshakeFailure)
return errors.New("tls: server selected an unsupported group")
}
+ c.curveID = hs.serverHello.keyShare.group
var err error
ecdheSecret, err = curve.finish(hs.serverHello.keyShare.keyExchange)
@@ -821,6 +829,9 @@
c.sendAlert(alertUnexpectedMessage)
return err
}
+ if ecdhe, ok := keyAgreement.(*ecdheKeyAgreement); ok {
+ c.curveID = ecdhe.curveID
+ }
c.peerSignatureAlgorithm = keyAgreement.peerSignatureAlgorithm()
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index 300ab50..f8b5dee 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -266,6 +266,10 @@
vers: c.vers,
}
+ if config.Bugs.SendServerHelloVersion != 0 {
+ hs.hello.vers = config.Bugs.SendServerHelloVersion
+ }
+
hs.hello.random = make([]byte, 32)
if _, err := io.ReadFull(config.rand(), hs.hello.random); err != nil {
c.sendAlert(alertInternalError)
@@ -352,13 +356,25 @@
}
}
- if selectedKeyShare == nil {
+ sendHelloRetryRequest := selectedKeyShare == nil
+ if config.Bugs.UnnecessaryHelloRetryRequest {
+ sendHelloRetryRequest = true
+ }
+ if config.Bugs.SkipHelloRetryRequest {
+ sendHelloRetryRequest = false
+ }
+ if sendHelloRetryRequest {
+ firstTime := true
+ ResendHelloRetryRequest:
// Send HelloRetryRequest.
helloRetryRequestMsg := helloRetryRequestMsg{
vers: c.vers,
cipherSuite: hs.hello.cipherSuite,
selectedGroup: selectedCurve,
}
+ if config.Bugs.SendHelloRetryRequestCurve != 0 {
+ helloRetryRequestMsg.selectedGroup = config.Bugs.SendHelloRetryRequestCurve
+ }
hs.writeServerHash(helloRetryRequestMsg.marshal())
c.writeRecord(recordTypeHandshake, helloRetryRequestMsg.marshal())
@@ -392,26 +408,47 @@
return errors.New("tls: new ClientHello does not match")
}
+ if firstTime && config.Bugs.SecondHelloRetryRequest {
+ firstTime = false
+ goto ResendHelloRetryRequest
+ }
+
selectedKeyShare = &newKeyShares[len(newKeyShares)-1]
}
// Once a curve has been selected and a key share identified,
// the server needs to generate a public value and send it in
// the ServerHello.
- curve, ok := curveForCurveID(selectedKeyShare.group)
+ curve, ok := curveForCurveID(selectedCurve)
if !ok {
panic("tls: server failed to look up curve ID")
}
+ c.curveID = selectedCurve
+
+ var peerKey []byte
+ if config.Bugs.SkipHelloRetryRequest {
+ // If skipping HelloRetryRequest, use a random key to
+ // avoid crashing.
+ curve2, _ := curveForCurveID(selectedCurve)
+ var err error
+ peerKey, err = curve2.offer(config.rand())
+ if err != nil {
+ return err
+ }
+ } else {
+ peerKey = selectedKeyShare.keyExchange
+ }
+
var publicKey []byte
var err error
- publicKey, ecdheSecret, err = curve.accept(config.rand(), selectedKeyShare.keyExchange)
+ publicKey, ecdheSecret, err = curve.accept(config.rand(), peerKey)
if err != nil {
c.sendAlert(alertHandshakeFailure)
return err
}
hs.hello.hasKeyShare = true
- curveID := selectedKeyShare.group
+ curveID := selectedCurve
if c.config.Bugs.SendCurve != 0 {
curveID = config.Bugs.SendCurve
}
@@ -648,6 +685,10 @@
compressionMethod: compressionNone,
}
+ if config.Bugs.SendServerHelloVersion != 0 {
+ hs.hello.vers = config.Bugs.SendServerHelloVersion
+ }
+
hs.hello.random = make([]byte, 32)
_, err = io.ReadFull(config.rand(), hs.hello.random)
if err != nil {
@@ -1009,6 +1050,9 @@
c.sendAlert(alertHandshakeFailure)
return err
}
+ if ecdhe, ok := keyAgreement.(*ecdheKeyAgreement); ok {
+ c.curveID = ecdhe.curveID
+ }
if skx != nil && !config.Bugs.SkipServerKeyExchange {
hs.writeServerHash(skx.marshal())
c.writeRecord(recordTypeHandshake, skx.marshal())
diff --git a/ssl/test/runner/key_agreement.go b/ssl/test/runner/key_agreement.go
index e4a349f..ebfb93d 100644
--- a/ssl/test/runner/key_agreement.go
+++ b/ssl/test/runner/key_agreement.go
@@ -484,13 +484,14 @@
return verifyMessage(ka.version, cert.PublicKey, config, sigAlg, msg, sig)
}
-// ecdheRSAKeyAgreement implements a TLS key agreement where the server
+// ecdheKeyAgreement implements a TLS key agreement where the server
// generates a ephemeral EC public/private key pair and signs it. The
// pre-master secret is then calculated using ECDH. The signature may
// either be ECDSA or RSA.
type ecdheKeyAgreement struct {
auth keyAgreementAuthentication
curve ecdhCurve
+ curveID CurveID
peerKey []byte
}
@@ -516,6 +517,7 @@
if ka.curve, ok = curveForCurveID(curveid); !ok {
return nil, errors.New("tls: preferredCurves includes unsupported curve")
}
+ ka.curveID = curveid
publicKey, err := ka.curve.offer(config.rand())
if err != nil {
@@ -554,6 +556,7 @@
return errors.New("tls: server selected unsupported curve")
}
curveid := CurveID(skx.key[1])<<8 | CurveID(skx.key[2])
+ ka.curveID = curveid
var ok bool
if ka.curve, ok = curveForCurveID(curveid); !ok {
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 92a2b6a..4997836 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -254,6 +254,9 @@
// expectedPeerSignatureAlgorithm, if not zero, is the signature
// algorithm that the peer should have used in the handshake.
expectedPeerSignatureAlgorithm signatureAlgorithm
+ // expectedCurveID, if not zero, is the curve that the handshake should
+ // have used.
+ expectedCurveID CurveID
// messageLen is the length, in bytes, of the test message that will be
// sent.
messageLen int
@@ -518,6 +521,10 @@
return fmt.Errorf("expected peer to use signature algorithm %04x, but got %04x", expected, connState.PeerSignatureAlgorithm)
}
+ if expected := test.expectedCurveID; expected != 0 && expected != connState.CurveID {
+ return fmt.Errorf("expected peer to use curve %04x, but got %04x", expected, connState.CurveID)
+ }
+
if test.exportKeyingMaterial > 0 {
actual := make([]byte, test.exportKeyingMaterial)
if _, err := io.ReadFull(tlsConn, actual); err != nil {
@@ -6304,6 +6311,8 @@
{"X25519", CurveX25519},
}
+const bogusCurve = 0x1234
+
func addCurveTests() {
for _, curve := range testCurves {
testCases = append(testCases, testCase{
@@ -6313,7 +6322,8 @@
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
CurvePreferences: []CurveID{curve.id},
},
- flags: []string{"-enable-all-curves"},
+ flags: []string{"-enable-all-curves"},
+ expectedCurveID: curve.id,
})
testCases = append(testCases, testCase{
name: "CurveTest-Client-" + curve.name + "-TLS13",
@@ -6322,7 +6332,8 @@
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
CurvePreferences: []CurveID{curve.id},
},
- flags: []string{"-enable-all-curves"},
+ flags: []string{"-enable-all-curves"},
+ expectedCurveID: curve.id,
})
testCases = append(testCases, testCase{
testType: serverTest,
@@ -6332,7 +6343,8 @@
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
CurvePreferences: []CurveID{curve.id},
},
- flags: []string{"-enable-all-curves"},
+ flags: []string{"-enable-all-curves"},
+ expectedCurveID: curve.id,
})
testCases = append(testCases, testCase{
testType: serverTest,
@@ -6342,12 +6354,12 @@
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
CurvePreferences: []CurveID{curve.id},
},
- flags: []string{"-enable-all-curves"},
+ flags: []string{"-enable-all-curves"},
+ expectedCurveID: curve.id,
})
}
// The server must be tolerant to bogus curves.
- const bogusCurve = 0x1234
testCases = append(testCases, testCase{
testType: serverTest,
name: "UnknownCurve",
@@ -7359,7 +7371,8 @@
})
testCases = append(testCases, testCase{
- name: "MissingKeyShare-Server",
+ testType: serverTest,
+ name: "MissingKeyShare-Server",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
@@ -7432,6 +7445,158 @@
shouldFail: true,
expectedLocalError: "remote error: unsupported extension",
})
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SendHelloRetryRequest",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // Require a HelloRetryRequest for every curve.
+ DefaultCurves: []CurveID{},
+ },
+ expectedCurveID: CurveX25519,
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SendHelloRetryRequest-2",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ DefaultCurves: []CurveID{CurveP384},
+ },
+ // Although the ClientHello did not predict our preferred curve,
+ // we always select it whether it is predicted or not.
+ expectedCurveID: CurveX25519,
+ })
+
+ testCases = append(testCases, testCase{
+ name: "UnknownCurve-HelloRetryRequest",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // P-384 requires HelloRetryRequest in BoringSSL.
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ SendHelloRetryRequestCurve: bogusCurve,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CURVE:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "DisabledCurve-HelloRetryRequest",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ CurvePreferences: []CurveID{CurveP256},
+ Bugs: ProtocolBugs{
+ IgnorePeerCurvePreferences: true,
+ },
+ },
+ flags: []string{"-p384-only"},
+ shouldFail: true,
+ expectedError: ":WRONG_CURVE:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "UnnecessaryHelloRetryRequest",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ Bugs: ProtocolBugs{
+ UnnecessaryHelloRetryRequest: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CURVE:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "SecondHelloRetryRequest",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // P-384 requires HelloRetryRequest in BoringSSL.
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ SecondHelloRetryRequest: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_MESSAGE:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SecondClientHelloMissingKeyShare",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ DefaultCurves: []CurveID{},
+ Bugs: ProtocolBugs{
+ SecondClientHelloMissingKeyShare: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":MISSING_KEY_SHARE:",
+ })
+
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "SecondClientHelloWrongCurve",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ DefaultCurves: []CurveID{},
+ Bugs: ProtocolBugs{
+ MisinterpretHelloRetryRequestCurve: CurveP521,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CURVE:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "HelloRetryRequestVersionMismatch",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // P-384 requires HelloRetryRequest in BoringSSL.
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ SendServerHelloVersion: 0x0305,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_VERSION_NUMBER:",
+ })
+
+ testCases = append(testCases, testCase{
+ name: "HelloRetryRequestCurveMismatch",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // P-384 requires HelloRetryRequest in BoringSSL.
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ // Send P-384 (correct) in the HelloRetryRequest.
+ SendHelloRetryRequestCurve: CurveP384,
+ // But send P-256 in the ServerHello.
+ SendCurve: CurveP256,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CURVE:",
+ })
+
+ // Test the server selecting a curve that requires a HelloRetryRequest
+ // without sending it.
+ testCases = append(testCases, testCase{
+ name: "SkipHelloRetryRequest",
+ config: Config{
+ MaxVersion: VersionTLS13,
+ // P-384 requires HelloRetryRequest in BoringSSL.
+ CurvePreferences: []CurveID{CurveP384},
+ Bugs: ProtocolBugs{
+ SkipHelloRetryRequest: true,
+ },
+ },
+ shouldFail: true,
+ expectedError: ":WRONG_CURVE:",
+ })
}
func worker(statusChan chan statusMsg, c chan *testCase, shimPath string, wg *sync.WaitGroup) {