Index: net/socket/ssl_client_socket_openssl.cc |
diff --git a/net/socket/ssl_client_socket_openssl.cc b/net/socket/ssl_client_socket_openssl.cc |
index 0d39f6483f2b3bcfed6a543a61e78995d917828a..59d170778bf2cbfec29ff2134455ae71d87dadde 100644 |
--- a/net/socket/ssl_client_socket_openssl.cc |
+++ b/net/socket/ssl_client_socket_openssl.cc |
@@ -9,7 +9,9 @@ |
#include <errno.h> |
#include <openssl/bio.h> |
+#include <openssl/bytestring.h> |
#include <openssl/err.h> |
+#include <openssl/evp.h> |
#include <openssl/mem.h> |
#include <openssl/ssl.h> |
#include <string.h> |
@@ -82,6 +84,15 @@ const char kDefaultSupportedNPNProtocol[] = "http/1.1"; |
// Default size of the internal BoringSSL buffers. |
const int KDefaultOpenSSLBufferSize = 17 * 1024; |
+// TLS extension number use for Token Binding. |
+const unsigned int kTbExtNum = 30033; |
+ |
+// Token Binding ProtocolVersions supported. |
+const uint8_t kTbProtocolVersionMajor = 0; |
+const uint8_t kTbProtocolVersionMinor = 3; |
+const uint8_t kTbMinProtocolVersionMajor = 0; |
+const uint8_t kTbMinProtocolVersionMinor = 2; |
+ |
void FreeX509Stack(STACK_OF(X509)* ptr) { |
sk_X509_pop_free(ptr, X509_free); |
} |
@@ -194,6 +205,18 @@ base::LazyInstance<PlatformKeyTaskRunner>::Leaky g_platform_key_task_runner = |
LAZY_INSTANCE_INITIALIZER; |
#endif |
+class ScopedCBB { |
+ public: |
+ ScopedCBB() { CBB_zero(&cbb_); } |
+ ~ScopedCBB() { CBB_cleanup(&cbb_); } |
+ |
+ CBB* get() { return &cbb_; } |
+ |
+ private: |
+ CBB cbb_; |
+ DISALLOW_COPY_AND_ASSIGN(ScopedCBB); |
+}; |
+ |
} // namespace |
class SSLClientSocketOpenSSL::SSLContext { |
@@ -243,6 +266,47 @@ class SSLClientSocketOpenSSL::SSLContext { |
SSL_CTX_set_session_cache_mode( |
ssl_ctx_.get(), SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL); |
SSL_CTX_sess_set_new_cb(ssl_ctx_.get(), NewSessionCallback); |
+ |
+ if (!SSL_CTX_add_client_custom_ext(ssl_ctx_.get(), kTbExtNum, |
+ &TokenBindingAddCallback, |
+ &TokenBindingFreeCallback, nullptr, |
+ &TokenBindingParseCallback, nullptr)) { |
+ NOTREACHED(); |
+ } |
+ } |
+ |
+ static int TokenBindingAddCallback(SSL* ssl, |
+ unsigned int extension_value, |
+ const uint8_t** out, |
+ size_t* out_len, |
+ int* out_alert_value, |
+ void* add_arg) { |
+ DCHECK_EQ(extension_value, kTbExtNum); |
+ SSLClientSocketOpenSSL* socket = |
+ SSLClientSocketOpenSSL::SSLContext::GetInstance() |
+ ->GetClientSocketFromSSL(ssl); |
+ return socket->TokenBindingAdd(out, out_len, out_alert_value); |
+ } |
+ |
+ static void TokenBindingFreeCallback(SSL* ssl, |
+ unsigned extension_value, |
+ const uint8_t* out, |
+ void* add_arg) { |
+ DCHECK_EQ(extension_value, kTbExtNum); |
+ OPENSSL_free(const_cast<unsigned char*>(out)); |
+ } |
+ |
+ static int TokenBindingParseCallback(SSL* ssl, |
+ unsigned int extension_value, |
+ const uint8_t* contents, |
+ size_t contents_len, |
+ int* out_alert_value, |
+ void* parse_arg) { |
+ DCHECK_EQ(extension_value, kTbExtNum); |
+ SSLClientSocketOpenSSL* socket = |
+ SSLClientSocketOpenSSL::SSLContext::GetInstance() |
+ ->GetClientSocketFromSSL(ssl); |
+ return socket->TokenBindingParse(contents, contents_len, out_alert_value); |
} |
static int ClientCertRequestCallback(SSL* ssl, void* arg) { |
@@ -431,6 +495,8 @@ SSLClientSocketOpenSSL::SSLClientSocketOpenSSL( |
cert_verifier_(context.cert_verifier), |
cert_transparency_verifier_(context.cert_transparency_verifier), |
channel_id_service_(context.channel_id_service), |
+ tb_was_negotiated_(false), |
+ tb_negotiated_param_(TB_PARAM_ECDSAP256), |
ssl_(NULL), |
transport_bio_(NULL), |
transport_(transport_socket.Pass()), |
@@ -607,6 +673,7 @@ void SSLClientSocketOpenSSL::Disconnect() { |
npn_proto_.clear(); |
channel_id_sent_ = false; |
+ tb_was_negotiated_ = false; |
session_pending_ = false; |
certificate_verified_ = false; |
channel_id_request_.Cancel(); |
@@ -704,6 +771,8 @@ bool SSLClientSocketOpenSSL::GetSSLInfo(SSLInfo* ssl_info) { |
ssl_info->client_cert_sent = |
ssl_config_.send_client_cert && ssl_config_.client_cert.get(); |
ssl_info->channel_id_sent = channel_id_sent_; |
+ ssl_info->token_binding_negotiated = tb_was_negotiated_; |
+ ssl_info->token_binding_key_param = tb_negotiated_param_; |
ssl_info->pinning_failure_log = pinning_failure_log_; |
AddSCTInfoToSSLInfo(ssl_info); |
@@ -1099,6 +1168,11 @@ int SSLClientSocketOpenSSL::DoHandshakeComplete(int result) { |
return ERR_SSL_FALLBACK_BEYOND_MINIMUM_VERSION; |
} |
+ // Check that if token binding was negotiated, then extended master secret |
+ // must also be negotiated. |
+ if (tb_was_negotiated_ && !SSL_get_extms_support(ssl_)) |
+ return ERR_SSL_PROTOCOL_ERROR; |
+ |
// SSL handshake is completed. If NPN wasn't negotiated, see if ALPN was. |
if (npn_status_ == kNextProtoUnsupported) { |
const uint8_t* alpn_proto = NULL; |
@@ -2061,6 +2135,9 @@ std::string SSLClientSocketOpenSSL::GetSessionCacheKey() const { |
} |
bool SSLClientSocketOpenSSL::IsRenegotiationAllowed() const { |
+ if (tb_was_negotiated_) |
+ return false; |
+ |
if (npn_status_ == kNextProtoUnsupported) |
return ssl_config_.renego_allowed_default; |
@@ -2161,4 +2238,83 @@ void SSLClientSocketOpenSSL::OnPrivateKeySignComplete( |
PumpReadWriteEvents(); |
} |
+int SSLClientSocketOpenSSL::TokenBindingAdd(const uint8_t** out, |
+ size_t* out_len, |
+ int* out_alert_value) { |
+ if (ssl_config_.token_binding_params.empty()) { |
+ return 0; |
+ } |
+ ScopedCBB output; |
+ CBB parameters_list; |
+ if (!CBB_init(output.get(), 7) || |
+ !CBB_add_u8(output.get(), kTbProtocolVersionMajor) || |
+ !CBB_add_u8(output.get(), kTbProtocolVersionMinor) || |
+ !CBB_add_u8_length_prefixed(output.get(), ¶meters_list)) { |
+ *out_alert_value = SSL_AD_INTERNAL_ERROR; |
+ return -1; |
+ } |
+ for (size_t i = 0; i < ssl_config_.token_binding_params.size(); ++i) { |
+ if (!CBB_add_u8(¶meters_list, ssl_config_.token_binding_params[i])) { |
+ *out_alert_value = SSL_AD_INTERNAL_ERROR; |
+ return -1; |
+ } |
+ } |
+ // |*out| will be freed by TokenBindingFreeCallback. |
+ if (!CBB_finish(output.get(), const_cast<uint8_t**>(out), out_len)) { |
+ *out_alert_value = SSL_AD_INTERNAL_ERROR; |
+ return -1; |
+ } |
+ |
+ return 1; |
+} |
+ |
+int SSLClientSocketOpenSSL::TokenBindingParse(const uint8_t* contents, |
+ size_t contents_len, |
+ int* out_alert_value) { |
+ if (completed_connect_) { |
+ // Token Binding may only be negotiated on the initial handshake. |
+ *out_alert_value = SSL_AD_ILLEGAL_PARAMETER; |
+ return 0; |
+ } |
+ |
+ CBS extension; |
+ CBS_init(&extension, contents, contents_len); |
+ |
+ CBS parameters_list; |
+ uint8_t version_major, version_minor, param; |
+ if (!CBS_get_u8(&extension, &version_major) || |
+ !CBS_get_u8(&extension, &version_minor) || |
+ !CBS_get_u8_length_prefixed(&extension, ¶meters_list) || |
+ !CBS_get_u8(¶meters_list, ¶m) || CBS_len(¶meters_list) > 0 || |
+ CBS_len(&extension) > 0) { |
+ *out_alert_value = SSL_AD_DECODE_ERROR; |
+ return 0; |
+ } |
+ // The server-negotiated version must be less than or equal to our version. |
+ if (version_major > kTbProtocolVersionMajor || |
+ (version_minor > kTbProtocolVersionMinor && |
+ version_major == kTbProtocolVersionMajor)) { |
+ *out_alert_value = SSL_AD_ILLEGAL_PARAMETER; |
+ return 0; |
+ } |
+ // If the version the server negotiated is older than we support, don't fail |
+ // parsing the extension, but also don't set |negotiated_|. |
+ if (version_major < kTbMinProtocolVersionMajor || |
+ (version_minor < kTbMinProtocolVersionMinor && |
+ version_major == kTbMinProtocolVersionMajor)) { |
+ return 1; |
+ } |
+ |
+ for (size_t i = 0; i < ssl_config_.token_binding_params.size(); ++i) { |
+ if (param == ssl_config_.token_binding_params[i]) { |
+ tb_negotiated_param_ = ssl_config_.token_binding_params[i]; |
+ tb_was_negotiated_ = true; |
+ return 1; |
+ } |
+ } |
+ |
+ *out_alert_value = SSL_AD_ILLEGAL_PARAMETER; |
+ return 0; |
+} |
+ |
} // namespace net |