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/handshake_client.c b/ssl/handshake_client.c
index 8afc289..52ff212 100644
--- a/ssl/handshake_client.c
+++ b/ssl/handshake_client.c
@@ -548,9 +548,8 @@
return ret;
}
-static int ssl3_write_client_cipher_list(SSL *ssl, CBB *out,
- uint16_t min_version,
- uint16_t max_version) {
+int ssl_write_client_cipher_list(SSL *ssl, CBB *out, uint16_t min_version,
+ uint16_t max_version) {
/* Prepare disabled cipher masks. */
ssl_set_client_disabled(ssl);
@@ -605,6 +604,45 @@
return CBB_flush(out);
}
+int ssl_add_client_hello_body(SSL *ssl, CBB *body) {
+ uint16_t min_version, max_version;
+ if (!ssl_get_version_range(ssl, &min_version, &max_version)) {
+ return 0;
+ }
+
+ /* Renegotiations do not participate in session resumption. */
+ int has_session = ssl->session != NULL &&
+ !ssl->s3->initial_handshake_complete;
+
+ CBB child;
+ if (!CBB_add_u16(body, ssl->client_version) ||
+ !CBB_add_bytes(body, ssl->s3->client_random, SSL3_RANDOM_SIZE) ||
+ !CBB_add_u8_length_prefixed(body, &child) ||
+ (has_session &&
+ !CBB_add_bytes(&child, ssl->session->session_id,
+ ssl->session->session_id_length))) {
+ return 0;
+ }
+
+ if (SSL_IS_DTLS(ssl)) {
+ if (!CBB_add_u8_length_prefixed(body, &child) ||
+ !CBB_add_bytes(&child, ssl->d1->cookie, ssl->d1->cookie_len)) {
+ return 0;
+ }
+ }
+
+ size_t header_len =
+ SSL_IS_DTLS(ssl) ? DTLS1_HM_HEADER_LENGTH : SSL3_HM_HEADER_LENGTH;
+ if (!ssl_write_client_cipher_list(ssl, body, min_version, max_version) ||
+ !CBB_add_u8(body, 1 /* one compression method */) ||
+ !CBB_add_u8(body, 0 /* null compression */) ||
+ !ssl_add_clienthello_tlsext(ssl, body, header_len + CBB_len(body))) {
+ return 0;
+ }
+
+ return 1;
+}
+
static int ssl3_send_client_hello(SSL *ssl) {
if (ssl->state == SSL3_ST_CW_CLNT_HELLO_B) {
return ssl->method->write_message(ssl);
@@ -654,34 +692,9 @@
goto err;
}
- /* Renegotiations do not participate in session resumption. */
- int has_session = ssl->session != NULL &&
- !ssl->s3->initial_handshake_complete;
-
- CBB body, child;
+ CBB body;
if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_CLIENT_HELLO) ||
- !CBB_add_u16(&body, ssl->client_version) ||
- !CBB_add_bytes(&body, ssl->s3->client_random, SSL3_RANDOM_SIZE) ||
- !CBB_add_u8_length_prefixed(&body, &child) ||
- (has_session &&
- !CBB_add_bytes(&child, ssl->session->session_id,
- ssl->session->session_id_length))) {
- goto err;
- }
-
- if (SSL_IS_DTLS(ssl)) {
- if (!CBB_add_u8_length_prefixed(&body, &child) ||
- !CBB_add_bytes(&child, ssl->d1->cookie, ssl->d1->cookie_len)) {
- goto err;
- }
- }
-
- size_t header_len =
- SSL_IS_DTLS(ssl) ? DTLS1_HM_HEADER_LENGTH : SSL3_HM_HEADER_LENGTH;
- if (!ssl3_write_client_cipher_list(ssl, &body, min_version, max_version) ||
- !CBB_add_u8(&body, 1 /* one compression method */) ||
- !CBB_add_u8(&body, 0 /* null compression */) ||
- !ssl_add_clienthello_tlsext(ssl, &body, header_len + CBB_len(&body)) ||
+ !ssl_add_client_hello_body(ssl, &body) ||
!ssl->method->finish_message(ssl, &cbb)) {
goto err;
}
diff --git a/ssl/internal.h b/ssl/internal.h
index eb8b879..0401791 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -873,6 +873,11 @@
SSL_ECDH_CTX *groups;
size_t groups_len;
+ /* retry_group is the group ID selected by the server in HelloRetryRequest. */
+ uint16_t retry_group;
+ /* key_share_bytes is the value of the previously sent KeyShare extension. */
+ uint8_t *key_share_bytes;
+ size_t key_share_bytes_len;
uint8_t *public_key;
size_t public_key_len;
@@ -882,6 +887,8 @@
SSL_HANDSHAKE *ssl_handshake_new(enum ssl_hs_wait_t (*do_handshake)(SSL *ssl));
+void ssl_handshake_clear_groups(SSL_HANDSHAKE *hs);
+
/* ssl_handshake_free releases all memory associated with |hs|. */
void ssl_handshake_free(SSL_HANDSHAKE *hs);
@@ -910,11 +917,14 @@
int ext_key_share_parse_serverhello(SSL *ssl, uint8_t **out_secret,
size_t *out_secret_len, uint8_t *out_alert,
CBS *contents);
-int ext_key_share_parse_clienthello(SSL *ssl, uint8_t **out_secret,
+int ext_key_share_parse_clienthello(SSL *ssl,
+ int *out_found, uint8_t **out_secret,
size_t *out_secret_len, uint8_t *out_alert,
CBS *contents);
int ext_key_share_add_serverhello(SSL *ssl, CBB *out);
+int ssl_add_client_hello_body(SSL *ssl, CBB *body);
+
/* SSLKEYLOGFILE functions. */
@@ -1228,6 +1238,9 @@
STACK_OF(SSL_CIPHER) *ssl_get_ciphers_by_id(SSL *ssl);
int ssl_verify_alarm_type(long type);
+int ssl_write_client_cipher_list(SSL *ssl, CBB *out, uint16_t min_version,
+ uint16_t max_version);
+
int ssl3_get_finished(SSL *ssl);
int ssl3_send_change_cipher_spec(SSL *ssl);
void ssl3_cleanup_key_block(SSL *ssl);
@@ -1331,6 +1344,13 @@
char ssl_early_callback_init(struct ssl_early_callback_ctx *ctx);
+/* tls1_get_grouplist sets |*out_group_ids| and |*out_group_ids_len| to the
+ * list of allowed group IDs. If |get_peer_groups| is non-zero, return the
+ * peer's group list. Otherwise, return the preferred list. */
+void tls1_get_grouplist(SSL *ssl, int get_peer_groups,
+ const uint16_t **out_group_ids,
+ size_t *out_group_ids_len);
+
/* tls1_check_group_id returns one if |group_id| is consistent with both our
* and the peer's group preferences. Note: if called as the client, only our
* preferences are checked; the peer (the server) does not send preferences. */
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index f76d9f0..7549240 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -300,12 +300,9 @@
#endif
};
-/* tls1_get_grouplist sets |*out_group_ids| and |*out_group_ids_len| to the
- * list of allowed group IDs. If |get_peer_groups| is non-zero, return the
- * peer's group list. Otherwise, return the preferred list. */
-static void tls1_get_grouplist(SSL *ssl, int get_peer_groups,
- const uint16_t **out_group_ids,
- size_t *out_group_ids_len) {
+void tls1_get_grouplist(SSL *ssl, int get_peer_groups,
+ const uint16_t **out_group_ids,
+ size_t *out_group_ids_len) {
if (get_peer_groups) {
/* Only clients send a supported group list, so this function is only
* called on the server. */
@@ -1973,7 +1970,24 @@
const uint16_t *groups;
size_t groups_len;
- tls1_get_grouplist(ssl, 0 /* local groups */, &groups, &groups_len);
+ if (ssl->s3->hs->retry_group) {
+ /* Append the new key share to the old list. */
+ if (!CBB_add_bytes(&kse_bytes, ssl->s3->hs->key_share_bytes,
+ ssl->s3->hs->key_share_bytes_len)) {
+ return 0;
+ }
+ OPENSSL_free(ssl->s3->hs->key_share_bytes);
+ ssl->s3->hs->key_share_bytes = NULL;
+
+ groups = &ssl->s3->hs->retry_group;
+ groups_len = 1;
+ } else {
+ tls1_get_grouplist(ssl, 0 /* local groups */, &groups, &groups_len);
+ /* Only send the top two preferred key shares. */
+ if (groups_len > 2) {
+ groups_len = 2;
+ }
+ }
ssl->s3->hs->groups = OPENSSL_malloc(groups_len * sizeof(SSL_ECDH_CTX));
if (ssl->s3->hs->groups == NULL) {
@@ -1996,6 +2010,17 @@
}
}
+ if (!ssl->s3->hs->retry_group) {
+ /* Save the contents of the extension to repeat it in the second
+ * ClientHello. */
+ ssl->s3->hs->key_share_bytes_len = CBB_len(&kse_bytes);
+ ssl->s3->hs->key_share_bytes = BUF_memdup(CBB_data(&kse_bytes),
+ CBB_len(&kse_bytes));
+ if (ssl->s3->hs->key_share_bytes == NULL) {
+ return 0;
+ }
+ }
+
return CBB_flush(out);
}
@@ -2030,16 +2055,12 @@
return 0;
}
- for (size_t i = 0; i < ssl->s3->hs->groups_len; i++) {
- SSL_ECDH_CTX_cleanup(&ssl->s3->hs->groups[i]);
- }
- OPENSSL_free(ssl->s3->hs->groups);
- ssl->s3->hs->groups = NULL;
-
+ ssl_handshake_clear_groups(ssl->s3->hs);
return 1;
}
-int ext_key_share_parse_clienthello(SSL *ssl, uint8_t **out_secret,
+int ext_key_share_parse_clienthello(SSL *ssl, int *out_found,
+ uint8_t **out_secret,
size_t *out_secret_len, uint8_t *out_alert,
CBS *contents) {
uint16_t group_id;
@@ -2049,7 +2070,7 @@
return 0;
}
- int found = 0;
+ *out_found = 0;
while (CBS_len(&key_shares) > 0) {
uint16_t id;
CBS peer_key;
@@ -2058,7 +2079,7 @@
return 0;
}
- if (id != group_id || found) {
+ if (id != group_id || *out_found) {
continue;
}
@@ -2078,10 +2099,10 @@
}
SSL_ECDH_CTX_cleanup(&group);
- found = 1;
+ *out_found = 1;
}
- return found;
+ return 1;
}
int ext_key_share_add_serverhello(SSL *ssl, CBB *out) {
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) {
diff --git a/ssl/tls13_both.c b/ssl/tls13_both.c
index 023dc0d..3cebdd3 100644
--- a/ssl/tls13_both.c
+++ b/ssl/tls13_both.c
@@ -40,6 +40,19 @@
return hs;
}
+void ssl_handshake_clear_groups(SSL_HANDSHAKE *hs) {
+ if (hs->groups == NULL) {
+ return;
+ }
+
+ for (size_t i = 0; i < hs->groups_len; i++) {
+ SSL_ECDH_CTX_cleanup(&hs->groups[i]);
+ }
+ OPENSSL_free(hs->groups);
+ hs->groups = NULL;
+ hs->groups_len = 0;
+}
+
void ssl_handshake_free(SSL_HANDSHAKE *hs) {
if (hs == NULL) {
return;
@@ -47,12 +60,8 @@
OPENSSL_cleanse(hs->secret, sizeof(hs->secret));
OPENSSL_cleanse(hs->traffic_secret_0, sizeof(hs->traffic_secret_0));
- if (hs->groups != NULL) {
- for (size_t i = 0; i < hs->groups_len; i++) {
- SSL_ECDH_CTX_cleanup(&hs->groups[i]);
- }
- OPENSSL_free(hs->groups);
- }
+ ssl_handshake_clear_groups(hs);
+ OPENSSL_free(hs->key_share_bytes);
OPENSSL_free(hs->public_key);
OPENSSL_free(hs->cert_context);
OPENSSL_free(hs);
diff --git a/ssl/tls13_client.c b/ssl/tls13_client.c
index 1b5573b..ead5b82 100644
--- a/ssl/tls13_client.c
+++ b/ssl/tls13_client.c
@@ -28,7 +28,10 @@
enum client_hs_state_t {
- state_process_server_hello = 0,
+ state_process_hello_retry_request = 0,
+ state_send_second_client_hello,
+ state_flush_second_client_hello,
+ state_process_server_hello,
state_process_encrypted_extensions,
state_process_certificate_request,
state_process_server_certificate,
@@ -43,6 +46,85 @@
state_done,
};
+static enum ssl_hs_wait_t do_process_hello_retry_request(SSL *ssl,
+ SSL_HANDSHAKE *hs) {
+ if (ssl->s3->tmp.message_type != SSL3_MT_HELLO_RETRY_REQUEST) {
+ hs->state = state_process_server_hello;
+ return ssl_hs_ok;
+ }
+
+ CBS cbs, extensions;
+ uint16_t server_wire_version, cipher_suite, group_id;
+ CBS_init(&cbs, ssl->init_msg, ssl->init_num);
+ if (!CBS_get_u16(&cbs, &server_wire_version) ||
+ !CBS_get_u16(&cbs, &cipher_suite) ||
+ !CBS_get_u16(&cbs, &group_id) ||
+ /* We do not currently parse any HelloRetryRequest extensions. */
+ !CBS_get_u16_length_prefixed(&cbs, &extensions) ||
+ CBS_len(&cbs) != 0) {
+ ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+ return ssl_hs_error;
+ }
+
+ /* TODO(svaldez): Don't do early_data on HelloRetryRequest. */
+
+ const uint16_t *groups;
+ size_t groups_len;
+ tls1_get_grouplist(ssl, 0 /* local groups */, &groups, &groups_len);
+ int found = 0;
+ for (size_t i = 0; i < groups_len; i++) {
+ if (groups[i] == group_id) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found) {
+ ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
+ OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CURVE);
+ return ssl_hs_error;
+ }
+
+ for (size_t i = 0; i < ssl->s3->hs->groups_len; i++) {
+ /* Check that the HelloRetryRequest does not request a key share that was
+ * provided in the initial ClientHello.
+ *
+ * TODO(svaldez): Don't enforce this check when the HelloRetryRequest is due
+ * to a cookie. */
+ if (SSL_ECDH_CTX_get_id(&ssl->s3->hs->groups[i]) == group_id) {
+ ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
+ OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CURVE);
+ return ssl_hs_error;
+ }
+ }
+
+ ssl_handshake_clear_groups(ssl->s3->hs);
+ ssl->s3->hs->retry_group = group_id;
+
+ hs->state = state_send_second_client_hello;
+ return ssl_hs_ok;
+}
+
+static enum ssl_hs_wait_t do_send_second_client_hello(SSL *ssl,
+ SSL_HANDSHAKE *hs) {
+ CBB cbb, body;
+ if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_CLIENT_HELLO) ||
+ !ssl_add_client_hello_body(ssl, &body) ||
+ !ssl->method->finish_message(ssl, &cbb)) {
+ CBB_cleanup(&cbb);
+ return ssl_hs_error;
+ }
+
+ hs->state = state_flush_second_client_hello;
+ return ssl_hs_write_message;
+}
+
+static enum ssl_hs_wait_t do_flush_second_client_hello(SSL *ssl,
+ SSL_HANDSHAKE *hs) {
+ hs->state = state_process_server_hello;
+ return ssl_hs_flush_and_read_message;
+}
+
static enum ssl_hs_wait_t do_process_server_hello(SSL *ssl, SSL_HANDSHAKE *hs) {
if (!tls13_check_message_type(ssl, SSL3_MT_SERVER_HELLO)) {
return ssl_hs_error;
@@ -58,6 +140,13 @@
!CBS_get_u16_length_prefixed(&cbs, &extensions) ||
CBS_len(&cbs) != 0) {
ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+ OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+ return ssl_hs_error;
+ }
+
+ if (server_wire_version != ssl->version) {
+ ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+ OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_VERSION_NUMBER);
return ssl_hs_error;
}
@@ -169,6 +258,12 @@
if (!tls13_advance_key_schedule(ssl, kZeroes, hash_len)) {
return ssl_hs_error;
}
+ }
+
+ /* If there was no HelloRetryRequest, the version negotiation logic has
+ * already hashed the message. */
+ if (ssl->s3->hs->retry_group != 0 &&
+ !ssl->method->hash_current_message(ssl)) {
return ssl_hs_error;
}
@@ -403,6 +498,15 @@
enum ssl_hs_wait_t ret = ssl_hs_error;
enum client_hs_state_t state = hs->state;
switch (state) {
+ case state_process_hello_retry_request:
+ ret = do_process_hello_retry_request(ssl, hs);
+ break;
+ case state_send_second_client_hello:
+ ret = do_send_second_client_hello(ssl, hs);
+ break;
+ case state_flush_second_client_hello:
+ ret = do_flush_second_client_hello(ssl, hs);
+ break;
case state_process_server_hello:
ret = do_process_server_hello(ssl, hs);
break;
diff --git a/ssl/tls13_server.c b/ssl/tls13_server.c
index 1a1e52a..22392f0 100644
--- a/ssl/tls13_server.c
+++ b/ssl/tls13_server.c
@@ -29,6 +29,9 @@
enum server_hs_state_t {
state_process_client_hello = 0,
+ state_send_hello_retry_request,
+ state_flush_hello_retry_request,
+ state_process_second_client_hello,
state_send_server_hello,
state_send_encrypted_extensions,
state_send_certificate_request,
@@ -43,6 +46,60 @@
state_done,
};
+static const uint8_t kZeroes[EVP_MAX_MD_SIZE] = {0};
+
+static int resolve_psk_secret(SSL *ssl) {
+ SSL_HANDSHAKE *hs = ssl->s3->hs;
+
+ if (ssl->s3->tmp.new_cipher->algorithm_auth != SSL_aPSK) {
+ return tls13_advance_key_schedule(ssl, kZeroes, hs->hash_len);
+ }
+
+ /* TODO(davidben): Support PSK. */
+ OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+ return 0;
+}
+
+static int resolve_ecdhe_secret(SSL *ssl, int *out_need_retry,
+ struct ssl_early_callback_ctx *early_ctx) {
+ *out_need_retry = 0;
+ SSL_HANDSHAKE *hs = ssl->s3->hs;
+
+ if (ssl->s3->tmp.new_cipher->algorithm_mkey != SSL_kECDHE) {
+ return tls13_advance_key_schedule(ssl, kZeroes, hs->hash_len);
+ }
+
+ const uint8_t *key_share_buf = NULL;
+ size_t key_share_len = 0;
+ CBS key_share;
+ if (!SSL_early_callback_ctx_extension_get(early_ctx, TLSEXT_TYPE_key_share,
+ &key_share_buf, &key_share_len)) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_MISSING_KEY_SHARE);
+ ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_MISSING_EXTENSION);
+ return ssl_hs_error;
+ }
+
+ CBS_init(&key_share, key_share_buf, key_share_len);
+ int found_key_share;
+ uint8_t *dhe_secret;
+ size_t dhe_secret_len;
+ uint8_t alert;
+ if (!ext_key_share_parse_clienthello(ssl, &found_key_share, &dhe_secret,
+ &dhe_secret_len, &alert, &key_share)) {
+ ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
+ return 0;
+ }
+
+ if (!found_key_share) {
+ *out_need_retry = 1;
+ return 0;
+ }
+
+ int ok = tls13_advance_key_schedule(ssl, dhe_secret, dhe_secret_len);
+ OPENSSL_free(dhe_secret);
+ return ok;
+}
+
static enum ssl_hs_wait_t do_process_client_hello(SSL *ssl, SSL_HANDSHAKE *hs) {
if (!tls13_check_message_type(ssl, SSL3_MT_CLIENT_HELLO)) {
return ssl_hs_error;
@@ -165,7 +222,6 @@
/* The PRF hash is now known. Set up the key schedule and hash the
* ClientHello. */
- static const uint8_t kZeroes[EVP_MAX_MD_SIZE] = {0};
size_t hash_len =
EVP_MD_size(ssl_get_handshake_digest(ssl_get_algorithm_prf(ssl)));
if (!tls13_init_key_schedule(ssl, kZeroes, hash_len)) {
@@ -173,42 +229,79 @@
}
/* Resolve PSK and incorporate it into the secret. */
- if (cipher->algorithm_auth == SSL_aPSK) {
- /* TODO(davidben): Support PSK. */
- OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
- return ssl_hs_error;
- } else if (!tls13_advance_key_schedule(ssl, kZeroes, hash_len)) {
+ if (!resolve_psk_secret(ssl)) {
return ssl_hs_error;
}
/* Resolve ECDHE and incorporate it into the secret. */
- if (cipher->algorithm_mkey == SSL_kECDHE) {
- const uint8_t *key_share_buf = NULL;
- size_t key_share_len = 0;
- CBS key_share;
- if (!SSL_early_callback_ctx_extension_get(&early_ctx, TLSEXT_TYPE_key_share,
- &key_share_buf, &key_share_len)) {
- OPENSSL_PUT_ERROR(SSL, SSL_R_MISSING_KEY_SHARE);
- ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_MISSING_EXTENSION);
- return ssl_hs_error;
+ int need_retry;
+ if (!resolve_ecdhe_secret(ssl, &need_retry, &early_ctx)) {
+ if (need_retry) {
+ hs->state = state_send_hello_retry_request;
+ return ssl_hs_ok;
}
+ return ssl_hs_error;
+ }
- CBS_init(&key_share, key_share_buf, key_share_len);
- uint8_t *dhe_secret;
- size_t dhe_secret_len;
- uint8_t alert;
- if (!ext_key_share_parse_clienthello(ssl, &dhe_secret, &dhe_secret_len,
- &alert, &key_share)) {
- ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
- return ssl_hs_error;
- }
+ hs->state = state_send_server_hello;
+ return ssl_hs_ok;
+}
- int ok = tls13_advance_key_schedule(ssl, dhe_secret, dhe_secret_len);
- OPENSSL_free(dhe_secret);
- if (!ok) {
- return ssl_hs_error;
+static enum ssl_hs_wait_t do_send_hello_retry_request(SSL *ssl,
+ SSL_HANDSHAKE *hs) {
+ CBB cbb, body, extensions;
+ uint16_t group_id;
+ if (!ssl->method->init_message(ssl, &cbb, &body,
+ SSL3_MT_HELLO_RETRY_REQUEST) ||
+ !CBB_add_u16(&body, ssl->version) ||
+ !CBB_add_u16(&body, ssl_cipher_get_value(ssl->s3->tmp.new_cipher)) ||
+ !tls1_get_shared_group(ssl, &group_id) ||
+ !CBB_add_u16(&body, group_id) ||
+ !CBB_add_u16_length_prefixed(&body, &extensions) ||
+ !ssl->method->finish_message(ssl, &cbb)) {
+ CBB_cleanup(&cbb);
+ return ssl_hs_error;
+ }
+
+ hs->state = state_flush_hello_retry_request;
+ return ssl_hs_write_message;
+}
+
+static enum ssl_hs_wait_t do_flush_hello_retry_request(SSL *ssl,
+ SSL_HANDSHAKE *hs) {
+ hs->state = state_process_second_client_hello;
+ return ssl_hs_flush_and_read_message;
+}
+
+static enum ssl_hs_wait_t do_process_second_client_hello(SSL *ssl,
+ SSL_HANDSHAKE *hs) {
+ if (!tls13_check_message_type(ssl, SSL3_MT_CLIENT_HELLO)) {
+ return ssl_hs_error;
+ }
+
+ struct ssl_early_callback_ctx early_ctx;
+
+ memset(&early_ctx, 0, sizeof(early_ctx));
+ early_ctx.ssl = ssl;
+ early_ctx.client_hello = ssl->init_msg;
+ early_ctx.client_hello_len = ssl->init_num;
+ if (!ssl_early_callback_init(&early_ctx)) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_CLIENTHELLO_PARSE_FAILED);
+ ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+ return ssl_hs_error;
+ }
+
+ int need_retry;
+ if (!resolve_ecdhe_secret(ssl, &need_retry, &early_ctx)) {
+ if (need_retry) {
+ /* Only send one HelloRetryRequest. */
+ ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
+ OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CURVE);
}
- } else if (!tls13_advance_key_schedule(ssl, kZeroes, hash_len)) {
+ return ssl_hs_error;
+ }
+
+ if (!ssl->method->hash_current_message(ssl)) {
return ssl_hs_error;
}
@@ -352,7 +445,6 @@
static enum ssl_hs_wait_t do_flush(SSL *ssl, SSL_HANDSHAKE *hs) {
/* Update the secret to the master secret and derive traffic keys. */
- static const uint8_t kZeroes[EVP_MAX_MD_SIZE] = {0};
if (!tls13_advance_key_schedule(ssl, kZeroes, hs->hash_len) ||
!tls13_derive_traffic_secret_0(ssl) ||
!tls13_set_traffic_key(ssl, type_data, evp_aead_seal,
@@ -426,6 +518,15 @@
case state_process_client_hello:
ret = do_process_client_hello(ssl, hs);
break;
+ case state_send_hello_retry_request:
+ ret = do_send_hello_retry_request(ssl, hs);
+ break;
+ case state_flush_hello_retry_request:
+ ret = do_flush_hello_retry_request(ssl, hs);
+ break;
+ case state_process_second_client_hello:
+ ret = do_process_second_client_hello(ssl, hs);
+ break;
case state_send_server_hello:
ret = do_send_server_hello(ssl, hs);
break;