Implement Token Binding
Update-Note: Token Binding can no longer be configured with the custom
extensions API. Instead, use the new built-in implementation. (The
internal repository should be all set.)
Bug: 183
Change-Id: I007523a638dc99582ebd1d177c38619fa7e1ac38
Reviewed-on: https://boringssl-review.googlesource.com/20645
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/crypto/err/ssl.errordata b/crypto/err/ssl.errordata
index 58c9115..eadb25d 100644
--- a/crypto/err/ssl.errordata
+++ b/crypto/err/ssl.errordata
@@ -86,6 +86,7 @@
SSL,168,MIXED_SPECIAL_OPERATOR_WITH_GROUPS
SSL,169,MTU_TOO_SMALL
SSL,170,NEGOTIATED_BOTH_NPN_AND_ALPN
+SSL,285,NEGOTIATED_TB_WITHOUT_EMS_OR_RI
SSL,171,NESTED_GROUP
SSL,172,NO_CERTIFICATES_RETURNED
SSL,173,NO_CERTIFICATE_ASSIGNED
diff --git a/include/openssl/base.h b/include/openssl/base.h
index 9edaa5c..dea069a 100644
--- a/include/openssl/base.h
+++ b/include/openssl/base.h
@@ -151,7 +151,7 @@
// A consumer may use this symbol in the preprocessor to temporarily build
// against multiple revisions of BoringSSL at the same time. It is not
// recommended to do so for longer than is necessary.
-#define BORINGSSL_API_VERSION 6
+#define BORINGSSL_API_VERSION 7
#if defined(BORINGSSL_SHARED_LIBRARY)
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index b868d3f..7ae8276 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -2785,6 +2785,33 @@
SSL *ssl, EVP_PKEY **out_pkey);
+// Token Binding.
+//
+// See draft-ietf-tokbind-protocol-16.
+
+// SSL_set_token_binding_params sets |params| as the Token Binding Key
+// parameters (section 3 of draft-ietf-tokbind-protocol-16) to negotiate on the
+// connection. If this function is not called, or if |len| is 0, then this
+// endpoint will not attempt to negotiate Token Binding. |params| are provided
+// in preference order, with the more preferred parameters at the beginning of
+// the list. This function returns 1 on success and 0 on failure.
+OPENSSL_EXPORT int SSL_set_token_binding_params(SSL *ssl, const uint8_t *params,
+ size_t len);
+
+// SSL_is_token_binding_negotiated returns 1 if Token Binding was negotiated
+// on this connection and 0 otherwise. On a server, it is possible for this
+// function to return 1 when the client's view of the connection is that Token
+// Binding was not negotiated. This occurs when the server indicates a version
+// of Token Binding less than the client's minimum version.
+OPENSSL_EXPORT int SSL_is_token_binding_negotiated(const SSL *ssl);
+
+// SSL_get_negotiated_token_binding_param returns the TokenBindingKeyParameters
+// enum value that was negotiated. It is only valid to call this function if
+// SSL_is_token_binding_negotiated returned 1, otherwise this function returns
+// an undefined value.
+OPENSSL_EXPORT uint8_t SSL_get_negotiated_token_binding_param(const SSL *ssl);
+
+
// DTLS-SRTP.
//
// See RFC 5764.
@@ -4588,6 +4615,7 @@
#define SSL_R_EMPTY_HELLO_RETRY_REQUEST 282
#define SSL_R_EARLY_DATA_NOT_IN_USE 283
#define SSL_R_HANDSHAKE_NOT_COMPLETE 284
+#define SSL_R_NEGOTIATED_TB_WITHOUT_EMS_OR_RI 285
#define SSL_R_SSLV3_ALERT_CLOSE_NOTIFY 1000
#define SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE 1010
#define SSL_R_SSLV3_ALERT_BAD_RECORD_MAC 1020
diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h
index 4b5d226..682bb9b 100644
--- a/include/openssl/tls1.h
+++ b/include/openssl/tls1.h
@@ -202,6 +202,9 @@
// ExtensionType value from RFC7627
#define TLSEXT_TYPE_extended_master_secret 23
+// ExtensionType value from draft-ietf-tokbind-negotiation-10
+#define TLSEXT_TYPE_token_binding 24
+
// ExtensionType value from RFC4507
#define TLSEXT_TYPE_session_ticket 35
diff --git a/ssl/handshake_client.cc b/ssl/handshake_client.cc
index 4c7012b..4e2158e 100644
--- a/ssl/handshake_client.cc
+++ b/ssl/handshake_client.cc
@@ -757,6 +757,13 @@
return ssl_hs_error;
}
+ if (ssl->token_binding_negotiated &&
+ (!hs->extended_master_secret || !ssl->s3->send_connection_binding)) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_NEGOTIATED_TB_WITHOUT_EMS_OR_RI);
+ ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION);
+ return ssl_hs_error;
+ }
+
ssl->method->next_message(ssl);
if (ssl->session != NULL) {
diff --git a/ssl/internal.h b/ssl/internal.h
index 91ca1f7..fb99101 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1376,6 +1376,12 @@
// peer_key is the peer's ECDH key for a TLS 1.2 client.
Array<uint8_t> peer_key;
+ // negotiated_token_binding_version is used by a server to store the
+ // on-the-wire encoding of the Token Binding protocol version to advertise in
+ // the ServerHello/EncryptedExtensions if the Token Binding extension is to be
+ // sent.
+ uint16_t negotiated_token_binding_version;
+
// server_params, in a TLS 1.2 server, stores the ServerKeyExchange
// parameters. It has client and server randoms prepended for signing
// convenience.
@@ -2606,6 +2612,14 @@
uint8_t *alpn_client_proto_list;
unsigned alpn_client_proto_list_len;
+ // Contains a list of supported Token Binding key parameters.
+ uint8_t *token_binding_params;
+ size_t token_binding_params_len;
+
+ // The negotiated Token Binding key parameter. Only valid if
+ // |token_binding_negotiated| is set.
+ uint8_t negotiated_token_binding_param;
+
// renegotiate_mode controls how peer renegotiation attempts are handled.
enum ssl_renegotiate_mode_t renegotiate_mode;
@@ -2633,6 +2647,9 @@
// we'll advertise support.
bool tlsext_channel_id_enabled:1;
+ // token_binding_negotiated is set if Token Binding was negotiated.
+ bool token_binding_negotiated:1;
+
// retain_only_sha256_of_client_certs is true if we should compute the SHA256
// hash of the peer's certificate and then discard it to save memory and
// session space. Only effective on the server side.
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index 8f53dcd..75e438d 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -771,6 +771,7 @@
SSL_CTX_free(ssl->session_ctx);
OPENSSL_free(ssl->supported_group_list);
OPENSSL_free(ssl->alpn_client_proto_list);
+ OPENSSL_free(ssl->token_binding_params);
EVP_PKEY_free(ssl->tlsext_channel_id_private);
OPENSSL_free(ssl->psk_identity_hint);
sk_CRYPTO_BUFFER_pop_free(ssl->client_CA, CRYPTO_BUFFER_free);
@@ -2122,6 +2123,28 @@
return 64;
}
+int SSL_set_token_binding_params(SSL *ssl, const uint8_t *params, size_t len) {
+ if (len > 256) {
+ OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
+ return 0;
+ }
+ OPENSSL_free(ssl->token_binding_params);
+ ssl->token_binding_params = (uint8_t *)BUF_memdup(params, len);
+ if (!ssl->token_binding_params) {
+ return 0;
+ }
+ ssl->token_binding_params_len = len;
+ return 1;
+}
+
+int SSL_is_token_binding_negotiated(const SSL *ssl) {
+ return ssl->token_binding_negotiated;
+}
+
+uint8_t SSL_get_negotiated_token_binding_param(const SSL *ssl) {
+ return ssl->negotiated_token_binding_param;
+}
+
size_t SSL_get0_certificate_types(SSL *ssl, const uint8_t **out_types) {
if (ssl->server || ssl->s3->hs == NULL) {
*out_types = NULL;
diff --git a/ssl/t1_lib.cc b/ssl/t1_lib.cc
index a8833e0..dafb885 100644
--- a/ssl/t1_lib.cc
+++ b/ssl/t1_lib.cc
@@ -2439,6 +2439,153 @@
return true;
}
+// Token Binding
+//
+// https://tools.ietf.org/html/draft-ietf-tokbind-negotiation-10
+
+// The Token Binding version number currently matches the draft number of
+// draft-ietf-tokbind-protocol, and when published as an RFC it will be 0x0100.
+// Since there are no wire changes to the protocol from draft 13 through the
+// current draft (16), this implementation supports all versions in that range.
+static uint16_t kTokenBindingMaxVersion = 16;
+static uint16_t kTokenBindingMinVersion = 13;
+
+static bool ext_token_binding_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
+ SSL *const ssl = hs->ssl;
+ if (ssl->token_binding_params == nullptr || SSL_is_dtls(ssl)) {
+ return true;
+ }
+
+ CBB contents, params;
+ if (!CBB_add_u16(out, TLSEXT_TYPE_token_binding) ||
+ !CBB_add_u16_length_prefixed(out, &contents) ||
+ !CBB_add_u16(&contents, kTokenBindingMaxVersion) ||
+ !CBB_add_u8_length_prefixed(&contents, ¶ms) ||
+ !CBB_add_bytes(¶ms, ssl->token_binding_params,
+ ssl->token_binding_params_len) ||
+ !CBB_flush(out)) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool ext_token_binding_parse_serverhello(SSL_HANDSHAKE *hs,
+ uint8_t *out_alert,
+ CBS *contents) {
+ SSL *const ssl = hs->ssl;
+ if (contents == nullptr) {
+ return true;
+ }
+
+ CBS params_list;
+ uint16_t version;
+ uint8_t param;
+ if (!CBS_get_u16(contents, &version) ||
+ !CBS_get_u8_length_prefixed(contents, ¶ms_list) ||
+ !CBS_get_u8(¶ms_list, ¶m) ||
+ CBS_len(¶ms_list) > 0 ||
+ CBS_len(contents) > 0) {
+ *out_alert = SSL_AD_DECODE_ERROR;
+ return false;
+ }
+
+ // The server-negotiated version must be less than or equal to our version.
+ if (version > kTokenBindingMaxVersion) {
+ *out_alert = SSL_AD_ILLEGAL_PARAMETER;
+ return false;
+ }
+
+ // If the server-selected version is less than what we support, then Token
+ // Binding wasn't negotiated (but the extension was parsed successfully).
+ if (version < kTokenBindingMinVersion) {
+ return true;
+ }
+
+ for (size_t i = 0; i < ssl->token_binding_params_len; ++i) {
+ if (param == ssl->token_binding_params[i]) {
+ ssl->negotiated_token_binding_param = param;
+ ssl->token_binding_negotiated = true;
+ return true;
+ }
+ }
+
+ *out_alert = SSL_AD_ILLEGAL_PARAMETER;
+ return false;
+}
+
+// select_tb_param looks for the first token binding param in
+// |ssl->token_binding_params| that is also in |params| and puts it in
+// |ssl->negotiated_token_binding_param|. It returns true if a token binding
+// param is found, and false otherwise.
+static bool select_tb_param(SSL *ssl, Span<const uint8_t> peer_params) {
+ for (size_t i = 0; i < ssl->token_binding_params_len; ++i) {
+ uint8_t tb_param = ssl->token_binding_params[i];
+ for (uint8_t peer_param : peer_params) {
+ if (tb_param == peer_param) {
+ ssl->negotiated_token_binding_param = tb_param;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static bool ext_token_binding_parse_clienthello(SSL_HANDSHAKE *hs,
+ uint8_t *out_alert,
+ CBS *contents) {
+ SSL *const ssl = hs->ssl;
+ if (contents == nullptr || ssl->token_binding_params == nullptr) {
+ return true;
+ }
+
+ CBS params;
+ uint16_t version;
+ if (!CBS_get_u16(contents, &version) ||
+ !CBS_get_u8_length_prefixed(contents, ¶ms) ||
+ CBS_len(¶ms) == 0 ||
+ CBS_len(contents) > 0) {
+ *out_alert = SSL_AD_DECODE_ERROR;
+ return false;
+ }
+
+ // If the client-selected version is less than what we support, then Token
+ // Binding wasn't negotiated (but the extension was parsed successfully).
+ if (version < kTokenBindingMinVersion) {
+ return true;
+ }
+
+ // If the client-selected version is higher than we support, use our max
+ // version. Otherwise, use the client's version.
+ hs->negotiated_token_binding_version =
+ std::min(version, kTokenBindingMaxVersion);
+ if (!select_tb_param(ssl, params)) {
+ return true;
+ }
+
+ ssl->token_binding_negotiated = true;
+ return true;
+}
+
+static bool ext_token_binding_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
+ SSL *const ssl = hs->ssl;
+
+ if (!ssl->token_binding_negotiated) {
+ return true;
+ }
+
+ CBB contents, params;
+ if (!CBB_add_u16(out, TLSEXT_TYPE_token_binding) ||
+ !CBB_add_u16_length_prefixed(out, &contents) ||
+ !CBB_add_u16(&contents, hs->negotiated_token_binding_version) ||
+ !CBB_add_u8_length_prefixed(&contents, ¶ms) ||
+ !CBB_add_u8(¶ms, ssl->negotiated_token_binding_param) ||
+ !CBB_flush(out)) {
+ return false;
+ }
+
+ return true;
+}
// kExtensions contains all the supported extensions.
static const struct tls_extension kExtensions[] = {
@@ -2608,6 +2755,14 @@
ext_supported_groups_parse_clienthello,
dont_add_serverhello,
},
+ {
+ TLSEXT_TYPE_token_binding,
+ NULL,
+ ext_token_binding_add_clienthello,
+ ext_token_binding_parse_serverhello,
+ ext_token_binding_parse_clienthello,
+ ext_token_binding_add_serverhello,
+ },
};
#define kNumExtensions (sizeof(kExtensions) / sizeof(struct tls_extension))
@@ -2970,6 +3125,15 @@
static int ssl_check_clienthello_tlsext(SSL_HANDSHAKE *hs) {
SSL *const ssl = hs->ssl;
+
+ if (ssl->token_binding_negotiated &&
+ !(SSL_get_secure_renegotiation_support(ssl) &&
+ SSL_get_extms_support(ssl))) {
+ OPENSSL_PUT_ERROR(SSL, SSL_R_NEGOTIATED_TB_WITHOUT_EMS_OR_RI);
+ ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION);
+ return -1;
+ }
+
int ret = SSL_TLSEXT_ERR_NOACK;
int al = SSL_AD_UNRECOGNIZED_NAME;
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 0123df7..50182b1 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -1725,6 +1725,18 @@
}
}
+ if (config->expected_token_binding_param != -1) {
+ if (!SSL_is_token_binding_negotiated(ssl)) {
+ fprintf(stderr, "no Token Binding negotiated\n");
+ return false;
+ }
+ if (SSL_get_negotiated_token_binding_param(ssl) !=
+ static_cast<uint8_t>(config->expected_token_binding_param)) {
+ fprintf(stderr, "Token Binding param mismatch\n");
+ return false;
+ }
+ }
+
if (config->expect_extended_master_secret && !SSL_get_extms_support(ssl)) {
fprintf(stderr, "No EMS for connection when expected\n");
return false;
@@ -1970,6 +1982,12 @@
}
}
}
+ if (!config->send_token_binding_params.empty()) {
+ SSL_set_token_binding_params(ssl.get(),
+ reinterpret_cast<const uint8_t *>(
+ config->send_token_binding_params.data()),
+ config->send_token_binding_params.length());
+ }
if (!config->host_name.empty() &&
!SSL_set_tlsext_host_name(ssl.get(), config->host_name.c_str())) {
return false;
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 15e42ff..a543e81 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -122,6 +122,7 @@
extensionSignedCertificateTimestamp uint16 = 18
extensionPadding uint16 = 21
extensionExtendedMasterSecret uint16 = 23
+ extensionTokenBinding uint16 = 24
extensionSessionTicket uint16 = 35
extensionOldKeyShare uint16 = 40 // draft-ietf-tls-tls13-16
extensionPreSharedKey uint16 = 41 // draft-ietf-tls-tls13-16
@@ -261,6 +262,8 @@
PeerCertificates []*x509.Certificate // certificate chain presented by remote peer
VerifiedChains [][]*x509.Certificate // verified chains built from PeerCertificates
ChannelID *ecdsa.PublicKey // the channel ID for this connection
+ TokenBindingNegotiated bool // whether Token Binding was negotiated
+ TokenBindingParam uint8 // the negotiated Token Binding key parameter
SRTPProtectionProfile uint16 // the negotiated DTLS-SRTP protection profile
TLSUnique []byte // the tls-unique channel binding
SCTList []byte // signed certificate timestamp list
@@ -453,6 +456,20 @@
// returned in the ConnectionState.
RequestChannelID bool
+ // TokenBindingParams contains a list of TokenBindingKeyParameters
+ // (draft-ietf-tokbind-protocol-16) to attempt to negotiate. If
+ // nil, Token Binding will not be negotiated.
+ TokenBindingParams []byte
+
+ // TokenBindingVersion contains the serialized ProtocolVersion to
+ // use when negotiating Token Binding.
+ TokenBindingVersion uint16
+
+ // ExpectTokenBindingParams is checked by a server that the client
+ // sent ExpectTokenBindingParams as its list of Token Binding
+ // paramters.
+ ExpectTokenBindingParams []byte
+
// PreSharedKey, if not nil, is the pre-shared key to use with
// the PSK cipher suites.
PreSharedKey []byte
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index 6493aa7..c38fba9 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -78,6 +78,9 @@
channelID *ecdsa.PublicKey
+ tokenBindingNegotiated bool
+ tokenBindingParam uint8
+
srtpProtectionProfile uint16
clientVersion uint16
@@ -1812,6 +1815,8 @@
state.VerifiedChains = c.verifiedChains
state.ServerName = c.serverName
state.ChannelID = c.channelID
+ state.TokenBindingNegotiated = c.tokenBindingNegotiated
+ state.TokenBindingParam = c.tokenBindingParam
state.SRTPProtectionProfile = c.srtpProtectionProfile
state.TLSUnique = c.firstFinished[:]
state.SCTList = c.sctList
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index e477edf..ab41e12 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -89,6 +89,8 @@
alpnProtocols: c.config.NextProtos,
duplicateExtension: c.config.Bugs.DuplicateExtension,
channelIDSupported: c.config.ChannelID != nil,
+ tokenBindingParams: c.config.TokenBindingParams,
+ tokenBindingVersion: c.config.TokenBindingVersion,
npnAfterAlpn: c.config.Bugs.SwapNPNAndALPN,
extendedMasterSecret: maxVersion >= VersionTLS10,
srtpProtectionProfiles: c.config.SRTPProtectionProfiles,
@@ -1451,6 +1453,29 @@
return errors.New("server advertised unrequested Channel ID extension")
}
+ if len(serverExtensions.tokenBindingParams) == 1 {
+ found := false
+ for _, p := range c.config.TokenBindingParams {
+ if p == serverExtensions.tokenBindingParams[0] {
+ c.tokenBindingParam = p
+ found = true
+ break
+ }
+ }
+ if !found {
+ return errors.New("tls: server advertised unsupported Token Binding key param")
+ }
+ if serverExtensions.tokenBindingVersion > c.config.TokenBindingVersion {
+ return errors.New("tls: server's Token Binding version is too new")
+ }
+ if c.vers < VersionTLS13 {
+ if !serverExtensions.extendedMasterSecret || serverExtensions.secureRenegotiation == nil {
+ return errors.New("server sent Token Binding without EMS or RI")
+ }
+ }
+ c.tokenBindingNegotiated = true
+ }
+
if serverExtensions.extendedMasterSecret && c.vers >= VersionTLS13 {
return errors.New("tls: server advertised extended master secret over TLS 1.3")
}
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index 16d858f..c80b1cf 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -281,6 +281,8 @@
alpnProtocols []string
duplicateExtension bool
channelIDSupported bool
+ tokenBindingParams []byte
+ tokenBindingVersion uint16
npnAfterAlpn bool
extendedMasterSecret bool
srtpProtectionProfiles []uint16
@@ -331,6 +333,8 @@
eqStrings(m.alpnProtocols, m1.alpnProtocols) &&
m.duplicateExtension == m1.duplicateExtension &&
m.channelIDSupported == m1.channelIDSupported &&
+ bytes.Equal(m.tokenBindingParams, m1.tokenBindingParams) &&
+ m.tokenBindingVersion == m1.tokenBindingVersion &&
m.npnAfterAlpn == m1.npnAfterAlpn &&
m.extendedMasterSecret == m1.extendedMasterSecret &&
eqUint16s(m.srtpProtectionProfiles, m1.srtpProtectionProfiles) &&
@@ -519,6 +523,13 @@
extensions.addU16(extensionChannelID)
extensions.addU16(0) // Length is always 0
}
+ if m.tokenBindingParams != nil {
+ extensions.addU16(extensionTokenBinding)
+ tokbindExtension := extensions.addU16LengthPrefixed()
+ tokbindExtension.addU16(m.tokenBindingVersion)
+ tokbindParams := tokbindExtension.addU8LengthPrefixed()
+ tokbindParams.addBytes(m.tokenBindingParams)
+ }
if m.nextProtoNeg && m.npnAfterAlpn {
extensions.addU16(extensionNextProtoNeg)
extensions.addU16(0) // Length is always 0
@@ -826,6 +837,12 @@
return false
}
m.channelIDSupported = true
+ case extensionTokenBinding:
+ if !body.readU16(&m.tokenBindingVersion) ||
+ !body.readU8LengthPrefixedBytes(&m.tokenBindingParams) ||
+ len(body) != 0 {
+ return false
+ }
case extensionExtendedMasterSecret:
if len(body) != 0 {
return false
@@ -1116,6 +1133,8 @@
alpnProtocolEmpty bool
duplicateExtension bool
channelIDRequested bool
+ tokenBindingParams []byte
+ tokenBindingVersion uint16
extendedMasterSecret bool
srtpProtectionProfile uint16
srtpMasterKeyIdentifier string
@@ -1175,6 +1194,13 @@
extensions.addU16(extensionChannelID)
extensions.addU16(0)
}
+ if m.tokenBindingParams != nil {
+ extensions.addU16(extensionTokenBinding)
+ tokbindExtension := extensions.addU16LengthPrefixed()
+ tokbindExtension.addU16(m.tokenBindingVersion)
+ tokbindParams := tokbindExtension.addU8LengthPrefixed()
+ tokbindParams.addBytes(m.tokenBindingParams)
+ }
if m.duplicateExtension {
// Add a duplicate bogus extension at the beginning and end.
extensions.addU16(0xffff)
@@ -1303,6 +1329,13 @@
return false
}
m.channelIDRequested = true
+ case extensionTokenBinding:
+ if !body.readU16(&m.tokenBindingVersion) ||
+ !body.readU8LengthPrefixedBytes(&m.tokenBindingParams) ||
+ len(m.tokenBindingParams) != 1 ||
+ len(body) != 0 {
+ return false
+ }
case extensionExtendedMasterSecret:
if len(body) != 0 {
return false
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index d317d01..dc74259 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -1381,6 +1381,21 @@
serverExtensions.channelIDRequested = true
}
+ if config.TokenBindingParams != nil {
+ if !bytes.Equal(config.ExpectTokenBindingParams, hs.clientHello.tokenBindingParams) {
+ return errors.New("client did not send expected token binding params")
+ }
+
+ // For testing, blindly send whatever is set in config, even if
+ // it is invalid.
+ serverExtensions.tokenBindingParams = config.TokenBindingParams
+ serverExtensions.tokenBindingVersion = config.TokenBindingVersion
+ }
+
+ if len(hs.clientHello.tokenBindingParams) > 0 && (!hs.clientHello.extendedMasterSecret || hs.clientHello.secureRenegotiation == nil) {
+ return errors.New("client sent Token Binding without EMS and/or RI")
+ }
+
if hs.clientHello.srtpProtectionProfiles != nil {
SRTPLoop:
for _, p1 := range c.config.SRTPProtectionProfiles {
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 1f2a20a..9255012 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -343,6 +343,12 @@
// expectChannelID controls whether the connection should have
// negotiated a Channel ID with channelIDKey.
expectChannelID bool
+ // expectTokenBinding controls whether the connection should have
+ // negotiated Token Binding.
+ expectTokenBinding bool
+ // expectedTokenBindingParam is the Token Binding parameter that should
+ // have been negotiated (if expectTokenBinding is true).
+ expectedTokenBindingParam uint8
// expectedNextProto controls whether the connection should
// negotiate a next protocol via NPN or ALPN.
expectedNextProto string
@@ -648,6 +654,17 @@
return fmt.Errorf("channel ID unexpectedly negotiated")
}
+ if test.expectTokenBinding {
+ if !connState.TokenBindingNegotiated {
+ return errors.New("no Token Binding negotiated")
+ }
+ if connState.TokenBindingParam != test.expectedTokenBindingParam {
+ return fmt.Errorf("expected param %02x, but got %02x", test.expectedTokenBindingParam, connState.TokenBindingParam)
+ }
+ } else if connState.TokenBindingNegotiated {
+ return errors.New("Token Binding unexpectedly negotiated")
+ }
+
if expected := test.expectedNextProto; expected != "" {
if actual := connState.NegotiatedProtocol; actual != expected {
return fmt.Errorf("next proto mismatch: got %s, wanted %s", actual, expected)
@@ -6149,6 +6166,404 @@
})
}
+ // Test Token Binding.
+
+ const maxTokenBindingVersion = 16
+ const minTokenBindingVersion = 13
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TokenBinding-Server-" + ver.name,
+
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{0, 1, 2},
+ TokenBindingVersion: maxTokenBindingVersion,
+ },
+ expectTokenBinding: true,
+ expectedTokenBindingParam: 2,
+ tls13Variant: ver.tls13Variant,
+ flags: []string{
+ "-token-binding-params",
+ base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+ "-expected-token-binding-param",
+ "2",
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TokenBinding-Server-UnsupportedParam-" + ver.name,
+
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{3},
+ TokenBindingVersion: maxTokenBindingVersion,
+ },
+ tls13Variant: ver.tls13Variant,
+ flags: []string{
+ "-token-binding-params",
+ base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TokenBinding-Server-OldVersion-" + ver.name,
+
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{0, 1, 2},
+ TokenBindingVersion: minTokenBindingVersion - 1,
+ },
+ tls13Variant: ver.tls13Variant,
+ flags: []string{
+ "-token-binding-params",
+ base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TokenBinding-Server-NewVersion-" + ver.name,
+
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{0, 1, 2},
+ TokenBindingVersion: maxTokenBindingVersion + 1,
+ },
+ expectTokenBinding: true,
+ expectedTokenBindingParam: 2,
+ tls13Variant: ver.tls13Variant,
+ flags: []string{
+ "-token-binding-params",
+ base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+ "-expected-token-binding-param",
+ "2",
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TokenBinding-Server-NoParams-" + ver.name,
+
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{},
+ TokenBindingVersion: maxTokenBindingVersion,
+ },
+ tls13Variant: ver.tls13Variant,
+ flags: []string{
+ "-token-binding-params",
+ base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+ },
+ shouldFail: true,
+ expectedError: ":ERROR_PARSING_EXTENSION:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TokenBinding-Server-RepeatedParam" + ver.name,
+
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{0, 1, 2, 2},
+ TokenBindingVersion: maxTokenBindingVersion,
+ },
+ expectTokenBinding: true,
+ expectedTokenBindingParam: 2,
+ tls13Variant: ver.tls13Variant,
+ flags: []string{
+ "-token-binding-params",
+ base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+ "-expected-token-binding-param",
+ "2",
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TokenBinding-Client-" + ver.name,
+
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{2},
+ TokenBindingVersion: maxTokenBindingVersion,
+ ExpectTokenBindingParams: []byte{0, 1, 2},
+ },
+ tls13Variant: ver.tls13Variant,
+ flags: []string{
+ "-token-binding-params",
+ base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
+ "-expected-token-binding-param",
+ "2",
+ },
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TokenBinding-Client-Unexpected-" + ver.name,
+
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{2},
+ TokenBindingVersion: maxTokenBindingVersion,
+ },
+ tls13Variant: ver.tls13Variant,
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION:",
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TokenBinding-Client-ExtraParams-" + ver.name,
+
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{2, 1},
+ TokenBindingVersion: maxTokenBindingVersion,
+ ExpectTokenBindingParams: []byte{0, 1, 2},
+ },
+ flags: []string{
+ "-token-binding-params",
+ base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
+ "-expected-token-binding-param",
+ "2",
+ },
+ tls13Variant: ver.tls13Variant,
+ shouldFail: true,
+ expectedError: ":ERROR_PARSING_EXTENSION:",
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TokenBinding-Client-NoParams-" + ver.name,
+
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{},
+ TokenBindingVersion: maxTokenBindingVersion,
+ ExpectTokenBindingParams: []byte{0, 1, 2},
+ },
+ flags: []string{
+ "-token-binding-params",
+ base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
+ "-expected-token-binding-param",
+ "2",
+ },
+ tls13Variant: ver.tls13Variant,
+ shouldFail: true,
+ expectedError: ":ERROR_PARSING_EXTENSION:",
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TokenBinding-Client-WrongParam-" + ver.name,
+
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{3},
+ TokenBindingVersion: maxTokenBindingVersion,
+ ExpectTokenBindingParams: []byte{0, 1, 2},
+ },
+ flags: []string{
+ "-token-binding-params",
+ base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
+ "-expected-token-binding-param",
+ "2",
+ },
+ tls13Variant: ver.tls13Variant,
+ shouldFail: true,
+ expectedError: ":ERROR_PARSING_EXTENSION:",
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TokenBinding-Client-OldVersion-" + ver.name,
+
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{2},
+ TokenBindingVersion: minTokenBindingVersion - 1,
+ ExpectTokenBindingParams: []byte{0, 1, 2},
+ },
+ flags: []string{
+ "-token-binding-params",
+ base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
+ },
+ tls13Variant: ver.tls13Variant,
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TokenBinding-Client-MinVersion-" + ver.name,
+
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{2},
+ TokenBindingVersion: minTokenBindingVersion,
+ ExpectTokenBindingParams: []byte{0, 1, 2},
+ },
+ flags: []string{
+ "-token-binding-params",
+ base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
+ "-expected-token-binding-param",
+ "2",
+ },
+ tls13Variant: ver.tls13Variant,
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TokenBinding-Client-VersionTooNew-" + ver.name,
+
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{2},
+ TokenBindingVersion: maxTokenBindingVersion + 1,
+ ExpectTokenBindingParams: []byte{0, 1, 2},
+ },
+ flags: []string{
+ "-token-binding-params",
+ base64.StdEncoding.EncodeToString([]byte{0, 1, 2}),
+ },
+ tls13Variant: ver.tls13Variant,
+ shouldFail: true,
+ expectedError: "ERROR_PARSING_EXTENSION",
+ })
+ if ver.version < VersionTLS13 {
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TokenBinding-Client-NoEMS-" + ver.name,
+
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{2},
+ TokenBindingVersion: maxTokenBindingVersion,
+ ExpectTokenBindingParams: []byte{2, 1, 0},
+ Bugs: ProtocolBugs{
+ NoExtendedMasterSecret: true,
+ },
+ },
+ tls13Variant: ver.tls13Variant,
+ flags: []string{
+ "-token-binding-params",
+ base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+ },
+ shouldFail: true,
+ expectedError: ":NEGOTIATED_TB_WITHOUT_EMS_OR_RI:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TokenBinding-Server-NoEMS-" + ver.name,
+
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{0, 1, 2},
+ TokenBindingVersion: maxTokenBindingVersion,
+ Bugs: ProtocolBugs{
+ NoExtendedMasterSecret: true,
+ },
+ },
+ tls13Variant: ver.tls13Variant,
+ flags: []string{
+ "-token-binding-params",
+ base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+ },
+ shouldFail: true,
+ expectedError: ":NEGOTIATED_TB_WITHOUT_EMS_OR_RI:",
+ })
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TokenBinding-Client-NoRI-" + ver.name,
+
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{2},
+ TokenBindingVersion: maxTokenBindingVersion,
+ ExpectTokenBindingParams: []byte{2, 1, 0},
+ Bugs: ProtocolBugs{
+ NoRenegotiationInfo: true,
+ },
+ },
+ tls13Variant: ver.tls13Variant,
+ flags: []string{
+ "-token-binding-params",
+ base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+ },
+ shouldFail: true,
+ expectedError: ":NEGOTIATED_TB_WITHOUT_EMS_OR_RI:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TokenBinding-Server-NoRI-" + ver.name,
+
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{0, 1, 2},
+ TokenBindingVersion: maxTokenBindingVersion,
+ Bugs: ProtocolBugs{
+ NoRenegotiationInfo: true,
+ },
+ },
+ tls13Variant: ver.tls13Variant,
+ flags: []string{
+ "-token-binding-params",
+ base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+ },
+ shouldFail: true,
+ expectedError: ":NEGOTIATED_TB_WITHOUT_EMS_OR_RI:",
+ })
+ } else {
+ testCases = append(testCases, testCase{
+ testType: clientTest,
+ name: "TokenBinding-WithEarlyDataFails-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{2},
+ TokenBindingVersion: maxTokenBindingVersion,
+ ExpectTokenBindingParams: []byte{2, 1, 0},
+ MaxEarlyDataSize: 16384,
+ },
+ resumeSession: true,
+ tls13Variant: ver.tls13Variant,
+ flags: []string{
+ "-enable-early-data",
+ "-expect-ticket-supports-early-data",
+ "-token-binding-params",
+ base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+ },
+ shouldFail: true,
+ expectedError: ":UNEXPECTED_EXTENSION_ON_EARLY_DATA:",
+ })
+ testCases = append(testCases, testCase{
+ testType: serverTest,
+ name: "TokenBinding-EarlyDataRejected-" + ver.name,
+ config: Config{
+ MinVersion: ver.version,
+ MaxVersion: ver.version,
+ TokenBindingParams: []byte{0, 1, 2},
+ TokenBindingVersion: maxTokenBindingVersion,
+ MaxEarlyDataSize: 16384,
+ },
+ resumeSession: true,
+ expectTokenBinding: true,
+ expectedTokenBindingParam: 2,
+ tls13Variant: ver.tls13Variant,
+ flags: []string{
+ "-enable-early-data",
+ "-expect-ticket-supports-early-data",
+ "-token-binding-params",
+ base64.StdEncoding.EncodeToString([]byte{2, 1, 0}),
+ },
+ })
+ }
+
// Test ticket behavior.
// Resume with a corrupt ticket.
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index 0d11f90..7c660c8 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -163,6 +163,7 @@
const Flag<std::string> kBase64Flags[] = {
{ "-expect-certificate-types", &TestConfig::expected_certificate_types },
{ "-expect-channel-id", &TestConfig::expected_channel_id },
+ { "-token-binding-params", &TestConfig::send_token_binding_params },
{ "-expect-ocsp-response", &TestConfig::expected_ocsp_response },
{ "-expect-signed-cert-timestamps",
&TestConfig::expected_signed_cert_timestamps },
@@ -174,6 +175,8 @@
const Flag<int> kIntFlags[] = {
{ "-port", &TestConfig::port },
{ "-resume-count", &TestConfig::resume_count },
+ { "-expected-token-binding-param",
+ &TestConfig::expected_token_binding_param },
{ "-min-version", &TestConfig::min_version },
{ "-max-version", &TestConfig::max_version },
{ "-expect-version", &TestConfig::expect_version },
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index fe107bc..7574337 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -49,6 +49,8 @@
std::string expected_channel_id;
bool enable_channel_id = false;
std::string send_channel_id;
+ int expected_token_binding_param = -1;
+ std::string send_token_binding_params;
bool shim_writes_first = false;
std::string host_name;
std::string advertise_alpn;
diff --git a/ssl/tls13_client.cc b/ssl/tls13_client.cc
index f437519..f013afd 100644
--- a/ssl/tls13_client.cc
+++ b/ssl/tls13_client.cc
@@ -476,7 +476,8 @@
OPENSSL_PUT_ERROR(SSL, SSL_R_ALPN_MISMATCH_ON_EARLY_DATA);
return ssl_hs_error;
}
- if (ssl->s3->tlsext_channel_id_valid || hs->received_custom_extension) {
+ if (ssl->s3->tlsext_channel_id_valid || hs->received_custom_extension ||
+ ssl->token_binding_negotiated) {
OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION_ON_EARLY_DATA);
return ssl_hs_error;
}
diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc
index 72273da..331e0d5 100644
--- a/ssl/tls13_server.cc
+++ b/ssl/tls13_server.cc
@@ -398,6 +398,8 @@
hs->early_data_offered &&
// Channel ID is incompatible with 0-RTT.
!ssl->s3->tlsext_channel_id_valid &&
+ // If Token Binding is negotiated, reject 0-RTT.
+ !ssl->token_binding_negotiated &&
// Custom extensions is incompatible with 0-RTT.
hs->custom_extensions.received == 0 &&
// The negotiated ALPN must match the one in the ticket.