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>kris/onging/CECPQ3_patch15
@@ -86,6 +86,7 @@ SSL,167,MISSING_TMP_ECDH_KEY | |||
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 | |||
@@ -151,7 +151,7 @@ extern "C" { | |||
// 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) | |||
@@ -2785,6 +2785,33 @@ OPENSSL_EXPORT void (*SSL_CTX_get_channel_id_cb(SSL_CTX *ctx))( | |||
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 @@ OPENSSL_EXPORT bool SealRecord(SSL *ssl, Span<uint8_t> out_prefix, | |||
#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 | |||
@@ -202,6 +202,9 @@ extern "C" { | |||
// 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 | |||
@@ -757,6 +757,13 @@ static enum ssl_hs_wait_t do_read_server_hello(SSL_HANDSHAKE *hs) { | |||
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) { | |||
@@ -1376,6 +1376,12 @@ struct SSL_HANDSHAKE { | |||
// 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 @@ struct SSLConnection { | |||
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 @@ struct SSLConnection { | |||
// 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. | |||
@@ -771,6 +771,7 @@ void SSL_free(SSL *ssl) { | |||
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 @@ size_t SSL_get_tls_channel_id(SSL *ssl, uint8_t *out, size_t max_out) { | |||
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; | |||
@@ -2439,6 +2439,153 @@ static bool ext_supported_groups_parse_clienthello(SSL_HANDSHAKE *hs, | |||
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 @@ static const struct tls_extension kExtensions[] = { | |||
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_scan_serverhello_tlsext(SSL_HANDSHAKE *hs, CBS *cbs, | |||
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; | |||
@@ -1725,6 +1725,18 @@ static bool CheckHandshakeProperties(SSL *ssl, bool is_resume, | |||
} | |||
} | |||
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 @@ static bool DoConnection(bssl::UniquePtr<SSL_SESSION> *out_session, | |||
} | |||
} | |||
} | |||
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; | |||
@@ -122,6 +122,7 @@ const ( | |||
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 @@ type ConnectionState struct { | |||
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 @@ type Config struct { | |||
// 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 | |||
@@ -78,6 +78,9 @@ type Conn struct { | |||
channelID *ecdsa.PublicKey | |||
tokenBindingNegotiated bool | |||
tokenBindingParam uint8 | |||
srtpProtectionProfile uint16 | |||
clientVersion uint16 | |||
@@ -1812,6 +1815,8 @@ func (c *Conn) ConnectionState() ConnectionState { | |||
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 | |||
@@ -89,6 +89,8 @@ func (c *Conn) clientHandshake() error { | |||
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 @@ func (hs *clientHandshakeState) processServerExtensions(serverExtensions *server | |||
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") | |||
} | |||
@@ -281,6 +281,8 @@ type clientHelloMsg struct { | |||
alpnProtocols []string | |||
duplicateExtension bool | |||
channelIDSupported bool | |||
tokenBindingParams []byte | |||
tokenBindingVersion uint16 | |||
npnAfterAlpn bool | |||
extendedMasterSecret bool | |||
srtpProtectionProfiles []uint16 | |||
@@ -331,6 +333,8 @@ func (m *clientHelloMsg) equal(i interface{}) bool { | |||
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 @@ func (m *clientHelloMsg) marshal() []byte { | |||
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 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { | |||
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 @@ type serverExtensions struct { | |||
alpnProtocolEmpty bool | |||
duplicateExtension bool | |||
channelIDRequested bool | |||
tokenBindingParams []byte | |||
tokenBindingVersion uint16 | |||
extendedMasterSecret bool | |||
srtpProtectionProfile uint16 | |||
srtpMasterKeyIdentifier string | |||
@@ -1175,6 +1194,13 @@ func (m *serverExtensions) marshal(extensions *byteBuilder) { | |||
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 @@ func (m *serverExtensions) unmarshal(data byteReader, version uint16) bool { | |||
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 | |||
@@ -1381,6 +1381,21 @@ func (hs *serverHandshakeState) processClientExtensions(serverExtensions *server | |||
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 { | |||
@@ -343,6 +343,12 @@ type testCase struct { | |||
// 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 @@ func doExchange(test *testCase, config *Config, conn net.Conn, isResume bool, tr | |||
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 @@ func addExtensionTests() { | |||
}) | |||
} | |||
// 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. | |||
@@ -163,6 +163,7 @@ const Flag<std::string> kStringFlags[] = { | |||
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<std::string> kBase64Flags[] = { | |||
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 }, | |||
@@ -49,6 +49,8 @@ struct TestConfig { | |||
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; | |||
@@ -476,7 +476,8 @@ static enum ssl_hs_wait_t do_read_encrypted_extensions(SSL_HANDSHAKE *hs) { | |||
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; | |||
} | |||
@@ -398,6 +398,8 @@ static enum ssl_hs_wait_t do_select_session(SSL_HANDSHAKE *hs) { | |||
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. | |||