summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/extensions/api/socket/tls_socket_unittest.cc2
-rw-r--r--chrome/browser/io_thread.cc8
-rw-r--r--chrome/browser/io_thread.h1
-rw-r--r--chrome/common/chrome_switches.cc3
-rw-r--r--chrome/common/chrome_switches.h1
-rw-r--r--net/BUILD.gn2
-rw-r--r--net/http/http_basic_stream.cc5
-rw-r--r--net/http/http_basic_stream.h3
-rw-r--r--net/http/http_network_session.cc3
-rw-r--r--net/http/http_network_session.h2
-rw-r--r--net/http/http_network_transaction.cc113
-rw-r--r--net/http/http_network_transaction.h20
-rw-r--r--net/http/http_network_transaction_ssl_unittest.cc68
-rw-r--r--net/http/http_network_transaction_unittest.cc50
-rw-r--r--net/http/http_request_headers.cc3
-rw-r--r--net/http/http_request_headers.h3
-rw-r--r--net/http/http_response_body_drainer_unittest.cc5
-rw-r--r--net/http/http_stream.h12
-rw-r--r--net/http/http_stream_factory_impl_unittest.cc5
-rw-r--r--net/http/http_stream_parser.cc12
-rw-r--r--net/http/http_stream_parser.h5
-rw-r--r--net/http/proxy_connect_redirect_http_stream.cc7
-rw-r--r--net/http/proxy_connect_redirect_http_stream.h2
-rw-r--r--net/log/net_log_event_type_list.h3
-rw-r--r--net/net.gypi3
-rw-r--r--net/net_common.gypi2
-rw-r--r--net/quic/quic_http_stream.cc6
-rw-r--r--net/quic/quic_http_stream.h2
-rw-r--r--net/socket/socket_test_util.cc18
-rw-r--r--net/socket/socket_test_util.h6
-rw-r--r--net/socket/ssl_client_socket.h9
-rw-r--r--net/socket/ssl_client_socket_nss.cc7
-rw-r--r--net/socket/ssl_client_socket_nss.h2
-rw-r--r--net/socket/ssl_client_socket_openssl.cc42
-rw-r--r--net/socket/ssl_client_socket_openssl.h7
-rw-r--r--net/spdy/spdy_http_stream.cc5
-rw-r--r--net/spdy/spdy_http_stream.h2
-rw-r--r--net/spdy/spdy_session.cc11
-rw-r--r--net/spdy/spdy_session.h5
-rw-r--r--net/spdy/spdy_test_util_common.cc3
-rw-r--r--net/spdy/spdy_test_util_common.h1
-rw-r--r--net/ssl/token_binding.h94
-rw-r--r--net/ssl/token_binding_nss.cc39
-rw-r--r--net/ssl/token_binding_openssl.cc147
-rwxr-xr-xnet/tools/testserver/testserver.py16
-rw-r--r--net/url_request/url_request_http_job_unittest.cc6
-rw-r--r--net/url_request/url_request_test_util.cc1
-rw-r--r--net/url_request/url_request_unittest.cc206
-rw-r--r--net/websockets/websocket_basic_handshake_stream.cc7
-rw-r--r--net/websockets/websocket_basic_handshake_stream.h2
-rw-r--r--third_party/tlslite/README.chromium4
-rw-r--r--third_party/tlslite/patches/exported_keying_material.patch56
-rw-r--r--third_party/tlslite/patches/token_binding_resumption.patch15
-rw-r--r--third_party/tlslite/tlslite/tlsconnection.py31
-rw-r--r--tools/metrics/histograms/histograms.xml16
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(
&params->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"/>