diff options
author | ekasper@google.com <ekasper@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-28 13:43:26 +0000 |
---|---|---|
committer | ekasper@google.com <ekasper@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-28 13:43:26 +0000 |
commit | 4e72ee50e8f859f1b7dc9a8904d41462c107277e (patch) | |
tree | 3f36a9f06c3f7b01407de9bea103f8c6442d159c /net | |
parent | 560e2f57c9f16b8c54a4fba8dc241a228dd6f049 (diff) | |
download | chromium_src-4e72ee50e8f859f1b7dc9a8904d41462c107277e.zip chromium_src-4e72ee50e8f859f1b7dc9a8904d41462c107277e.tar.gz chromium_src-4e72ee50e8f859f1b7dc9a8904d41462c107277e.tar.bz2 |
Add support for fetching Certificate Transparency SCTs over a TLS extension
BUG=309578
Review URL: https://codereview.chromium.org/83333003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@237775 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/socket/ssl_client_socket.cc | 12 | ||||
-rw-r--r-- | net/socket/ssl_client_socket.h | 11 | ||||
-rw-r--r-- | net/socket/ssl_client_socket_nss.cc | 29 | ||||
-rw-r--r-- | net/socket/ssl_client_socket_unittest.cc | 99 | ||||
-rw-r--r-- | net/ssl/ssl_config_service.cc | 1 | ||||
-rw-r--r-- | net/ssl/ssl_config_service.h | 3 | ||||
-rw-r--r-- | net/test/spawned_test_server/base_test_server.cc | 6 | ||||
-rw-r--r-- | net/test/spawned_test_server/base_test_server.h | 9 | ||||
-rw-r--r-- | net/third_party/nss/ssl/exports_win.def | 1 | ||||
-rwxr-xr-x | net/tools/testserver/testserver.py | 18 |
10 files changed, 184 insertions, 5 deletions
diff --git a/net/socket/ssl_client_socket.cc b/net/socket/ssl_client_socket.cc index a849488..588b701 100644 --- a/net/socket/ssl_client_socket.cc +++ b/net/socket/ssl_client_socket.cc @@ -16,7 +16,8 @@ SSLClientSocket::SSLClientSocket() : was_npn_negotiated_(false), was_spdy_negotiated_(false), protocol_negotiated_(kProtoUnknown), - channel_id_sent_(false) { + channel_id_sent_(false), + signed_cert_timestamps_received_(false) { } // static @@ -144,6 +145,15 @@ void SSLClientSocket::set_channel_id_sent(bool channel_id_sent) { channel_id_sent_ = channel_id_sent; } +bool SSLClientSocket::WereSignedCertTimestampsReceived() const { + return signed_cert_timestamps_received_; +} + +void SSLClientSocket::set_signed_cert_timestamps_received( + bool signed_cert_timestamps_received) { + signed_cert_timestamps_received_ = signed_cert_timestamps_received; +} + // static void SSLClientSocket::RecordChannelIDSupport( ServerBoundCertService* server_bound_cert_service, diff --git a/net/socket/ssl_client_socket.h b/net/socket/ssl_client_socket.h index 9f8532a..a88c893 100644 --- a/net/socket/ssl_client_socket.h +++ b/net/socket/ssl_client_socket.h @@ -126,9 +126,18 @@ class NET_EXPORT SSLClientSocket : public SSLSocket { // Public for ssl_client_socket_openssl_unittest.cc. virtual bool WasChannelIDSent() const; + // Returns true if the server sent Certificate Transparency SCTs + // via a TLS extension. + // Temporary glue for testing while the CT code hasn't landed. + // TODO(ekasper): expose received SCTs via SSLInfo instead. + virtual bool WereSignedCertTimestampsReceived() const; + protected: virtual void set_channel_id_sent(bool channel_id_sent); + virtual void set_signed_cert_timestamps_received( + bool signed_cert_timestamps_received); + // Records histograms for channel id support during full handshakes - resumed // handshakes are ignored. static void RecordChannelIDSupport( @@ -151,6 +160,8 @@ class NET_EXPORT SSLClientSocket : public SSLSocket { NextProto protocol_negotiated_; // True if a channel ID was sent. bool channel_id_sent_; + // True if SCTs were received via a TLS extension. + bool signed_cert_timestamps_received_; }; } // namespace net diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc index 89eab14..9f2f10a 100644 --- a/net/socket/ssl_client_socket_nss.cc +++ b/net/socket/ssl_client_socket_nss.cc @@ -414,6 +414,7 @@ struct HandshakeState { channel_id_sent = false; server_cert_chain.Reset(NULL); server_cert = NULL; + sct_list_from_tls_extension.clear(); resumed_handshake = false; ssl_connection_status = 0; } @@ -443,6 +444,8 @@ struct HandshakeState { // always be non-NULL. PeerCertificateChain server_cert_chain; scoped_refptr<X509Certificate> server_cert; + // SignedCertificateTimestampList received via TLS extension (RFC 6962). + std::string sct_list_from_tls_extension; // True if the current handshake was the result of TLS session resumption. bool resumed_handshake; @@ -754,6 +757,10 @@ class SSLClientSocketNSS::Core : public base::RefCountedThreadSafe<Core> { // Updates the NSS and platform specific certificates. void UpdateServerCert(); + // Update the nss_handshake_state_ with SignedCertificateTimestampLists + // received in the handshake, via a TLS extension or (to be implemented) + // OCSP stapling. + void UpdateSignedCertTimestamps(); // Updates the nss_handshake_state_ with the negotiated security parameters. void UpdateConnectionStatus(); // Record histograms for channel id support during full handshakes - resumed @@ -1652,6 +1659,7 @@ void SSLClientSocketNSS::Core::HandshakeSucceeded() { RecordChannelIDSupportOnNSSTaskRunner(); UpdateServerCert(); + UpdateSignedCertTimestamps(); UpdateConnectionStatus(); UpdateNextProto(); @@ -2413,6 +2421,18 @@ void SSLClientSocketNSS::Core::UpdateServerCert() { } } +void SSLClientSocketNSS::Core::UpdateSignedCertTimestamps() { + const SECItem* signed_cert_timestamps = + SSL_PeerSignedCertTimestamps(nss_fd_); + + if (!signed_cert_timestamps || !signed_cert_timestamps->len) + return; + + nss_handshake_state_.sct_list_from_tls_extension = std::string( + reinterpret_cast<char*>(signed_cert_timestamps->data), + signed_cert_timestamps->len); +} + void SSLClientSocketNSS::Core::UpdateConnectionStatus() { SSLChannelInfo channel_info; SECStatus ok = SSL_GetChannelInfo(nss_fd_, @@ -3175,6 +3195,13 @@ int SSLClientSocketNSS::InitializeSSLOptions() { } #endif + rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SIGNED_CERT_TIMESTAMPS, + ssl_config_.signed_cert_timestamps_enabled); + if (rv != SECSuccess) { + LogFailedNSSFunction(net_log_, "SSL_OptionSet", + "SSL_ENABLE_SIGNED_CERT_TIMESTAMPS"); + } + // Chromium patch to libssl #ifdef SSL_ENABLE_CACHED_INFO rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_CACHED_INFO, @@ -3320,6 +3347,8 @@ int SSLClientSocketNSS::DoHandshakeComplete(int result) { // Done! } set_channel_id_sent(core_->state().channel_id_sent); + set_signed_cert_timestamps_received( + !core_->state().sct_list_from_tls_extension.empty()); LeaveFunction(result); return result; diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc index f791928..0e667c6 100644 --- a/net/socket/ssl_client_socket_unittest.cc +++ b/net/socket/ssl_client_socket_unittest.cc @@ -1793,6 +1793,105 @@ TEST_F(SSLClientSocketCertRequestInfoTest, TwoAuthorities) { request_info->cert_authorities[1]); } +TEST_F(SSLClientSocketTest, ConnectSignedCertTimestampsEnabled) { + SpawnedTestServer::SSLOptions ssl_options; + ssl_options.signed_cert_timestamps = "test"; + + SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS, + ssl_options, + base::FilePath()); + ASSERT_TRUE(test_server.Start()); + + AddressList addr; + ASSERT_TRUE(test_server.GetAddressList(&addr)); + + TestCompletionCallback callback; + CapturingNetLog log; + scoped_ptr<StreamSocket> transport( + new TCPClientSocket(addr, &log, NetLog::Source())); + int rv = transport->Connect(callback.callback()); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + SSLConfig ssl_config; + ssl_config.signed_cert_timestamps_enabled = true; + + scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket( + transport.Pass(), test_server.host_port_pair(), ssl_config)); + + EXPECT_FALSE(sock->IsConnected()); + + rv = sock->Connect(callback.callback()); + + CapturingNetLog::CapturedEntryList entries; + log.GetEntries(&entries); + EXPECT_TRUE(LogContainsBeginEvent(entries, 5, NetLog::TYPE_SSL_CONNECT)); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(sock->IsConnected()); + log.GetEntries(&entries); + EXPECT_TRUE(LogContainsSSLConnectEndEvent(entries, -1)); + +#if !defined(USE_OPENSSL) + EXPECT_TRUE(sock->WereSignedCertTimestampsReceived()); +#else + // Enabling CT for OpenSSL is currently a noop. + EXPECT_FALSE(sock->WereSignedCertTimestampsReceived()); +#endif + + sock->Disconnect(); + EXPECT_FALSE(sock->IsConnected()); +} + +TEST_F(SSLClientSocketTest, ConnectSignedCertTimestampsDisabled) { + SpawnedTestServer::SSLOptions ssl_options; + ssl_options.signed_cert_timestamps = "test"; + + SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS, + ssl_options, + base::FilePath()); + ASSERT_TRUE(test_server.Start()); + + AddressList addr; + ASSERT_TRUE(test_server.GetAddressList(&addr)); + + TestCompletionCallback callback; + CapturingNetLog log; + scoped_ptr<StreamSocket> transport( + new TCPClientSocket(addr, &log, NetLog::Source())); + int rv = transport->Connect(callback.callback()); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + SSLConfig ssl_config; + ssl_config.signed_cert_timestamps_enabled = false; + + scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket( + transport.Pass(), test_server.host_port_pair(), ssl_config)); + + EXPECT_FALSE(sock->IsConnected()); + + rv = sock->Connect(callback.callback()); + + CapturingNetLog::CapturedEntryList entries; + log.GetEntries(&entries); + EXPECT_TRUE(LogContainsBeginEvent(entries, 5, NetLog::TYPE_SSL_CONNECT)); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(sock->IsConnected()); + log.GetEntries(&entries); + EXPECT_TRUE(LogContainsSSLConnectEndEvent(entries, -1)); + + EXPECT_FALSE(sock->WereSignedCertTimestampsReceived()); + + sock->Disconnect(); + EXPECT_FALSE(sock->IsConnected()); +} + } // namespace } // namespace net diff --git a/net/ssl/ssl_config_service.cc b/net/ssl/ssl_config_service.cc index 893506f..a28c46d 100644 --- a/net/ssl/ssl_config_service.cc +++ b/net/ssl/ssl_config_service.cc @@ -43,6 +43,7 @@ SSLConfig::SSLConfig() cached_info_enabled(false), channel_id_enabled(true), false_start_enabled(true), + signed_cert_timestamps_enabled(true), require_forward_secrecy(false), unrestricted_ssl3_fallback_enabled(false), send_client_cert(false), diff --git a/net/ssl/ssl_config_service.h b/net/ssl/ssl_config_service.h index efd09ef..0b19e30 100644 --- a/net/ssl/ssl_config_service.h +++ b/net/ssl/ssl_config_service.h @@ -99,6 +99,9 @@ struct NET_EXPORT SSLConfig { bool cached_info_enabled; // True if TLS cached info extension is enabled. bool channel_id_enabled; // True if TLS channel ID extension is enabled. bool false_start_enabled; // True if we'll use TLS False Start. + // True if the Certificate Transparency signed_certificate_timestamp + // TLS extension is enabled. + bool signed_cert_timestamps_enabled; // require_forward_secrecy, if true, causes only (EC)DHE cipher suites to be // enabled. NOTE: this only applies to server sockets currently, although diff --git a/net/test/spawned_test_server/base_test_server.cc b/net/test/spawned_test_server/base_test_server.cc index b8697d4..775341b 100644 --- a/net/test/spawned_test_server/base_test_server.cc +++ b/net/test/spawned_test_server/base_test_server.cc @@ -398,6 +398,12 @@ bool BaseTestServer::GenerateArguments(base::DictionaryValue* arguments) const { arguments->Set("tls-intolerant", new base::FundamentalValue(ssl_options_.tls_intolerant)); } + if (!ssl_options_.signed_cert_timestamps.empty()) { + std::string b64_scts; + if (!base::Base64Encode(ssl_options_.signed_cert_timestamps, &b64_scts)) + return false; + arguments->SetString("signed-cert-timestamps", b64_scts); + } } return GenerateAdditionalArguments(arguments); diff --git a/net/test/spawned_test_server/base_test_server.h b/net/test/spawned_test_server/base_test_server.h index ff395c5..9d5cda8 100644 --- a/net/test/spawned_test_server/base_test_server.h +++ b/net/test/spawned_test_server/base_test_server.h @@ -147,6 +147,14 @@ class BaseTestServer { // If not TLS_INTOLERANT_NONE, the server will abort any handshake that // negotiates an intolerant TLS version in order to test version fallback. TLSIntolerantLevel tls_intolerant; + + // (Fake) SignedCertificateTimestampList (as a raw binary string) to send in + // a TLS extension. + // Temporary glue for testing: validation of SCTs is application-controlled + // and can be appropriately mocked out, so sending fake data here does not + // affect handshaking behaviour. + // TODO(ekasper): replace with valid SCT files for test certs. + std::string signed_cert_timestamps; }; // Pass as the 'host' parameter during construction to server on 127.0.0.1 @@ -260,4 +268,3 @@ class BaseTestServer { } // namespace net #endif // NET_TEST_SPAWNED_TEST_SERVER_BASE_TEST_SERVER_H_ - diff --git a/net/third_party/nss/ssl/exports_win.def b/net/third_party/nss/ssl/exports_win.def index a9dc8eb..677ecfe 100644 --- a/net/third_party/nss/ssl/exports_win.def +++ b/net/third_party/nss/ssl/exports_win.def @@ -60,3 +60,4 @@ SSL_GetPlatformClientAuthDataHook SSL_HandshakeResumedSession SSL_RestartHandshakeAfterChannelIDReq SSL_GetChannelBinding +SSL_PeerSignedCertTimestamps diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py index f028497..83c14d6 100755 --- a/net/tools/testserver/testserver.py +++ b/net/tools/testserver/testserver.py @@ -128,7 +128,7 @@ class HTTPSServer(tlslite.api.TLSSocketServerMixIn, def __init__(self, server_address, request_hander_class, pem_cert_and_key, ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers, - record_resume_info, tls_intolerant): + record_resume_info, tls_intolerant, signed_cert_timestamps): self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key) # Force using only python implementation - otherwise behavior is different # depending on whether m2crypto Python module is present (error is thrown @@ -140,6 +140,7 @@ class HTTPSServer(tlslite.api.TLSSocketServerMixIn, self.ssl_client_auth = ssl_client_auth self.ssl_client_cas = [] self.tls_intolerant = tls_intolerant + self.signed_cert_timestamps = signed_cert_timestamps for ca_file in ssl_client_cas: s = open(ca_file).read() @@ -171,7 +172,9 @@ class HTTPSServer(tlslite.api.TLSSocketServerMixIn, reqCert=self.ssl_client_auth, settings=self.ssl_handshake_settings, reqCAs=self.ssl_client_cas, - tlsIntolerant=self.tls_intolerant) + tlsIntolerant=self.tls_intolerant, + signedCertTimestamps= + self.signed_cert_timestamps) tlsConnection.ignoreAbruptClose = True return True except tlslite.api.TLSAbruptCloseError: @@ -1935,7 +1938,9 @@ class ServerRunner(testserver_base.TestServerRunner): self.options.ssl_client_ca, self.options.ssl_bulk_cipher, self.options.record_resume, - self.options.tls_intolerant) + self.options.tls_intolerant, + self.options.signed_cert_timestamps.decode( + "base64")) print 'HTTPS server started on %s:%d...' % (host, server.server_port) else: server = HTTPServer((host, port), TestPageHandler) @@ -2073,6 +2078,13 @@ class ServerRunner(testserver_base.TestServerRunner): 'aborted. 2 means TLS 1.1 or higher will be ' 'aborted. 3 means TLS 1.2 or higher will be ' 'aborted.') + self.option_parser.add_option('--signed-cert-timestamps', + dest='signed_cert_timestamps', + default='', + help='Base64 encoded SCT list. If set, ' + 'server will respond with a ' + 'signed_certificate_timestamp TLS extension ' + 'whenever the client supports it.') self.option_parser.add_option('--https-record-resume', dest='record_resume', const=True, default=False, action='store_const', |