diff options
55 files changed, 1029 insertions, 80 deletions
diff --git a/chrome/browser/extensions/api/socket/tls_socket_unittest.cc b/chrome/browser/extensions/api/socket/tls_socket_unittest.cc index b6b58b9..15f9218 100644 --- a/chrome/browser/extensions/api/socket/tls_socket_unittest.cc +++ b/chrome/browser/extensions/api/socket/tls_socket_unittest.cc @@ -74,6 +74,8 @@ class MockSSLClientSocket : public net::SSLClientSocket { MOCK_CONST_METHOD0(GetUnverifiedServerCertificateChain, scoped_refptr<net::X509Certificate>()); MOCK_CONST_METHOD0(GetChannelIDService, net::ChannelIDService*()); + MOCK_METHOD2(GetSignedEKMForTokenBinding, + net::Error(crypto::ECPrivateKey*, std::vector<uint8_t>*)); MOCK_CONST_METHOD0(GetSSLFailureState, net::SSLFailureState()); bool IsConnected() const override { return true; } diff --git a/chrome/browser/io_thread.cc b/chrome/browser/io_thread.cc index 0de54ea..5e38908 100644 --- a/chrome/browser/io_thread.cc +++ b/chrome/browser/io_thread.cc @@ -455,8 +455,8 @@ IOThread::Globals::Globals() ignore_certificate_errors(false), testing_fixed_http_port(0), testing_fixed_https_port(0), - enable_user_alternate_protocol_ports(false) { -} + enable_user_alternate_protocol_ports(false), + enable_token_binding(false) {} IOThread::Globals::~Globals() {} @@ -847,6 +847,9 @@ void IOThread::Init() { } globals_->enable_brotli.set( base::FeatureList::IsEnabled(features::kBrotliEncoding)); + if (command_line.HasSwitch(switches::kEnableTokenBinding)) { + globals_->enable_token_binding = true; + } // TODO(erikchen): Remove ScopedTracker below once http://crbug.com/466432 // is fixed. tracked_objects::ScopedTracker tracking_profile13( @@ -1188,6 +1191,7 @@ void IOThread::InitializeNetworkSessionParamsFromGlobals( ¶ms->origin_to_force_quic_on); params->enable_user_alternate_protocol_ports = globals.enable_user_alternate_protocol_ports; + params->enable_token_binding = globals.enable_token_binding; } base::TimeTicks IOThread::creation_time() const { diff --git a/chrome/browser/io_thread.h b/chrome/browser/io_thread.h index bebae8a..be33922 100644 --- a/chrome/browser/io_thread.h +++ b/chrome/browser/io_thread.h @@ -248,6 +248,7 @@ class IOThread : public content::BrowserThreadDelegate { // main frame load fails with a DNS error in order to provide more useful // information to the renderer so it can show a more specific error page. scoped_ptr<chrome_browser_net::DnsProbeService> dns_probe_service; + bool enable_token_binding; }; // |net_log| must either outlive the IOThread or be NULL. diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index 1b9d664..0147315 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc @@ -545,6 +545,9 @@ const char kEnableTabAudioMuting[] = "enable-tab-audio-muting"; // instant-extended-api, where thumbnails are generally smaller. const char kEnableThumbnailRetargeting[] = "enable-thumbnail-retargeting"; +// Enables token binding (draft-ietf-tokbind-protocol-02). +const char kEnableTokenBinding[] = "enable-token-binding"; + // Enables Alternate-Protocol when the port is user controlled (> 1024). const char kEnableUserAlternateProtocolPorts[] = "enable-user-controlled-alternate-protocol-ports"; diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index 71aac50..b98abe4 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h @@ -155,6 +155,7 @@ extern const char kEnableSiteEngagementService[]; extern const char kEnableSupervisedUserManagedBookmarksFolder[]; extern const char kEnableTabAudioMuting[]; extern const char kEnableThumbnailRetargeting[]; +extern const char kEnableTokenBinding[]; extern const char kEnableUserAlternateProtocolPorts[]; extern const char kEnableWebAppFrame[]; extern const char kEnableWebNotificationCustomLayouts[]; diff --git a/net/BUILD.gn b/net/BUILD.gn index 6203ce4..33d414b 100644 --- a/net/BUILD.gn +++ b/net/BUILD.gn @@ -180,6 +180,7 @@ if (!is_nacl) { "socket/ssl_client_socket_nss.h", "socket/ssl_server_socket_nss.cc", "socket/ssl_server_socket_nss.h", + "ssl/token_binding_nss.cc", ] if (is_ios) { # Always removed for !ios below. @@ -226,6 +227,7 @@ if (!is_nacl) { "ssl/ssl_platform_key_task_runner.h", "ssl/threaded_ssl_private_key.cc", "ssl/threaded_ssl_private_key.h", + "ssl/token_binding_openssl.cc", ] } diff --git a/net/http/http_basic_stream.cc b/net/http/http_basic_stream.cc index 260ad55..6b08d13 100644 --- a/net/http/http_basic_stream.cc +++ b/net/http/http_basic_stream.cc @@ -111,6 +111,11 @@ bool HttpBasicStream::GetRemoteEndpoint(IPEndPoint* endpoint) { return state_.connection()->socket()->GetPeerAddress(endpoint) == OK; } +Error HttpBasicStream::GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) { + return parser()->GetSignedEKMForTokenBinding(key, out); +} + void HttpBasicStream::Drain(HttpNetworkSession* session) { HttpResponseBodyDrainer* drainer = new HttpResponseBodyDrainer(this); drainer->Start(session); diff --git a/net/http/http_basic_stream.h b/net/http/http_basic_stream.h index 0ca1962..1bb9a0b 100644 --- a/net/http/http_basic_stream.h +++ b/net/http/http_basic_stream.h @@ -76,6 +76,9 @@ class HttpBasicStream : public HttpStream { bool GetRemoteEndpoint(IPEndPoint* endpoint) override; + Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) override; + void Drain(HttpNetworkSession* session) override; void PopulateNetErrorDetails(NetErrorDetails* details) override; diff --git a/net/http/http_network_session.cc b/net/http/http_network_session.cc index f528ab1..1836014 100644 --- a/net/http/http_network_session.cc +++ b/net/http/http_network_session.cc @@ -129,7 +129,8 @@ HttpNetworkSession::Params::Params() quic_idle_connection_timeout_seconds(kIdleConnectionTimeoutSeconds), quic_disable_preconnect_if_0rtt(false), quic_migrate_sessions_on_network_change(false), - proxy_delegate(NULL) { + proxy_delegate(NULL), + enable_token_binding(false) { quic_supported_versions.push_back(QUIC_VERSION_25); } diff --git a/net/http/http_network_session.h b/net/http/http_network_session.h index a9c2ac9..f445590 100644 --- a/net/http/http_network_session.h +++ b/net/http/http_network_session.h @@ -191,6 +191,8 @@ class NET_EXPORT HttpNetworkSession // changes. bool quic_migrate_sessions_on_network_change; ProxyDelegate* proxy_delegate; + // Enable support for Token Binding. + bool enable_token_binding; }; enum SocketPoolType { diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc index d6baef1..93b4c30 100644 --- a/net/http/http_network_transaction.cc +++ b/net/http/http_network_transaction.cc @@ -8,6 +8,7 @@ #include <utility> #include <vector> +#include "base/base64url.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/compiler_specific.h" @@ -62,6 +63,7 @@ #include "net/ssl/ssl_cert_request_info.h" #include "net/ssl/ssl_connection_status_flags.h" #include "net/ssl/ssl_private_key.h" +#include "net/ssl/token_binding.h" #include "url/gurl.h" #include "url/url_canon.h" @@ -201,6 +203,11 @@ int HttpNetworkTransaction::Start(const HttpRequestInfo* request_info, if (request_->privacy_mode == PRIVACY_MODE_ENABLED) server_ssl_config_.channel_id_enabled = false; + if (session_->params().enable_token_binding && + session_->params().channel_id_service) { + server_ssl_config_.token_binding_params.push_back(TB_PARAM_ECDSAP256); + } + next_state_ = STATE_NOTIFY_BEFORE_CREATE_STREAM; int rv = DoLoop(OK); if (rv == ERR_IO_PENDING) @@ -639,6 +646,42 @@ bool HttpNetworkTransaction::IsSecureRequest() const { return request_->url.SchemeIsCryptographic(); } +bool HttpNetworkTransaction::IsTokenBindingEnabled() const { + if (!IsSecureRequest()) + return false; + SSLInfo ssl_info; + stream_->GetSSLInfo(&ssl_info); + return ssl_info.token_binding_negotiated && + ssl_info.token_binding_key_param == TB_PARAM_ECDSAP256 && + session_->params().channel_id_service; +} + +void HttpNetworkTransaction::RecordTokenBindingSupport() const { + // This enum is used for an UMA histogram - do not change or re-use values. + enum { + DISABLED = 0, + CLIENT_ONLY = 1, + CLIENT_AND_SERVER = 2, + CLIENT_NO_CHANNEL_ID_SERVICE = 3, + TOKEN_BINDING_SUPPORT_MAX + } supported; + if (!IsSecureRequest()) + return; + SSLInfo ssl_info; + stream_->GetSSLInfo(&ssl_info); + if (!session_->params().enable_token_binding) { + supported = DISABLED; + } else if (!session_->params().channel_id_service) { + supported = CLIENT_NO_CHANNEL_ID_SERVICE; + } else if (ssl_info.token_binding_negotiated) { + supported = CLIENT_AND_SERVER; + } else { + supported = CLIENT_ONLY; + } + UMA_HISTOGRAM_ENUMERATION("Net.TokenBinding.Support", supported, + TOKEN_BINDING_SUPPORT_MAX); +} + bool HttpNetworkTransaction::UsingHttpProxyWithoutTunnel() const { return (proxy_info_.is_http() || proxy_info_.is_https() || proxy_info_.is_quic()) && @@ -701,6 +744,13 @@ int HttpNetworkTransaction::DoLoop(int result) { case STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE: rv = DoGenerateServerAuthTokenComplete(rv); break; + case STATE_GET_TOKEN_BINDING_KEY: + DCHECK_EQ(OK, rv); + rv = DoGetTokenBindingKey(); + break; + case STATE_GET_TOKEN_BINDING_KEY_COMPLETE: + rv = DoGetTokenBindingKeyComplete(rv); + break; case STATE_INIT_REQUEST_BODY: DCHECK_EQ(OK, rv); rv = DoInitRequestBody(); @@ -916,11 +966,34 @@ int HttpNetworkTransaction::DoGenerateServerAuthToken() { int HttpNetworkTransaction::DoGenerateServerAuthTokenComplete(int rv) { DCHECK_NE(ERR_IO_PENDING, rv); if (rv == OK) - next_state_ = STATE_INIT_REQUEST_BODY; + next_state_ = STATE_GET_TOKEN_BINDING_KEY; return rv; } -void HttpNetworkTransaction::BuildRequestHeaders( +int HttpNetworkTransaction::DoGetTokenBindingKey() { + next_state_ = STATE_GET_TOKEN_BINDING_KEY_COMPLETE; + if (!IsTokenBindingEnabled()) + return OK; + + net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_GET_TOKEN_BINDING_KEY); + ChannelIDService* channel_id_service = session_->params().channel_id_service; + return channel_id_service->GetOrCreateChannelID( + request_->url.host(), &token_binding_key_, io_callback_, + &token_binding_request_); +} + +int HttpNetworkTransaction::DoGetTokenBindingKeyComplete(int rv) { + DCHECK_NE(ERR_IO_PENDING, rv); + next_state_ = STATE_INIT_REQUEST_BODY; + if (!IsTokenBindingEnabled()) + return OK; + + net_log_.EndEventWithNetErrorCode( + NetLog::TYPE_HTTP_TRANSACTION_GET_TOKEN_BINDING_KEY, rv); + return rv; +} + +int HttpNetworkTransaction::BuildRequestHeaders( bool using_http_proxy_without_tunnel) { request_headers_.SetHeader(HttpRequestHeaders::kHost, GetHostAndOptionalPort(request_->url)); @@ -953,6 +1026,16 @@ void HttpNetworkTransaction::BuildRequestHeaders( request_headers_.SetHeader(HttpRequestHeaders::kContentLength, "0"); } + RecordTokenBindingSupport(); + if (token_binding_key_) { + std::string token_binding_header; + int rv = BuildTokenBindingHeader(&token_binding_header); + if (rv != OK) + return rv; + request_headers_.SetHeader(HttpRequestHeaders::kTokenBinding, + token_binding_header); + } + // Honor load flags that impact proxy caches. if (request_->load_flags & LOAD_BYPASS_CACHE) { request_headers_.SetHeader(HttpRequestHeaders::kPragma, "no-cache"); @@ -977,6 +1060,29 @@ void HttpNetworkTransaction::BuildRequestHeaders( response_.did_use_http_auth = request_headers_.HasHeader(HttpRequestHeaders::kAuthorization) || request_headers_.HasHeader(HttpRequestHeaders::kProxyAuthorization); + return OK; +} + +int HttpNetworkTransaction::BuildTokenBindingHeader(std::string* out) { + std::vector<uint8_t> signed_ekm; + int rv = stream_->GetSignedEKMForTokenBinding(token_binding_key_.get(), + &signed_ekm); + if (rv != OK) + return rv; + std::string provided_token_binding; + rv = BuildProvidedTokenBinding(token_binding_key_.get(), signed_ekm, + &provided_token_binding); + if (rv != OK) + return rv; + std::vector<base::StringPiece> token_bindings; + token_bindings.push_back(provided_token_binding); + std::string header; + rv = BuildTokenBindingMessageFromTokenBindings(token_bindings, &header); + if (rv != OK) + return rv; + base::Base64UrlEncode(header, base::Base64UrlEncodePolicy::INCLUDE_PADDING, + out); + return OK; } int HttpNetworkTransaction::DoInitRequestBody() { @@ -1001,7 +1107,7 @@ int HttpNetworkTransaction::DoBuildRequest() { // we have proxy info available. if (request_headers_.IsEmpty()) { bool using_http_proxy_without_tunnel = UsingHttpProxyWithoutTunnel(); - BuildRequestHeaders(using_http_proxy_without_tunnel); + return BuildRequestHeaders(using_http_proxy_without_tunnel); } return OK; @@ -1486,6 +1592,7 @@ void HttpNetworkTransaction::ResetStateForAuthRestart() { remote_endpoint_ = IPEndPoint(); net_error_details_.quic_broken = false; net_error_details_.quic_connection_error = QUIC_NO_ERROR; + token_binding_key_.reset(); } void HttpNetworkTransaction::CacheNetErrorDetailsAndResetStream() { diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h index f8584b8..5b554af 100644 --- a/net/http/http_network_transaction.h +++ b/net/http/http_network_transaction.h @@ -14,6 +14,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/time/time.h" +#include "crypto/ec_private_key.h" #include "net/base/net_error_details.h" #include "net/base/request_priority.h" #include "net/http/http_auth.h" @@ -24,10 +25,15 @@ #include "net/log/net_log.h" #include "net/proxy/proxy_service.h" #include "net/socket/connection_attempts.h" +#include "net/ssl/channel_id_service.h" #include "net/ssl/ssl_config_service.h" #include "net/ssl/ssl_failure_state.h" #include "net/websockets/websocket_handshake_stream_base.h" +namespace crypto { +class ECPrivateKey; +} + namespace net { class BidirectionalStreamJob; @@ -149,6 +155,8 @@ class NET_EXPORT_PRIVATE HttpNetworkTransaction STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE, STATE_GENERATE_SERVER_AUTH_TOKEN, STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE, + STATE_GET_TOKEN_BINDING_KEY, + STATE_GET_TOKEN_BINDING_KEY_COMPLETE, STATE_INIT_REQUEST_BODY, STATE_INIT_REQUEST_BODY_COMPLETE, STATE_BUILD_REQUEST, @@ -165,6 +173,8 @@ class NET_EXPORT_PRIVATE HttpNetworkTransaction }; bool IsSecureRequest() const; + bool IsTokenBindingEnabled() const; + void RecordTokenBindingSupport() const; // Returns true if the request is using an HTTP(S) proxy without being // tunneled via the CONNECT method. @@ -189,6 +199,8 @@ class NET_EXPORT_PRIVATE HttpNetworkTransaction int DoGenerateProxyAuthTokenComplete(int result); int DoGenerateServerAuthToken(); int DoGenerateServerAuthTokenComplete(int result); + int DoGetTokenBindingKey(); + int DoGetTokenBindingKeyComplete(int result); int DoInitRequestBody(); int DoInitRequestBodyComplete(int result); int DoBuildRequest(); @@ -202,7 +214,8 @@ class NET_EXPORT_PRIVATE HttpNetworkTransaction int DoDrainBodyForAuthRestart(); int DoDrainBodyForAuthRestartComplete(int result); - void BuildRequestHeaders(bool using_http_proxy_without_tunnel); + int BuildRequestHeaders(bool using_http_proxy_without_tunnel); + int BuildTokenBindingHeader(std::string* out); // Writes a log message to help debugging in the field when we block a proxy // response to a CONNECT request. @@ -328,6 +341,11 @@ class NET_EXPORT_PRIVATE HttpNetworkTransaction // The SSLFailureState which caused the last TLS version fallback. SSLFailureState fallback_failure_state_; + // Key to use for signing message in Token Binding header. + scoped_ptr<crypto::ECPrivateKey> token_binding_key_; + // Object to manage lookup of |token_binding_key_|. + ChannelIDService::Request token_binding_request_; + HttpRequestHeaders request_headers_; // The size in bytes of the buffer we use to drain the response body that diff --git a/net/http/http_network_transaction_ssl_unittest.cc b/net/http/http_network_transaction_ssl_unittest.cc index 4aa59a6..51f3406 100644 --- a/net/http/http_network_transaction_ssl_unittest.cc +++ b/net/http/http_network_transaction_ssl_unittest.cc @@ -18,6 +18,7 @@ #include "net/http/transport_security_state.h" #include "net/proxy/proxy_service.h" #include "net/socket/socket_test_util.h" +#include "net/ssl/default_channel_id_store.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { @@ -54,6 +55,20 @@ class TLS12SSLConfigService : public SSLConfigService { SSLConfig ssl_config_; }; +class TokenBindingSSLConfigService : public SSLConfigService { + public: + TokenBindingSSLConfigService() { + ssl_config_.token_binding_params.push_back(TB_PARAM_ECDSAP256); + } + + void GetSSLConfig(SSLConfig* config) override { *config = ssl_config_; } + + private: + ~TokenBindingSSLConfigService() override {} + + SSLConfig ssl_config_; +}; + } // namespace class HttpNetworkTransactionSSLTest : public testing::Test { @@ -148,5 +163,58 @@ TEST_F(HttpNetworkTransactionSSLTest, SSLFallback) { EXPECT_TRUE(ssl_config.version_fallback); } +#if !defined(OS_IOS) +TEST_F(HttpNetworkTransactionSSLTest, TokenBinding) { + ssl_config_service_ = new TokenBindingSSLConfigService; + session_params_.ssl_config_service = ssl_config_service_.get(); + ChannelIDService channel_id_service(new DefaultChannelIDStore(NULL), + base::ThreadTaskRunnerHandle::Get()); + session_params_.channel_id_service = &channel_id_service; + + SSLSocketDataProvider ssl_data(ASYNC, OK); + ssl_data.token_binding_negotiated = true; + ssl_data.token_binding_key_param = TB_PARAM_ECDSAP256; + mock_socket_factory_.AddSSLSocketDataProvider(&ssl_data); + MockRead mock_reads[] = {MockRead("HTTP/1.1 200 OK\r\n\r\n"), + MockRead(SYNCHRONOUS, OK)}; + StaticSocketDataProvider data(mock_reads, arraysize(mock_reads), NULL, 0); + mock_socket_factory_.AddSocketDataProvider(&data); + + HttpNetworkSession session(session_params_); + HttpNetworkTransaction trans(DEFAULT_PRIORITY, &session); + + TestCompletionCallback callback; + int rv = + callback.GetResult(trans.Start(GetRequestInfo("https://www.example.com/"), + callback.callback(), BoundNetLog())); + EXPECT_EQ(OK, rv); + + HttpRequestHeaders headers1; + ASSERT_TRUE(trans.GetFullRequestHeaders(&headers1)); + std::string token_binding_header1; + EXPECT_TRUE(headers1.GetHeader(HttpRequestHeaders::kTokenBinding, + &token_binding_header1)); + + // Send a second request and verify that the token binding header is the same + // as in the first request. + mock_socket_factory_.AddSSLSocketDataProvider(&ssl_data); + StaticSocketDataProvider data2(mock_reads, arraysize(mock_reads), NULL, 0); + mock_socket_factory_.AddSocketDataProvider(&data2); + + rv = + callback.GetResult(trans.Start(GetRequestInfo("https://www.example.com/"), + callback.callback(), BoundNetLog())); + EXPECT_EQ(OK, rv); + + HttpRequestHeaders headers2; + ASSERT_TRUE(trans.GetFullRequestHeaders(&headers2)); + std::string token_binding_header2; + EXPECT_TRUE(headers2.GetHeader(HttpRequestHeaders::kTokenBinding, + &token_binding_header2)); + + EXPECT_EQ(token_binding_header1, token_binding_header2); +} +#endif // !defined(OS_IOS) + } // namespace net diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc index ef00344..9bda396 100644 --- a/net/http/http_network_transaction_unittest.cc +++ b/net/http/http_network_transaction_unittest.cc @@ -75,6 +75,7 @@ #include "net/spdy/spdy_session.h" #include "net/spdy/spdy_session_pool.h" #include "net/spdy/spdy_test_util_common.h" +#include "net/ssl/default_channel_id_store.h" #include "net/ssl/ssl_cert_request_info.h" #include "net/ssl/ssl_config_service.h" #include "net/ssl/ssl_config_service_defaults.h" @@ -14413,6 +14414,12 @@ class FakeStream : public HttpStream, bool GetRemoteEndpoint(IPEndPoint* endpoint) override { return false; } + Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) override { + ADD_FAILURE(); + return ERR_NOT_IMPLEMENTED; + } + void Drain(HttpNetworkSession* session) override { ADD_FAILURE(); } void PopulateNetErrorDetails(NetErrorDetails* details) override { return; } @@ -14644,6 +14651,12 @@ class FakeWebSocketBasicHandshakeStream : public WebSocketHandshakeStreamBase { bool GetRemoteEndpoint(IPEndPoint* endpoint) override { return false; } + Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) override { + ADD_FAILURE(); + return ERR_NOT_IMPLEMENTED; + } + void Drain(HttpNetworkSession* session) override { NOTREACHED(); } void PopulateNetErrorDetails(NetErrorDetails* details) override { return; } @@ -15802,4 +15815,41 @@ TEST_P(HttpNetworkTransactionTest, DisableNPN) { EXPECT_TRUE(trans.server_ssl_config_.npn_protos.empty()); } +#if !defined(OS_IOS) +TEST_P(HttpNetworkTransactionTest, TokenBindingSpdy) { + const std::string https_url = "https://www.example.com"; + HttpRequestInfo request; + request.url = GURL(https_url); + request.method = "GET"; + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.token_binding_negotiated = true; + ssl.token_binding_key_param = TB_PARAM_ECDSAP256; + ssl.SetNextProto(GetProtocol()); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl); + + scoped_ptr<SpdyFrame> resp( + spdy_util_.ConstructSpdyGetSynReply(nullptr, 0, 1)); + scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = {CreateMockRead(*resp), CreateMockRead(*body), + MockRead(ASYNC, ERR_IO_PENDING)}; + StaticSocketDataProvider data(reads, arraysize(reads), nullptr, 0); + session_deps_.socket_factory->AddSocketDataProvider(&data); + session_deps_.channel_id_service.reset(new ChannelIDService( + new DefaultChannelIDStore(nullptr), base::ThreadTaskRunnerHandle::Get())); + scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_)); + + HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get()); + TestCompletionCallback callback; + EXPECT_EQ(ERR_IO_PENDING, + trans.Start(&request, callback.callback(), BoundNetLog())); + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_TRUE(trans.GetResponseInfo()->was_fetched_via_spdy); + HttpRequestHeaders headers; + ASSERT_TRUE(trans.GetFullRequestHeaders(&headers)); + EXPECT_TRUE(headers.HasHeader(HttpRequestHeaders::kTokenBinding)); +} +#endif // !defined(OS_IOS) + } // namespace net diff --git a/net/http/http_request_headers.cc b/net/http/http_request_headers.cc index d3f1401..4e2ae0e 100644 --- a/net/http/http_request_headers.cc +++ b/net/http/http_request_headers.cc @@ -36,8 +36,9 @@ const char HttpRequestHeaders::kProxyAuthorization[] = "Proxy-Authorization"; const char HttpRequestHeaders::kProxyConnection[] = "Proxy-Connection"; const char HttpRequestHeaders::kRange[] = "Range"; const char HttpRequestHeaders::kReferer[] = "Referer"; -const char HttpRequestHeaders::kUserAgent[] = "User-Agent"; const char HttpRequestHeaders::kTransferEncoding[] = "Transfer-Encoding"; +const char HttpRequestHeaders::kTokenBinding[] = "Token-Binding"; +const char HttpRequestHeaders::kUserAgent[] = "User-Agent"; HttpRequestHeaders::HeaderKeyValuePair::HeaderKeyValuePair() { } diff --git a/net/http/http_request_headers.h b/net/http/http_request_headers.h index 76bf25e..db70004 100644 --- a/net/http/http_request_headers.h +++ b/net/http/http_request_headers.h @@ -76,8 +76,9 @@ class NET_EXPORT HttpRequestHeaders { static const char kProxyConnection[]; static const char kRange[]; static const char kReferer[]; - static const char kUserAgent[]; static const char kTransferEncoding[]; + static const char kTokenBinding[]; + static const char kUserAgent[]; HttpRequestHeaders(); ~HttpRequestHeaders(); diff --git a/net/http/http_response_body_drainer_unittest.cc b/net/http/http_response_body_drainer_unittest.cc index 4568cba..9ee6f84 100644 --- a/net/http/http_response_body_drainer_unittest.cc +++ b/net/http/http_response_body_drainer_unittest.cc @@ -107,6 +107,11 @@ class MockHttpStream : public HttpStream { void GetSSLInfo(SSLInfo* ssl_info) override {} void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override {} bool GetRemoteEndpoint(IPEndPoint* endpoint) override { return false; } + Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) override { + ADD_FAILURE(); + return ERR_NOT_IMPLEMENTED; + } // Mocked API int ReadResponseBody(IOBuffer* buf, diff --git a/net/http/http_stream.h b/net/http/http_stream.h index b7e7888..096dd46 100644 --- a/net/http/http_stream.h +++ b/net/http/http_stream.h @@ -13,14 +13,21 @@ #include <stdint.h> +#include <vector> + #include "base/macros.h" #include "base/memory/scoped_ptr.h" #include "net/base/completion_callback.h" #include "net/base/net_error_details.h" +#include "net/base/net_errors.h" #include "net/base/net_export.h" #include "net/base/request_priority.h" #include "net/base/upload_progress.h" +namespace crypto { +class ECPrivateKey; +} + namespace net { class BoundNetLog; @@ -151,6 +158,11 @@ class NET_EXPORT_PRIVATE HttpStream { // and does not modify |endpoint| if it is unavailable. virtual bool GetRemoteEndpoint(IPEndPoint* endpoint) = 0; + // Signs the EKM value for Token Binding from the TLS layer using |*key| and + // puts the result in |*out|. Returns OK or ERR_FAILED. + virtual Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) = 0; + // In the case of an HTTP error or redirect, flush the response body (usually // a simple error or "this page has moved") so that we can re-use the // underlying connection. This stream is responsible for deleting itself when diff --git a/net/http/http_stream_factory_impl_unittest.cc b/net/http/http_stream_factory_impl_unittest.cc index 32c20e3..0d89cf1 100644 --- a/net/http/http_stream_factory_impl_unittest.cc +++ b/net/http/http_stream_factory_impl_unittest.cc @@ -103,6 +103,11 @@ class MockWebSocketHandshakeStream : public WebSocketHandshakeStreamBase { void GetSSLInfo(SSLInfo* ssl_info) override {} void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override {} bool GetRemoteEndpoint(IPEndPoint* endpoint) override { return false; } + Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) override { + ADD_FAILURE(); + return ERR_NOT_IMPLEMENTED; + } void Drain(HttpNetworkSession* session) override {} void PopulateNetErrorDetails(NetErrorDetails* details) override { return; } void SetPriority(RequestPriority priority) override {} diff --git a/net/http/http_stream_parser.cc b/net/http/http_stream_parser.cc index e4a4207..bc7746d 100644 --- a/net/http/http_stream_parser.cc +++ b/net/http/http_stream_parser.cc @@ -24,6 +24,7 @@ #include "net/http/http_util.h" #include "net/socket/client_socket_handle.h" #include "net/socket/ssl_client_socket.h" +#include "net/ssl/token_binding.h" namespace net { @@ -1100,6 +1101,17 @@ void HttpStreamParser::GetSSLCertRequestInfo( } } +Error HttpStreamParser::GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) { + if (!request_->url.SchemeIsCryptographic() || !connection_->socket()) { + NOTREACHED(); + return ERR_FAILED; + } + SSLClientSocket* ssl_socket = + static_cast<SSLClientSocket*>(connection_->socket()); + return ssl_socket->GetSignedEKMForTokenBinding(key, out); +} + int HttpStreamParser::EncodeChunk(const base::StringPiece& payload, char* output, size_t output_size) { diff --git a/net/http/http_stream_parser.h b/net/http/http_stream_parser.h index d51cb91..b8bb23a 100644 --- a/net/http/http_stream_parser.h +++ b/net/http/http_stream_parser.h @@ -15,7 +15,9 @@ #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/strings/string_piece.h" +#include "crypto/ec_private_key.h" #include "net/base/completion_callback.h" +#include "net/base/net_errors.h" #include "net/base/net_export.h" #include "net/base/upload_progress.h" #include "net/log/net_log.h" @@ -95,6 +97,9 @@ class NET_EXPORT_PRIVATE HttpStreamParser { void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info); + Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out); + // Encodes the given |payload| in the chunked format to |output|. // Returns the number of bytes written to |output|. |output_size| should // be large enough to store the encoded chunk, which is payload.size() + diff --git a/net/http/proxy_connect_redirect_http_stream.cc b/net/http/proxy_connect_redirect_http_stream.cc index 2b26b08..45d7424 100644 --- a/net/http/proxy_connect_redirect_http_stream.cc +++ b/net/http/proxy_connect_redirect_http_stream.cc @@ -102,6 +102,13 @@ bool ProxyConnectRedirectHttpStream::GetRemoteEndpoint(IPEndPoint* endpoint) { return false; } +Error ProxyConnectRedirectHttpStream::GetSignedEKMForTokenBinding( + crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) { + NOTREACHED(); + return ERR_NOT_IMPLEMENTED; +} + void ProxyConnectRedirectHttpStream::Drain(HttpNetworkSession* session) { NOTREACHED(); } diff --git a/net/http/proxy_connect_redirect_http_stream.h b/net/http/proxy_connect_redirect_http_stream.h index af3940c..36e595c 100644 --- a/net/http/proxy_connect_redirect_http_stream.h +++ b/net/http/proxy_connect_redirect_http_stream.h @@ -57,6 +57,8 @@ class ProxyConnectRedirectHttpStream : public HttpStream { void GetSSLInfo(SSLInfo* ssl_info) override; void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override; bool GetRemoteEndpoint(IPEndPoint* endpoint) override; + Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) override; void Drain(HttpNetworkSession* session) override; void PopulateNetErrorDetails(NetErrorDetails* details) override; diff --git a/net/log/net_log_event_type_list.h b/net/log/net_log_event_type_list.h index e323327..23ca1bd 100644 --- a/net/log/net_log_event_type_list.h +++ b/net/log/net_log_event_type_list.h @@ -1115,6 +1115,9 @@ EVENT_TYPE(HTTP_TRANSACTION_READ_BODY) // restarting for authentication, on keep alive connections. EVENT_TYPE(HTTP_TRANSACTION_DRAIN_BODY_FOR_AUTH_RESTART) +// Measures the time taken to look up the key used for Token Binding. +EVENT_TYPE(HTTP_TRANSACTION_GET_TOKEN_BINDING_KEY) + // This event is sent when we try to restart a transaction after an error. // The following parameters are attached: // { diff --git a/net/net.gypi b/net/net.gypi index ef6a1b4..c34ae2b 100644 --- a/net/net.gypi +++ b/net/net.gypi @@ -1184,6 +1184,9 @@ 'ssl/ssl_platform_key_win.cc', 'ssl/threaded_ssl_private_key.cc', 'ssl/threaded_ssl_private_key.h', + 'ssl/token_binding.h', + 'ssl/token_binding_nss.cc', + 'ssl/token_binding_openssl.cc', 'third_party/mozilla_security_manager/nsKeygenHandler.cpp', 'third_party/mozilla_security_manager/nsKeygenHandler.h', 'third_party/mozilla_security_manager/nsNSSCertificateDB.cpp', diff --git a/net/net_common.gypi b/net/net_common.gypi index b08beb8..6d7f943 100644 --- a/net/net_common.gypi +++ b/net/net_common.gypi @@ -136,6 +136,7 @@ 'socket/ssl_client_socket_nss.h', 'socket/ssl_server_socket_nss.cc', 'socket/ssl_server_socket_nss.h', + 'ssl/token_binding_nss.cc', ], 'dependencies': [ '../third_party/boringssl/boringssl.gyp:boringssl', @@ -189,6 +190,7 @@ 'ssl/ssl_platform_key_task_runner.h', 'ssl/threaded_ssl_private_key.cc', 'ssl/threaded_ssl_private_key.h', + 'ssl/token_binding_openssl.cc', ], }, ], diff --git a/net/quic/quic_http_stream.cc b/net/quic/quic_http_stream.cc index 81b57f8..7cd89ce 100644 --- a/net/quic/quic_http_stream.cc +++ b/net/quic/quic_http_stream.cc @@ -281,6 +281,12 @@ bool QuicHttpStream::GetRemoteEndpoint(IPEndPoint* endpoint) { return true; } +Error QuicHttpStream::GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) { + NOTREACHED(); + return ERR_NOT_IMPLEMENTED; +} + void QuicHttpStream::Drain(HttpNetworkSession* session) { NOTREACHED(); Close(false); diff --git a/net/quic/quic_http_stream.h b/net/quic/quic_http_stream.h index 7abdf8b..0b23ea5 100644 --- a/net/quic/quic_http_stream.h +++ b/net/quic/quic_http_stream.h @@ -61,6 +61,8 @@ class NET_EXPORT_PRIVATE QuicHttpStream void GetSSLInfo(SSLInfo* ssl_info) override; void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override; bool GetRemoteEndpoint(IPEndPoint* endpoint) override; + Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) override; void Drain(HttpNetworkSession* session) override; void PopulateNetErrorDetails(NetErrorDetails* details) override; void SetPriority(RequestPriority priority) override; diff --git a/net/socket/socket_test_util.cc b/net/socket/socket_test_util.cc index 04f5151..095ebe8 100644 --- a/net/socket/socket_test_util.cc +++ b/net/socket/socket_test_util.cc @@ -287,7 +287,8 @@ SSLSocketDataProvider::SSLSocketDataProvider(IoMode mode, int result) client_cert_sent(false), cert_request_info(NULL), channel_id_sent(false), - connection_status(0) { + connection_status(0), + token_binding_negotiated(false) { SSLConnectionStatusSetVersion(SSL_CONNECTION_VERSION_TLS1_2, &connection_status); // Set to TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 @@ -826,6 +827,12 @@ ChannelIDService* MockClientSocket::GetChannelIDService() const { return NULL; } +Error MockClientSocket::GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) { + NOTREACHED(); + return ERR_NOT_IMPLEMENTED; +} + SSLFailureState MockClientSocket::GetSSLFailureState() const { return IsConnected() ? SSL_FAILURE_NONE : SSL_FAILURE_UNKNOWN; } @@ -1199,6 +1206,8 @@ bool MockSSLClientSocket::GetSSLInfo(SSLInfo* ssl_info) { ssl_info->client_cert_sent = data_->client_cert_sent; ssl_info->channel_id_sent = data_->channel_id_sent; ssl_info->connection_status = data_->connection_status; + ssl_info->token_binding_negotiated = data_->token_binding_negotiated; + ssl_info->token_binding_key_param = data_->token_binding_key_param; return true; } @@ -1224,6 +1233,13 @@ ChannelIDService* MockSSLClientSocket::GetChannelIDService() const { return data_->channel_id_service; } +Error MockSSLClientSocket::GetSignedEKMForTokenBinding( + crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) { + out->push_back('A'); + return OK; +} + void MockSSLClientSocket::OnReadComplete(const MockRead& data) { NOTIMPLEMENTED(); } diff --git a/net/socket/socket_test_util.h b/net/socket/socket_test_util.h index a3c6df4..90f7fda 100644 --- a/net/socket/socket_test_util.h +++ b/net/socket/socket_test_util.h @@ -361,6 +361,8 @@ struct SSLSocketDataProvider { bool channel_id_sent; ChannelIDService* channel_id_service; int connection_status; + bool token_binding_negotiated; + TokenBindingParam token_binding_key_param; }; // Uses the sequence_number field in the mock reads and writes to @@ -577,6 +579,8 @@ class MockClientSocket : public SSLClientSocket { int GetTLSUniqueChannelBinding(std::string* out) override; NextProtoStatus GetNextProto(std::string* proto) const override; ChannelIDService* GetChannelIDService() const override; + Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) override; SSLFailureState GetSSLFailureState() const override; protected: @@ -691,6 +695,8 @@ class MockSSLClientSocket : public MockClientSocket, public AsyncSocket { // SSLClientSocket implementation. void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override; + Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) override; NextProtoStatus GetNextProto(std::string* proto) const override; // This MockSocket does not implement the manual async IO feature. diff --git a/net/socket/ssl_client_socket.h b/net/socket/ssl_client_socket.h index 3a6aa94..9f6551a 100644 --- a/net/socket/ssl_client_socket.h +++ b/net/socket/ssl_client_socket.h @@ -22,6 +22,10 @@ class FilePath; class SequencedTaskRunner; } +namespace crypto { +class ECPrivateKey; +} + namespace net { class CTPolicyEnforcer; @@ -144,6 +148,11 @@ class NET_EXPORT SSLClientSocket : public SSLSocket { // channel ids are not supported. virtual ChannelIDService* GetChannelIDService() const = 0; + // Signs the EKM value for Token Binding with |*key| and puts it in |*out|. + // Returns a net error code. + virtual Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) = 0; + // Returns the state of the handshake when it failed, or |SSL_FAILURE_NONE| if // the handshake succeeded. This is used to classify causes of the TLS version // fallback. diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc index b15d761..5619247 100644 --- a/net/socket/ssl_client_socket_nss.cc +++ b/net/socket/ssl_client_socket_nss.cc @@ -3179,6 +3179,13 @@ ChannelIDService* SSLClientSocketNSS::GetChannelIDService() const { return channel_id_service_; } +Error SSLClientSocketNSS::GetSignedEKMForTokenBinding( + crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) { + NOTREACHED(); + return ERR_NOT_IMPLEMENTED; +} + SSLFailureState SSLClientSocketNSS::GetSSLFailureState() const { if (completed_handshake_) return SSL_FAILURE_NONE; diff --git a/net/socket/ssl_client_socket_nss.h b/net/socket/ssl_client_socket_nss.h index 366df1c..d8a1549 100644 --- a/net/socket/ssl_client_socket_nss.h +++ b/net/socket/ssl_client_socket_nss.h @@ -99,6 +99,8 @@ class SSLClientSocketNSS : public SSLClientSocket { // SSLClientSocket implementation. ChannelIDService* GetChannelIDService() const override; + Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) override; SSLFailureState GetSSLFailureState() const override; private: diff --git a/net/socket/ssl_client_socket_openssl.cc b/net/socket/ssl_client_socket_openssl.cc index 13121b4..501eaefc 100644 --- a/net/socket/ssl_client_socket_openssl.cc +++ b/net/socket/ssl_client_socket_openssl.cc @@ -522,6 +522,7 @@ SSLClientSocketOpenSSL::SSLClientSocketOpenSSL( channel_id_service_(context.channel_id_service), tb_was_negotiated_(false), tb_negotiated_param_(TB_PARAM_ECDSAP256), + tb_signed_ekm_map_(10), ssl_(NULL), transport_bio_(NULL), transport_(std::move(transport_socket)), @@ -573,6 +574,47 @@ SSLClientSocketOpenSSL::GetChannelIDService() const { return channel_id_service_; } +Error SSLClientSocketOpenSSL::GetSignedEKMForTokenBinding( + crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) { + // The same key will be used across multiple requests to sign the same value, + // so the signature is cached. + std::string raw_public_key; + if (!key->ExportRawPublicKey(&raw_public_key)) + return ERR_FAILED; + SignedEkmMap::iterator it = tb_signed_ekm_map_.Get(raw_public_key); + if (it != tb_signed_ekm_map_.end()) { + *out = it->second; + return OK; + } + + uint8_t tb_ekm_buf[32]; + static const char kTokenBindingExporterLabel[] = "EXPORTER-Token-Binding"; + if (!SSL_export_keying_material(ssl_, tb_ekm_buf, sizeof(tb_ekm_buf), + kTokenBindingExporterLabel, + strlen(kTokenBindingExporterLabel), nullptr, + 0, false /* no context */)) { + return ERR_FAILED; + } + + size_t sig_len; + crypto::ScopedEVP_PKEY_CTX pctx(EVP_PKEY_CTX_new(key->key(), nullptr)); + if (!EVP_PKEY_sign_init(pctx.get()) || + !EVP_PKEY_sign(pctx.get(), nullptr, &sig_len, tb_ekm_buf, + sizeof(tb_ekm_buf))) { + return ERR_FAILED; + } + out->resize(sig_len); + if (!EVP_PKEY_sign(pctx.get(), out->data(), &sig_len, tb_ekm_buf, + sizeof(tb_ekm_buf))) { + return ERR_FAILED; + } + out->resize(sig_len); + + tb_signed_ekm_map_.Put(raw_public_key, *out); + return OK; +} + SSLFailureState SSLClientSocketOpenSSL::GetSSLFailureState() const { return ssl_failure_state_; } diff --git a/net/socket/ssl_client_socket_openssl.h b/net/socket/ssl_client_socket_openssl.h index 178daeb..6e464d7 100644 --- a/net/socket/ssl_client_socket_openssl.h +++ b/net/socket/ssl_client_socket_openssl.h @@ -13,6 +13,8 @@ #include <string> #include <vector> +#include "base/compiler_specific.h" +#include "base/containers/mru_cache.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" @@ -42,6 +44,8 @@ class CTVerifier; class SSLCertRequestInfo; class SSLInfo; +using SignedEkmMap = base::MRUCache<std::string, std::vector<uint8_t>>; + // An SSL client socket implemented with OpenSSL. class SSLClientSocketOpenSSL : public SSLClientSocket { public: @@ -72,6 +76,8 @@ class SSLClientSocketOpenSSL : public SSLClientSocket { void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override; NextProtoStatus GetNextProto(std::string* proto) const override; ChannelIDService* GetChannelIDService() const override; + Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) override; SSLFailureState GetSSLFailureState() const override; // SSLSocket implementation. @@ -301,6 +307,7 @@ class SSLClientSocketOpenSSL : public SSLClientSocket { ChannelIDService* channel_id_service_; bool tb_was_negotiated_; TokenBindingParam tb_negotiated_param_; + SignedEkmMap tb_signed_ekm_map_; // OpenSSL stuff SSL* ssl_; diff --git a/net/spdy/spdy_http_stream.cc b/net/spdy/spdy_http_stream.cc index 16e19b2..c6d0a37 100644 --- a/net/spdy/spdy_http_stream.cc +++ b/net/spdy/spdy_http_stream.cc @@ -570,6 +570,11 @@ bool SpdyHttpStream::GetRemoteEndpoint(IPEndPoint* endpoint) { return spdy_session_->GetPeerAddress(endpoint) == OK; } +Error SpdyHttpStream::GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) { + return spdy_session_->GetSignedEKMForTokenBinding(key, out); +} + void SpdyHttpStream::Drain(HttpNetworkSession* session) { NOTREACHED(); Close(false); diff --git a/net/spdy/spdy_http_stream.h b/net/spdy/spdy_http_stream.h index a79569a..76130d8 100644 --- a/net/spdy/spdy_http_stream.h +++ b/net/spdy/spdy_http_stream.h @@ -79,6 +79,8 @@ class NET_EXPORT_PRIVATE SpdyHttpStream : public SpdyStream::Delegate, void GetSSLInfo(SSLInfo* ssl_info) override; void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override; bool GetRemoteEndpoint(IPEndPoint* endpoint) override; + Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) override; void Drain(HttpNetworkSession* session) override; void PopulateNetErrorDetails(NetErrorDetails* details) override; void SetPriority(RequestPriority priority) override; diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc index 97b5837..1855231 100644 --- a/net/spdy/spdy_session.cc +++ b/net/spdy/spdy_session.cc @@ -2044,6 +2044,17 @@ bool SpdySession::GetSSLInfo(SSLInfo* ssl_info, return connection_->socket()->GetSSLInfo(ssl_info); } +Error SpdySession::GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) { + if (!is_secure_) { + NOTREACHED(); + return ERR_FAILED; + } + SSLClientSocket* ssl_socket = + static_cast<SSLClientSocket*>(connection_->socket()); + return ssl_socket->GetSignedEKMForTokenBinding(key, out); +} + void SpdySession::OnError(SpdyFramer::SpdyError error_code) { CHECK(in_io_loop_); diff --git a/net/spdy/spdy_session.h b/net/spdy/spdy_session.h index efab1b8..491766e 100644 --- a/net/spdy/spdy_session.h +++ b/net/spdy/spdy_session.h @@ -357,6 +357,11 @@ class NET_EXPORT SpdySession : public BufferedSpdyFramerVisitorInterface, bool* was_npn_negotiated, NextProto* protocol_negotiated); + // Signs the EKM value for Token Binding from the TLS layer using |*key| and + // puts the result in |*out|. Returns OK or ERR_FAILED. + Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out); + // Send a WINDOW_UPDATE frame for a stream. Called by a stream // whenever receive window size is increased. void SendStreamWindowUpdate(SpdyStreamId stream_id, diff --git a/net/spdy/spdy_test_util_common.cc b/net/spdy/spdy_test_util_common.cc index 6a28794..f7022f5 100644 --- a/net/spdy/spdy_test_util_common.cc +++ b/net/spdy/spdy_test_util_common.cc @@ -337,6 +337,7 @@ crypto::ECSignatureCreator* MockECSignatureCreatorFactory::Create( SpdySessionDependencies::SpdySessionDependencies(NextProto protocol) : host_resolver(new MockCachingHostResolver), cert_verifier(new MockCertVerifier), + channel_id_service(nullptr), transport_security_state(new TransportSecurityState), proxy_service(ProxyService::CreateDirect()), ssl_config_service(new SSLConfigServiceDefaults), @@ -372,6 +373,7 @@ SpdySessionDependencies::SpdySessionDependencies( scoped_ptr<ProxyService> proxy_service) : host_resolver(new MockHostResolver), cert_verifier(new MockCertVerifier), + channel_id_service(nullptr), transport_security_state(new TransportSecurityState), proxy_service(std::move(proxy_service)), ssl_config_service(new SSLConfigServiceDefaults), @@ -416,6 +418,7 @@ HttpNetworkSession::Params SpdySessionDependencies::CreateSessionParams( HttpNetworkSession::Params params; params.host_resolver = session_deps->host_resolver.get(); params.cert_verifier = session_deps->cert_verifier.get(); + params.channel_id_service = session_deps->channel_id_service.get(); params.transport_security_state = session_deps->transport_security_state.get(); params.proxy_service = session_deps->proxy_service.get(); diff --git a/net/spdy/spdy_test_util_common.h b/net/spdy/spdy_test_util_common.h index 9ef8df5..ebde051 100644 --- a/net/spdy/spdy_test_util_common.h +++ b/net/spdy/spdy_test_util_common.h @@ -188,6 +188,7 @@ struct SpdySessionDependencies { // NOTE: host_resolver must be ordered before http_auth_handler_factory. scoped_ptr<MockHostResolverBase> host_resolver; scoped_ptr<CertVerifier> cert_verifier; + scoped_ptr<ChannelIDService> channel_id_service; scoped_ptr<TransportSecurityState> transport_security_state; scoped_ptr<ProxyService> proxy_service; scoped_refptr<SSLConfigService> ssl_config_service; diff --git a/net/ssl/token_binding.h b/net/ssl/token_binding.h new file mode 100644 index 0000000..5ae04c5 --- /dev/null +++ b/net/ssl/token_binding.h @@ -0,0 +1,94 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_SSL_TOKEN_BINDING_H_ +#define NET_SSL_TOKEN_BINDING_H_ + +#include <string> +#include <vector> + +#include "base/strings/string_piece.h" +#include "crypto/ec_private_key.h" +#include "net/base/net_errors.h" +#include "net/base/net_export.h" + +namespace net { + +// Given a vector of serialized TokenBinding structs (as defined in +// draft-ietf-tokbind-protocol-02), this function combines them to form the +// serialized TokenBindingMessage struct in |*out|. This function returns a net +// error. +// +// struct { +// TokenBinding tokenbindings<0..2^16-1>; +// } TokenBindingMessage; +Error BuildTokenBindingMessageFromTokenBindings( + const std::vector<base::StringPiece>& token_bindings, + std::string* out); + +// Builds a TokenBinding struct with a provided TokenBindingID created from +// |*key| and a signature of |ekm| using |*key| to sign. +// +// enum { +// rsa2048_pkcs1.5(0), rsa2048_pss(1), ecdsap256(2), (255) +// } TokenBindingKeyParameters; +// +// struct { +// opaque modulus<1..2^16-1>; +// opaque publicexponent<1..2^8-1>; +// } RSAPublicKey; +// +// struct { +// opaque point <1..2^8-1>; +// } ECPoint; +// +// enum { +// provided_token_binding(0), referred_token_binding(1), (255) +// } TokenBindingType; +// +// struct { +// TokenBindingType tokenbinding_type; +// TokenBindingKeyParameters key_parameters; +// select (key_parameters) { +// case rsa2048_pkcs1.5: +// case rsa2048_pss: +// RSAPublicKey rsapubkey; +// case ecdsap256: +// ECPoint point; +// } +// } TokenBindingID; +// +// struct { +// TokenBindingID tokenbindingid; +// opaque signature<0..2^16-1>;// Signature over the exported keying +// // material value +// Extension extensions<0..2^16-1>; +// } TokenBinding; +Error BuildProvidedTokenBinding(crypto::ECPrivateKey* key, + const std::vector<uint8_t>& ekm, + std::string* out); + +// Given a TokenBindingMessage, parses the first TokenBinding from it, +// extracts the ECPoint of the TokenBindingID into |*ec_point|, and extracts the +// signature of the EKM value into |*signature|. It also verifies that the first +// TokenBinding is a provided Token Binding, and that the key parameters is +// ecdsap256. This function returns whether the message was able to be parsed +// successfully. +NET_EXPORT_PRIVATE bool ParseTokenBindingMessage( + base::StringPiece token_binding_message, + base::StringPiece* ec_point, + base::StringPiece* signature); + +// Takes an ECPoint |ec_point| from a TokenBindingID and |signature| from a +// TokenBinding and verifies that |signature| is the signature of |ekm| using +// |ec_point| as the public key. Returns true if the signature verifies and +// false if it doesn't or some other error occurs in verification. This function +// is only provided for testing. +NET_EXPORT_PRIVATE bool VerifyEKMSignature(base::StringPiece ec_point, + base::StringPiece signature, + base::StringPiece ekm); + +} // namespace net + +#endif // NET_SSL_TOKEN_BINDING_H_ diff --git a/net/ssl/token_binding_nss.cc b/net/ssl/token_binding_nss.cc new file mode 100644 index 0000000..bb18498 --- /dev/null +++ b/net/ssl/token_binding_nss.cc @@ -0,0 +1,39 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "token_binding.h" + +#include "net/base/net_errors.h" + +namespace net { + +Error BuildTokenBindingMessageFromTokenBindings( + const std::vector<base::StringPiece>& token_bindings, + std::string* out) { + NOTREACHED(); + return ERR_NOT_IMPLEMENTED; +} + +Error BuildProvidedTokenBinding(crypto::ECPrivateKey* key, + const std::vector<uint8_t>& ekm, + std::string* out) { + NOTREACHED(); + return ERR_NOT_IMPLEMENTED; +} + +bool ParseTokenBindingMessage(base::StringPiece token_binding_message, + base::StringPiece* ec_point, + base::StringPiece* signature) { + NOTREACHED(); + return false; +} + +bool VerifyEKMSignature(base::StringPiece ec_point, + base::StringPiece signature, + base::StringPiece ekm) { + NOTREACHED(); + return false; +} + +} // namespace net diff --git a/net/ssl/token_binding_openssl.cc b/net/ssl/token_binding_openssl.cc new file mode 100644 index 0000000..0cfc3fe --- /dev/null +++ b/net/ssl/token_binding_openssl.cc @@ -0,0 +1,147 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/ssl/token_binding.h" + +#include <openssl/bytestring.h> +#include <openssl/ec.h> +#include <openssl/evp.h> +#include <openssl/mem.h> + +#include "base/stl_util.h" +#include "crypto/scoped_openssl_types.h" +#include "net/base/net_errors.h" +#include "net/ssl/ssl_config.h" + +namespace net { + +namespace { + +enum TokenBindingType { + TB_TYPE_PROVIDED = 0, + TB_TYPE_REFERRED = 1, +}; + +bool BuildTokenBindingID(TokenBindingType type, + crypto::ECPrivateKey* key, + CBB* out) { + CBB ec_point; + if (!CBB_add_u8(out, type) || !CBB_add_u8(out, TB_PARAM_ECDSAP256) || + !CBB_add_u8_length_prefixed(out, &ec_point)) { + return false; + } + + EVP_PKEY* pkey = key->key(); + static const int kExpectedKeyLength = 65; + uint8_t* buf; + // TODO(nharper): Replace i2o_ECPublicKey with EC_POINT_point2cbb. + if (pkey->type != EVP_PKEY_EC || + i2o_ECPublicKey(pkey->pkey.ec, nullptr) != kExpectedKeyLength || + !CBB_add_space(&ec_point, &buf, kExpectedKeyLength) || + i2o_ECPublicKey(pkey->pkey.ec, &buf) != kExpectedKeyLength || + !CBB_flush(out)) { + return false; + } + return true; +} + +} // namespace + +Error BuildTokenBindingMessageFromTokenBindings( + const std::vector<base::StringPiece>& token_bindings, + std::string* out) { + CBB tb_message, child; + if (!CBB_init(&tb_message, 0) || + !CBB_add_u16_length_prefixed(&tb_message, &child)) { + CBB_cleanup(&tb_message); + return ERR_FAILED; + } + for (const base::StringPiece& token_binding : token_bindings) { + if (!CBB_add_bytes(&child, + reinterpret_cast<const uint8_t*>(token_binding.data()), + token_binding.size())) { + CBB_cleanup(&tb_message); + return ERR_FAILED; + } + } + + uint8_t* out_data; + size_t out_len; + if (!CBB_finish(&tb_message, &out_data, &out_len)) { + CBB_cleanup(&tb_message); + return ERR_FAILED; + } + out->assign(reinterpret_cast<char*>(out_data), out_len); + OPENSSL_free(out_data); + return OK; +} + +Error BuildProvidedTokenBinding(crypto::ECPrivateKey* key, + const std::vector<uint8_t>& signed_ekm, + std::string* out) { + uint8_t* out_data; + size_t out_len; + CBB token_binding; + if (!CBB_init(&token_binding, 0) || + !BuildTokenBindingID(TB_TYPE_PROVIDED, key, &token_binding) || + !CBB_add_u16(&token_binding, signed_ekm.size()) || + !CBB_add_bytes(&token_binding, signed_ekm.data(), signed_ekm.size()) || + // 0-length extensions + !CBB_add_u16(&token_binding, 0) || + !CBB_finish(&token_binding, &out_data, &out_len)) { + CBB_cleanup(&token_binding); + return ERR_FAILED; + } + out->assign(reinterpret_cast<char*>(out_data), out_len); + OPENSSL_free(out_data); + return OK; +} + +bool ParseTokenBindingMessage(base::StringPiece token_binding_message, + base::StringPiece* ec_point_out, + base::StringPiece* signature_out) { + CBS tb_message, tb, ec_point, signature; + uint8_t tb_type, tb_param; + CBS_init(&tb_message, + reinterpret_cast<const uint8_t*>(token_binding_message.data()), + token_binding_message.size()); + if (!CBS_get_u16_length_prefixed(&tb_message, &tb) || + !CBS_get_u8(&tb, &tb_type) || !CBS_get_u8(&tb, &tb_param) || + !CBS_get_u8_length_prefixed(&tb, &ec_point) || + !CBS_get_u16_length_prefixed(&tb, &signature) || + tb_type != TB_TYPE_PROVIDED || tb_param != TB_PARAM_ECDSAP256) { + return false; + } + + *ec_point_out = base::StringPiece( + reinterpret_cast<const char*>(CBS_data(&ec_point)), CBS_len(&ec_point)); + *signature_out = base::StringPiece( + reinterpret_cast<const char*>(CBS_data(&signature)), CBS_len(&signature)); + return true; +} + +bool VerifyEKMSignature(base::StringPiece ec_point, + base::StringPiece signature, + base::StringPiece ekm) { + crypto::ScopedEC_Key key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); + EC_KEY* keyp = key.get(); + const uint8_t* ec_point_data = + reinterpret_cast<const uint8_t*>(ec_point.data()); + if (o2i_ECPublicKey(&keyp, &ec_point_data, ec_point.size()) != key.get()) + return false; + crypto::ScopedEVP_PKEY pkey(EVP_PKEY_new()); + if (!EVP_PKEY_assign_EC_KEY(pkey.get(), key.release())) + return false; + crypto::ScopedEVP_PKEY_CTX pctx(EVP_PKEY_CTX_new(pkey.get(), nullptr)); + if (!EVP_PKEY_verify_init(pctx.get()) || + !EVP_PKEY_verify( + pctx.get(), reinterpret_cast<const uint8_t*>(signature.data()), + signature.size(), reinterpret_cast<const uint8_t*>(ekm.data()), + ekm.size())) { + return false; + } + return true; +} + +} // namespace net diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py index 1531765..fe57a54 100755 --- a/net/tools/testserver/testserver.py +++ b/net/tools/testserver/testserver.py @@ -340,6 +340,7 @@ class TestPageHandler(testserver_base.BasePageHandler): self.GetSSLSessionCacheHandler, self.SSLManySmallRecords, self.GetChannelID, + self.GetTokenBindingEKM, self.GetClientCert, self.ClientCipherListHandler, self.CloseSocketHandler, @@ -1516,6 +1517,21 @@ class TestPageHandler(testserver_base.BasePageHandler): self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64')) return True + def GetTokenBindingEKM(self): + """Send a reply containing the EKM value for token binding from the TLS + layer.""" + + if not self._ShouldHandleRequest('/tokbind-ekm'): + return False + + ekm = self.server.tlsConnection.exportKeyingMaterial( + "EXPORTER-Token-Binding", "", False, 32) + self.send_response(200) + self.send_header('Content-Type', 'application/octet-stream') + self.end_headers() + self.wfile.write(ekm) + return True + def GetClientCert(self): """Send a reply whether a client certificate was provided.""" diff --git a/net/url_request/url_request_http_job_unittest.cc b/net/url_request/url_request_http_job_unittest.cc index 5958487..dddfd8d 100644 --- a/net/url_request/url_request_http_job_unittest.cc +++ b/net/url_request/url_request_http_job_unittest.cc @@ -809,6 +809,12 @@ class FakeWebSocketHandshakeStream : public WebSocketHandshakeStreamBase { bool GetRemoteEndpoint(IPEndPoint* endpoint) override { return false; } + Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) override { + ADD_FAILURE(); + return ERR_NOT_IMPLEMENTED; + } + void Drain(HttpNetworkSession* session) override {} void PopulateNetErrorDetails(NetErrorDetails* details) override { return; } diff --git a/net/url_request/url_request_test_util.cc b/net/url_request/url_request_test_util.cc index b3c03c7..60f0f36 100644 --- a/net/url_request/url_request_test_util.cc +++ b/net/url_request/url_request_test_util.cc @@ -111,6 +111,7 @@ void TestURLRequestContext::Init() { params.network_delegate = network_delegate(); params.http_server_properties = http_server_properties(); params.net_log = net_log(); + params.channel_id_service = channel_id_service(); context_storage_.set_http_network_session( make_scoped_ptr(new HttpNetworkSession(params))); context_storage_.set_http_transaction_factory(make_scoped_ptr( diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc index d0e5d25..78e4eb3 100644 --- a/net/url_request/url_request_unittest.cc +++ b/net/url_request/url_request_unittest.cc @@ -16,6 +16,7 @@ #include <algorithm> #include <limits> +#include "base/base64url.h" #include "base/bind.h" #include "base/compiler_specific.h" #include "base/files/file_path.h" @@ -79,9 +80,12 @@ #include "net/log/test_net_log_util.h" #include "net/proxy/proxy_service.h" #include "net/socket/ssl_client_socket.h" +#include "net/ssl/channel_id_service.h" +#include "net/ssl/default_channel_id_store.h" #include "net/ssl/ssl_cipher_suite_names.h" #include "net/ssl/ssl_connection_status_flags.h" #include "net/ssl/ssl_server_config.h" +#include "net/ssl/token_binding.h" #include "net/test/cert_test_util.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "net/test/embedded_test_server/http_request.h" @@ -3370,7 +3374,113 @@ scoped_ptr<test_server::HttpResponse> HandleRedirectConnect( } // namespace -// In this unit test, we're using the EmbeddedTestServer as a proxy server and +class TestSSLConfigService : public SSLConfigService { + public: + TestSSLConfigService(bool ev_enabled, + bool online_rev_checking, + bool rev_checking_required_local_anchors, + bool token_binding_enabled) + : ev_enabled_(ev_enabled), + online_rev_checking_(online_rev_checking), + rev_checking_required_local_anchors_( + rev_checking_required_local_anchors), + token_binding_enabled_(token_binding_enabled), + min_version_(kDefaultSSLVersionMin), + fallback_min_version_(kDefaultSSLVersionFallbackMin) {} + + void set_min_version(uint16_t version) { min_version_ = version; } + + void set_fallback_min_version(uint16_t version) { + fallback_min_version_ = version; + } + + // SSLConfigService: + void GetSSLConfig(SSLConfig* config) override { + *config = SSLConfig(); + config->rev_checking_enabled = online_rev_checking_; + config->verify_ev_cert = ev_enabled_; + config->rev_checking_required_local_anchors = + rev_checking_required_local_anchors_; + if (fallback_min_version_) { + config->version_fallback_min = fallback_min_version_; + } + if (min_version_) { + config->version_min = min_version_; + } + if (token_binding_enabled_) { + config->token_binding_params.push_back(TB_PARAM_ECDSAP256); + } + } + + protected: + ~TestSSLConfigService() override {} + + private: + const bool ev_enabled_; + const bool online_rev_checking_; + const bool rev_checking_required_local_anchors_; + const bool token_binding_enabled_; + uint16_t min_version_; + uint16_t fallback_min_version_; +}; + +// TODO(svaldez): Update tests to use EmbeddedTestServer. +#if !defined(OS_IOS) +class TokenBindingURLRequestTest : public URLRequestTestHTTP { + public: + void SetUp() override { + default_context_.set_ssl_config_service( + new TestSSLConfigService(false, false, false, true)); + channel_id_service_.reset(new ChannelIDService( + new DefaultChannelIDStore(NULL), base::ThreadTaskRunnerHandle::Get())); + default_context_.set_channel_id_service(channel_id_service_.get()); + URLRequestTestHTTP::SetUp(); + } + + protected: + scoped_ptr<ChannelIDService> channel_id_service_; +}; + +TEST_F(TokenBindingURLRequestTest, TokenBindingTest) { + SpawnedTestServer::SSLOptions ssl_options; + ssl_options.supported_token_binding_params.push_back(TB_PARAM_ECDSAP256); + SpawnedTestServer https_test_server(SpawnedTestServer::TYPE_HTTPS, + ssl_options, + base::FilePath(kTestFilePath)); + ASSERT_TRUE(https_test_server.Start()); + + TestDelegate d; + { + scoped_ptr<URLRequest> r(default_context_.CreateRequest( + https_test_server.GetURL("tokbind-ekm"), DEFAULT_PRIORITY, &d)); + r->Start(); + EXPECT_TRUE(r->is_pending()); + + base::RunLoop().Run(); + + EXPECT_EQ(URLRequestStatus::SUCCESS, r->status().status()); + + HttpRequestHeaders headers; + std::string token_binding_header, token_binding_message; + EXPECT_TRUE(r->GetFullRequestHeaders(&headers)); + EXPECT_TRUE(headers.GetHeader(HttpRequestHeaders::kTokenBinding, + &token_binding_header)); + EXPECT_TRUE(base::Base64UrlDecode( + token_binding_header, base::Base64UrlDecodePolicy::REQUIRE_PADDING, + &token_binding_message)); + base::StringPiece ec_point, signature; + EXPECT_TRUE( + ParseTokenBindingMessage(token_binding_message, &ec_point, &signature)); + + EXPECT_GT(d.bytes_received(), 0); + std::string ekm = d.data_received(); + + EXPECT_TRUE(VerifyEKMSignature(ec_point, signature, ekm)); + } +} +#endif // !defined(OS_IOS) + +// In this unit test, we're using the HTTPTestServer as a proxy server and // issuing a CONNECT request with the magic host name "www.redirect.com". // The EmbeddedTestServer will return a 302 response, which we should not // follow. @@ -8528,61 +8638,17 @@ TEST_F(HTTPSRequestTest, DisableECDSAOnXP) { #endif // OS_WIN -class TestSSLConfigService : public SSLConfigService { - public: - TestSSLConfigService(bool ev_enabled, - bool online_rev_checking, - bool rev_checking_required_local_anchors) - : ev_enabled_(ev_enabled), - online_rev_checking_(online_rev_checking), - rev_checking_required_local_anchors_( - rev_checking_required_local_anchors), - min_version_(kDefaultSSLVersionMin), - fallback_min_version_(kDefaultSSLVersionFallbackMin) {} - - void set_min_version(uint16_t version) { min_version_ = version; } - - void set_fallback_min_version(uint16_t version) { - fallback_min_version_ = version; - } - - // SSLConfigService: - void GetSSLConfig(SSLConfig* config) override { - *config = SSLConfig(); - config->rev_checking_enabled = online_rev_checking_; - config->verify_ev_cert = ev_enabled_; - config->rev_checking_required_local_anchors = - rev_checking_required_local_anchors_; - if (fallback_min_version_) { - config->version_fallback_min = fallback_min_version_; - } - if (min_version_) { - config->version_min = min_version_; - } - } - - protected: - ~TestSSLConfigService() override {} - - private: - const bool ev_enabled_; - const bool online_rev_checking_; - const bool rev_checking_required_local_anchors_; - uint16_t min_version_; - uint16_t fallback_min_version_; -}; - class FallbackTestURLRequestContext : public TestURLRequestContext { public: explicit FallbackTestURLRequestContext(bool delay_initialization) : TestURLRequestContext(delay_initialization) {} void set_fallback_min_version(uint16_t version) { - TestSSLConfigService *ssl_config_service = - new TestSSLConfigService(true /* check for EV */, - false /* online revocation checking */, - false /* require rev. checking for local - anchors */); + TestSSLConfigService* ssl_config_service = new TestSSLConfigService( + true /* check for EV */, false /* online revocation checking */, + false /* require rev. checking for local + anchors */, + false /* token binding enabled */); ssl_config_service->set_fallback_min_version(version); set_ssl_config_service(ssl_config_service); } @@ -8957,11 +9023,11 @@ class HTTPSOCSPTest : public HTTPSRequestTest { // connetions to testserver. This can be overridden in test subclasses for // different behaviour. virtual void SetupContext(URLRequestContext* context) { - context->set_ssl_config_service( - new TestSSLConfigService(true /* check for EV */, - true /* online revocation checking */, - false /* require rev. checking for local - anchors */)); + context->set_ssl_config_service(new TestSSLConfigService( + true /* check for EV */, true /* online revocation checking */, + false /* require rev. checking for local + anchors */, + false /* token binding enabled */)); } scoped_ptr<ScopedTestRoot> test_root_; @@ -9152,11 +9218,11 @@ TEST_F(HTTPSOCSPTest, MAYBE_RevokedStapled) { class HTTPSHardFailTest : public HTTPSOCSPTest { protected: void SetupContext(URLRequestContext* context) override { - context->set_ssl_config_service( - new TestSSLConfigService(false /* check for EV */, - false /* online revocation checking */, - true /* require rev. checking for local - anchors */)); + context->set_ssl_config_service(new TestSSLConfigService( + false /* check for EV */, false /* online revocation checking */, + true /* require rev. checking for local + anchors */, + false /* token binding enabled */)); } }; @@ -9189,11 +9255,11 @@ TEST_F(HTTPSHardFailTest, FailsOnOCSPInvalid) { class HTTPSEVCRLSetTest : public HTTPSOCSPTest { protected: void SetupContext(URLRequestContext* context) override { - context->set_ssl_config_service( - new TestSSLConfigService(true /* check for EV */, - false /* online revocation checking */, - false /* require rev. checking for local - anchors */)); + context->set_ssl_config_service(new TestSSLConfigService( + true /* check for EV */, false /* online revocation checking */, + false /* require rev. checking for local + anchors */, + false /* token binding enabled */)); } }; @@ -9374,11 +9440,11 @@ TEST_F(HTTPSEVCRLSetTest, ExpiredCRLSetAndRevokedNonEVCert) { class HTTPSCRLSetTest : public HTTPSOCSPTest { protected: void SetupContext(URLRequestContext* context) override { - context->set_ssl_config_service( - new TestSSLConfigService(false /* check for EV */, - false /* online revocation checking */, - false /* require rev. checking for local - anchors */)); + context->set_ssl_config_service(new TestSSLConfigService( + false /* check for EV */, false /* online revocation checking */, + false /* require rev. checking for local + anchors */, + false /* token binding enabled */)); } }; diff --git a/net/websockets/websocket_basic_handshake_stream.cc b/net/websockets/websocket_basic_handshake_stream.cc index e26bb99..aee3367 100644 --- a/net/websockets/websocket_basic_handshake_stream.cc +++ b/net/websockets/websocket_basic_handshake_stream.cc @@ -444,6 +444,13 @@ void WebSocketBasicHandshakeStream::PopulateNetErrorDetails( return; } +Error WebSocketBasicHandshakeStream::GetSignedEKMForTokenBinding( + crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) { + NOTREACHED(); + return ERR_NOT_IMPLEMENTED; +} + void WebSocketBasicHandshakeStream::Drain(HttpNetworkSession* session) { HttpResponseBodyDrainer* drainer = new HttpResponseBodyDrainer(this); drainer->Start(session); diff --git a/net/websockets/websocket_basic_handshake_stream.h b/net/websockets/websocket_basic_handshake_stream.h index d84ef22..d390982 100644 --- a/net/websockets/websocket_basic_handshake_stream.h +++ b/net/websockets/websocket_basic_handshake_stream.h @@ -64,6 +64,8 @@ class NET_EXPORT_PRIVATE WebSocketBasicHandshakeStream void GetSSLInfo(SSLInfo* ssl_info) override; void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override; bool GetRemoteEndpoint(IPEndPoint* endpoint) override; + Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key, + std::vector<uint8_t>* out) override; void Drain(HttpNetworkSession* session) override; void SetPriority(RequestPriority priority) override; void PopulateNetErrorDetails(NetErrorDetails* details) override; diff --git a/third_party/tlslite/README.chromium b/third_party/tlslite/README.chromium index e995e95..3bc992d 100644 --- a/third_party/tlslite/README.chromium +++ b/third_party/tlslite/README.chromium @@ -45,3 +45,7 @@ Local Modifications: binding negotiation TLS extension (draft-ietf-tokbind-negotiation-00) - patches/disable_channel_id.patch: Add flag to HandshakeSettings to allow for disabling channel id. +- patches/exported_keying_material.patch: Add method to Session to get + exported keying material (RFC 5705) for use in e.g. Token Binding. +- patches/token_binding_resumption.patch: Fix token binding negotiation + extension to work on session resumption. diff --git a/third_party/tlslite/patches/exported_keying_material.patch b/third_party/tlslite/patches/exported_keying_material.patch new file mode 100644 index 0000000..9d4ed9c --- /dev/null +++ b/third_party/tlslite/patches/exported_keying_material.patch @@ -0,0 +1,56 @@ +diff --git a/third_party/tlslite/tlslite/tlsconnection.py b/third_party/tlslite/tlslite/tlsconnection.py +index 7363a30..e42b362 100644 +--- a/third_party/tlslite/tlslite/tlsconnection.py ++++ b/third_party/tlslite/tlslite/tlsconnection.py +@@ -181,6 +181,8 @@ class TLSConnection(TLSRecordLayer): + @type sock: L{socket.socket} + """ + TLSRecordLayer.__init__(self, sock) ++ self.clientRandom = b"" ++ self.serverRandom = b"" + + #********************************************************* + # Client Handshake Functions +@@ -606,6 +608,9 @@ class TLSConnection(TLSRecordLayer): + else: break + masterSecret = result + ++ self.clientRandom = clientHello.random ++ self.serverRandom = serverHello.random ++ + # Create the session object which is used for resumptions + self.session = Session() + self.session.create(masterSecret, serverHello.session_id, cipherSuite, +@@ -1398,6 +1403,9 @@ class TLSConnection(TLSRecordLayer): + else: break + masterSecret = result + ++ self.clientRandom = clientHello.random ++ self.serverRandom = serverHello.random ++ + #Create the session object + self.session = Session() + if cipherSuite in CipherSuite.certAllSuites: +@@ -2013,3 +2025,22 @@ class TLSConnection(TLSRecordLayer): + except: + self._shutdown(False) + raise ++ ++ ++ def exportKeyingMaterial(self, label, context, use_context, length): ++ """Returns the exported keying material as defined in RFC 5705.""" ++ ++ seed = self.clientRandom + self.serverRandom ++ if use_context: ++ if len(context) > 65535: ++ raise ValueError("Context is too long") ++ seed += bytearray(2) ++ seed[len(seed) - 2] = len(context) >> 8 ++ seed[len(seed) - 1] = len(context) & 0xFF ++ seed += context ++ if self.version in ((3,1), (3,2)): ++ return PRF(self.session.masterSecret, label, seed, length) ++ elif self.version == (3,3): ++ return PRF_1_2(self.session.masterSecret, label, seed, length) ++ else: ++ raise AssertionError() diff --git a/third_party/tlslite/patches/token_binding_resumption.patch b/third_party/tlslite/patches/token_binding_resumption.patch new file mode 100644 index 0000000..5d856b2 --- /dev/null +++ b/third_party/tlslite/patches/token_binding_resumption.patch @@ -0,0 +1,15 @@ +diff --git a/third_party/tlslite/tlslite/tlsconnection.py b/third_party/tlslite/tlslite/tlsconnection.py +index 6a53282..6e26fdd 100644 +--- a/third_party/tlslite/tlslite/tlsconnection.py ++++ b/third_party/tlslite/tlslite/tlsconnection.py +@@ -1536,6 +1536,10 @@ class TLSConnection(TLSRecordLayer): + serverHello.extended_master_secret = \ + clientHello.extended_master_secret and \ + settings.enableExtendedMasterSecret ++ for param in clientHello.tb_client_params: ++ if param in settings.supportedTokenBindingParams: ++ serverHello.tb_params = param ++ break + for result in self._sendMsg(serverHello): + yield result + diff --git a/third_party/tlslite/tlslite/tlsconnection.py b/third_party/tlslite/tlslite/tlsconnection.py index 7363a30..e42b362 100644 --- a/third_party/tlslite/tlslite/tlsconnection.py +++ b/third_party/tlslite/tlslite/tlsconnection.py @@ -181,6 +181,8 @@ class TLSConnection(TLSRecordLayer): @type sock: L{socket.socket} """ TLSRecordLayer.__init__(self, sock) + self.clientRandom = b"" + self.serverRandom = b"" #********************************************************* # Client Handshake Functions @@ -606,6 +608,9 @@ class TLSConnection(TLSRecordLayer): else: break masterSecret = result + self.clientRandom = clientHello.random + self.serverRandom = serverHello.random + # Create the session object which is used for resumptions self.session = Session() self.session.create(masterSecret, serverHello.session_id, cipherSuite, @@ -1398,6 +1403,9 @@ class TLSConnection(TLSRecordLayer): else: break masterSecret = result + self.clientRandom = clientHello.random + self.serverRandom = serverHello.random + #Create the session object self.session = Session() if cipherSuite in CipherSuite.certAllSuites: @@ -1536,6 +1544,10 @@ class TLSConnection(TLSRecordLayer): serverHello.extended_master_secret = \ clientHello.extended_master_secret and \ settings.enableExtendedMasterSecret + for param in clientHello.tb_client_params: + if param in settings.supportedTokenBindingParams: + serverHello.tb_params = param + break for result in self._sendMsg(serverHello): yield result @@ -2013,3 +2025,22 @@ class TLSConnection(TLSRecordLayer): except: self._shutdown(False) raise + + + def exportKeyingMaterial(self, label, context, use_context, length): + """Returns the exported keying material as defined in RFC 5705.""" + + seed = self.clientRandom + self.serverRandom + if use_context: + if len(context) > 65535: + raise ValueError("Context is too long") + seed += bytearray(2) + seed[len(seed) - 2] = len(context) >> 8 + seed[len(seed) - 1] = len(context) & 0xFF + seed += context + if self.version in ((3,1), (3,2)): + return PRF(self.session.masterSecret, label, seed, length) + elif self.version == (3,3): + return PRF_1_2(self.session.masterSecret, label, seed, length) + else: + raise AssertionError() diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index 7b7ef1d..e6c783a 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -27626,6 +27626,15 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. </summary> </histogram> +<histogram name="Net.TokenBinding.Support" enum="TokenBinding.Support"> + <owner>nharper@chromium.org</owner> + <summary> + The number of secure HTTP requests broken down by support for Token Binding, + indicating if Token Binding was negotiated and supported by both client and + server, or why it wasn't if not. + </summary> +</histogram> + <histogram name="Net.Transaction_Bandwidth" units="KB/s"> <obsolete> Discontinued as of 4/12/09 @@ -79806,6 +79815,13 @@ To add a new entry, add it with any value and run test to compute valid value. <int value="1" label="Renegotiation patched"/> </enum> +<enum name="TokenBinding.Support" type="int"> + <int value="0" label="DISABLED"/> + <int value="1" label="CLIENT_ONLY"/> + <int value="2" label="CLIENT_AND_SERVER"/> + <int value="3" label="CLIENT_NO_CHANNEL_ID_SERVICE"/> +</enum> + <enum name="TouchEventsState" type="int"> <int value="0" label="Enabled"/> <int value="1" label="Automatic - enabled"/> |