diff options
author | rch@chromium.org <rch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-06 18:11:12 +0000 |
---|---|---|
committer | rch@chromium.org <rch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-06 18:11:12 +0000 |
commit | 4f40354683153162d6cb310aba339a7696f84e0f (patch) | |
tree | c89095d726ba576f0a29d1d72b2d08a341de151f | |
parent | 6909587866caaf1fc1ddb1ded8fcc69687ecefd1 (diff) | |
download | chromium_src-4f40354683153162d6cb310aba339a7696f84e0f.zip chromium_src-4f40354683153162d6cb310aba339a7696f84e0f.tar.gz chromium_src-4f40354683153162d6cb310aba339a7696f84e0f.tar.bz2 |
Implement TLS Channel ID support for SPDY CREDENTIAL frames
BUG=139700
Review URL: https://chromiumcodereview.appspot.com/10807088
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@150112 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | net/net.gyp | 3 | ||||
-rw-r--r-- | net/socket/socket_test_util.cc | 4 | ||||
-rw-r--r-- | net/socket/socket_test_util.h | 3 | ||||
-rw-r--r-- | net/spdy/spdy_credential_builder.cc | 84 | ||||
-rw-r--r-- | net/spdy/spdy_credential_builder.h | 38 | ||||
-rw-r--r-- | net/spdy/spdy_credential_builder_unittest.cc | 133 | ||||
-rw-r--r-- | net/spdy/spdy_http_stream_spdy3_unittest.cc | 26 | ||||
-rw-r--r-- | net/spdy/spdy_session.cc | 46 |
8 files changed, 283 insertions, 54 deletions
diff --git a/net/net.gyp b/net/net.gyp index fc277b8..b0e45886 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -676,6 +676,8 @@ 'spdy/buffered_spdy_framer.cc', 'spdy/buffered_spdy_framer.h', 'spdy/spdy_bitmasks.h', + 'spdy/spdy_credential_builder.cc', + 'spdy/spdy_credential_builder.h', 'spdy/spdy_credential_state.cc', 'spdy/spdy_credential_state.h', 'spdy/spdy_frame_builder.cc', @@ -1300,6 +1302,7 @@ 'socket_stream/socket_stream_unittest.cc', 'spdy/buffered_spdy_framer_spdy3_unittest.cc', 'spdy/buffered_spdy_framer_spdy2_unittest.cc', + 'spdy/spdy_credential_builder_unittest.cc', 'spdy/spdy_credential_state_unittest.cc', 'spdy/spdy_frame_reader_test.cc', 'spdy/spdy_framer_test.cc', diff --git a/net/socket/socket_test_util.cc b/net/socket/socket_test_util.cc index 5b6d05f..41ccd08 100644 --- a/net/socket/socket_test_util.cc +++ b/net/socket/socket_test_util.cc @@ -686,6 +686,8 @@ SSLClientSocket* MockClientSocketFactory::CreateSSLClientSocket( void MockClientSocketFactory::ClearSSLSessionCache() { } +const char MockClientSocket::kTlsUnique[] = "MOCK_TLSUNIQ"; + MockClientSocket::MockClientSocket(net::NetLog* net_log) : ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), connected_(false), @@ -746,7 +748,7 @@ int MockClientSocket::ExportKeyingMaterial(const base::StringPiece& label, } int MockClientSocket::GetTLSUniqueChannelBinding(std::string* out) { - out->assign("MOCK_TLSUNIQ"); + out->assign(MockClientSocket::kTlsUnique); return OK; } diff --git a/net/socket/socket_test_util.h b/net/socket/socket_test_util.h index 6cbd4d5..e0ce38e 100644 --- a/net/socket/socket_test_util.h +++ b/net/socket/socket_test_util.h @@ -589,6 +589,9 @@ class MockClientSocketFactory : public ClientSocketFactory { class MockClientSocket : public SSLClientSocket { public: + // Value returned by GetTLSUniqueChannelBinding(). + static const char kTlsUnique[]; + // TODO(ajwong): Why do we need net::NetLog? explicit MockClientSocket(net::NetLog* net_log); diff --git a/net/spdy/spdy_credential_builder.cc b/net/spdy/spdy_credential_builder.cc new file mode 100644 index 0000000..af04eff --- /dev/null +++ b/net/spdy/spdy_credential_builder.cc @@ -0,0 +1,84 @@ +// Copyright (c) 2012 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/spdy/spdy_credential_builder.h" + +#include "base/logging.h" +#include "base/string_piece.h" +#include "crypto/ec_private_key.h" +#include "crypto/ec_signature_creator.h" +#include "crypto/signature_creator.h" +#include "net/base/asn1_util.h" +#include "net/base/server_bound_cert_service.h" +#include "net/base/net_errors.h" +#include "net/socket/ssl_client_socket.h" +#include "net/spdy/spdy_framer.h" + +namespace net { + +namespace { + +std::vector<uint8> ToVector(base::StringPiece piece) { + return std::vector<uint8>(piece.data(), piece.data() + piece.length()); +} + +} // namespace + +// static +int SpdyCredentialBuilder::Build(std::string tls_unique, + SSLClientCertType type, + const std::string& key, + const std::string& cert, + size_t slot, + SpdyCredential* credential) { + if (type != CLIENT_CERT_ECDSA_SIGN) + return ERR_BAD_SSL_CLIENT_AUTH_CERT; + + std::string secret = SpdyCredentialBuilder::GetCredentialSecret(tls_unique); + + // Extract the SubjectPublicKeyInfo from the certificate. + base::StringPiece public_key_info; + if(!asn1::ExtractSPKIFromDERCert(cert, &public_key_info)) + return ERR_BAD_SSL_CLIENT_AUTH_CERT; + + // Next, extract the SubjectPublicKey data, which will actually + // be stored in the cert field of the credential frame. + base::StringPiece public_key; + if (!asn1::ExtractSubjectPublicKeyFromSPKI(public_key_info, &public_key)) + return ERR_BAD_SSL_CLIENT_AUTH_CERT; + // Drop one byte of padding bits count from the BIT STRING + // (this will always be zero). Drop one byte of X9.62 format specification + // (this will always be 4 to indicated an uncompressed point). + DCHECK_GT(public_key.length(), 2u); + DCHECK_EQ(0, static_cast<int>(public_key[0])); + DCHECK_EQ(4, static_cast<int>(public_key[1])); + public_key = public_key.substr(2, public_key.length()); + + // Convert the strings into a vector<unit8> + std::vector<uint8> proof_vector; + scoped_ptr<crypto::ECPrivateKey> private_key( + crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo( + ServerBoundCertService::kEPKIPassword, + ToVector(key), ToVector(public_key_info))); + scoped_ptr<crypto::ECSignatureCreator> creator( + crypto::ECSignatureCreator::Create(private_key.get())); + creator->Sign(reinterpret_cast<const unsigned char *>(secret.data()), + secret.length(), &proof_vector); + + credential->slot = slot; + credential->certs.push_back(public_key.as_string()); + credential->proof.assign(proof_vector.begin(), proof_vector.end()); + return OK; +} + +// static +std::string SpdyCredentialBuilder::GetCredentialSecret(std::string tls_unique) { + const char prefix[] = "SPDY CREDENTIAL ChannelID\0client -> server"; + std::string secret(prefix, arraysize(prefix)); + secret.append(tls_unique); + + return secret; +} + +} // namespace net diff --git a/net/spdy/spdy_credential_builder.h b/net/spdy/spdy_credential_builder.h new file mode 100644 index 0000000..c8893c1 --- /dev/null +++ b/net/spdy/spdy_credential_builder.h @@ -0,0 +1,38 @@ +// Copyright (c) 2012 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_SPDY_SPDY_CREDENTIAL_BUILDER_H_ +#define NET_SPDY_SPDY_CREDENTIAL_BUILDER_H_ + +#include <string> + +#include "net/base/net_export.h" +#include "net/base/ssl_client_cert_type.h" + +namespace net { + +class SSLClientSocket; +struct SpdyCredential; + +// This class provides facilities for building the various fields of +// SPDY CREDENTIAL frames. +class NET_EXPORT_PRIVATE SpdyCredentialBuilder { + public: + static int Build(std::string tls_unique, + SSLClientCertType type, + const std::string& key, + const std::string& cert, + size_t slot, + SpdyCredential* credential); + + private: + friend class SpdyCredentialBuilderTest; + + // Returns the secret data to be signed as part of a credential frame. + static std::string GetCredentialSecret(std::string tls_unique); +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_CREDENTIAL_BUILDER_H_ diff --git a/net/spdy/spdy_credential_builder_unittest.cc b/net/spdy/spdy_credential_builder_unittest.cc new file mode 100644 index 0000000..575ce88 --- /dev/null +++ b/net/spdy/spdy_credential_builder_unittest.cc @@ -0,0 +1,133 @@ +// Copyright (c) 2012 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/spdy/spdy_credential_builder.h" + +#include "base/threading/sequenced_worker_pool.h" +#include "crypto/ec_signature_creator.h" +#include "crypto/ec_private_key.h" +#include "net/base/asn1_util.h" +#include "net/base/default_server_bound_cert_store.h" +#include "net/base/server_bound_cert_service.h" +#include "net/spdy/spdy_test_util_spdy3.h" +#include "testing/platform_test.h" + +using namespace net::test_spdy3; + +namespace net { + +namespace { + +const static size_t kSlot = 2; +const static char kSecretPrefix[] = + "SPDY CREDENTIAL ChannelID\0client -> server"; + +void CreateCertAndKey(std::string* cert, std::string* key) { + // TODO(rch): Share this code with ServerBoundCertServiceTest. + scoped_refptr<base::SequencedWorkerPool> sequenced_worker_pool = + new base::SequencedWorkerPool(1, "CreateCertAndKey"); + scoped_ptr<ServerBoundCertService> server_bound_cert_service( + new ServerBoundCertService(new DefaultServerBoundCertStore(NULL), + sequenced_worker_pool)); + + TestCompletionCallback callback; + std::vector<uint8> requested_cert_types; + requested_cert_types.push_back(CLIENT_CERT_ECDSA_SIGN); + SSLClientCertType cert_type; + ServerBoundCertService::RequestHandle request_handle; + int rv = server_bound_cert_service->GetDomainBoundCert( + "https://www.google.com", requested_cert_types, &cert_type, key, cert, + callback.callback(), &request_handle); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, cert_type); + + sequenced_worker_pool->Shutdown(); +} + +} // namespace + +class SpdyCredentialBuilderTest : public testing::Test { + public: + SpdyCredentialBuilderTest() { + CreateCertAndKey(&cert_, &key_); + } + + protected: + int BuildWithType(SSLClientCertType type) { + return SpdyCredentialBuilder::Build( + MockClientSocket::kTlsUnique, type, key_, cert_, kSlot, &credential_); + } + + int Build() { + return BuildWithType(CLIENT_CERT_ECDSA_SIGN); + } + + std::string GetCredentialSecret() { + return SpdyCredentialBuilder::GetCredentialSecret( + MockClientSocket::kTlsUnique); + } + + SpdyTestStateHelper helper_; // Provides deterministic EC signatures. + std::string cert_; + std::string key_; + SpdyCredential credential_; +}; + +TEST_F(SpdyCredentialBuilderTest, GetCredentialSecret) { + std::string secret_str(kSecretPrefix, arraysize(kSecretPrefix)); + secret_str.append(MockClientSocket::kTlsUnique); + + EXPECT_EQ(secret_str, GetCredentialSecret()); +} + +TEST_F(SpdyCredentialBuilderTest, SucceedsWithECDSACert) { + EXPECT_EQ(OK, BuildWithType(CLIENT_CERT_ECDSA_SIGN)); +} + +TEST_F(SpdyCredentialBuilderTest, FailsWithRSACert) { + EXPECT_EQ(ERR_BAD_SSL_CLIENT_AUTH_CERT, + BuildWithType(CLIENT_CERT_RSA_SIGN)); +} + +TEST_F(SpdyCredentialBuilderTest, SetsSlotCorrectly) { + ASSERT_EQ(OK, Build()); + EXPECT_EQ(kSlot, credential_.slot); +} + +TEST_F(SpdyCredentialBuilderTest, SetsCertCorrectly) { + ASSERT_EQ(OK, Build()); + base::StringPiece spki; + ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(cert_, &spki)); + base::StringPiece spk; + ASSERT_TRUE(asn1::ExtractSubjectPublicKeyFromSPKI(spki, &spk)); + EXPECT_EQ(1u, credential_.certs.size()); + EXPECT_EQ(0, (int)spk[0]); + EXPECT_EQ(4, (int)spk[1]); + EXPECT_EQ(spk.substr(2, spk.length()).as_string(), credential_.certs[0]); +} + +TEST_F(SpdyCredentialBuilderTest, SetsProofCorrectly) { + ASSERT_EQ(OK, Build()); + base::StringPiece spki; + ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(cert_, &spki)); + std::vector<uint8> spki_data(spki.data(), + spki.data() + spki.size()); + std::vector<uint8> key_data(key_.data(), + key_.data() + key_.length()); + std::vector<uint8> proof_data; + scoped_ptr<crypto::ECPrivateKey> private_key( + crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo( + ServerBoundCertService::kEPKIPassword, key_data, spki_data)); + scoped_ptr<crypto::ECSignatureCreator> creator( + crypto::ECSignatureCreator::Create(private_key.get())); + std::string secret = GetCredentialSecret(); + creator->Sign(reinterpret_cast<const unsigned char *>(secret.data()), + secret.length(), &proof_data); + + std::string proof(proof_data.begin(), proof_data.end()); + EXPECT_EQ(proof, credential_.proof); +} + +} // namespace net diff --git a/net/spdy/spdy_http_stream_spdy3_unittest.cc b/net/spdy/spdy_http_stream_spdy3_unittest.cc index 04de978..91acad0 100644 --- a/net/spdy/spdy_http_stream_spdy3_unittest.cc +++ b/net/spdy/spdy_http_stream_spdy3_unittest.cc @@ -13,6 +13,7 @@ #include "net/base/default_server_bound_cert_store.h" #include "net/http/http_response_headers.h" #include "net/http/http_response_info.h" +#include "net/spdy/spdy_credential_builder.h" #include "net/spdy/spdy_http_utils.h" #include "net/spdy/spdy_session.h" #include "net/spdy/spdy_test_util_spdy3.h" @@ -532,25 +533,12 @@ void GetECServerBoundCertAndProof( EXPECT_EQ(OK, callback.WaitForResult()); EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, cert_type); - unsigned char secret[32]; - memset(secret, 'A', arraysize(secret)); - - // Convert the key string into a vector<unit8> - std::vector<uint8> key_data(key.begin(), key.end()); - - base::StringPiece spki_piece; - ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(*cert, &spki_piece)); - std::vector<uint8> spki(spki_piece.data(), - spki_piece.data() + spki_piece.size()); - - std::vector<uint8> proof_data; - scoped_ptr<crypto::ECPrivateKey> private_key( - crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo( - ServerBoundCertService::kEPKIPassword, key_data, spki)); - scoped_ptr<crypto::ECSignatureCreator> creator( - crypto::ECSignatureCreator::Create(private_key.get())); - creator->Sign(secret, arraysize(secret), &proof_data); - proof->assign(proof_data.begin(), proof_data.end()); + SpdyCredential credential; + SpdyCredentialBuilder::Build(MockClientSocket::kTlsUnique, cert_type, key, + *cert, 2, &credential); + + cert->assign(credential.certs[0]); + proof->assign(credential.proof); } } // namespace diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc index fc0a5cc..75497a5 100644 --- a/net/spdy/spdy_session.cc +++ b/net/spdy/spdy_session.cc @@ -31,6 +31,7 @@ #include "net/base/server_bound_cert_service.h" #include "net/http/http_network_session.h" #include "net/http/http_server_properties.h" +#include "net/spdy/spdy_credential_builder.h" #include "net/spdy/spdy_frame_builder.h" #include "net/spdy/spdy_http_utils.h" #include "net/spdy/spdy_protocol.h" @@ -626,42 +627,19 @@ SpdyCredentialControlFrame* SpdySession::CreateCredentialFrame( const std::string& cert, RequestPriority priority) { DCHECK(is_secure_); - unsigned char secret[32]; // 32 bytes from the spec - GetSSLClientSocket()->ExportKeyingMaterial("SPDY certificate proof", - true, origin, - secret, arraysize(secret)); - - // Convert the key string into a vector<unit8> - std::vector<uint8> key_data; - for (size_t i = 0; i < key.length(); i++) { - key_data.push_back(key[i]); - } - - std::vector<uint8> proof; - switch (type) { - case CLIENT_CERT_ECDSA_SIGN: { - base::StringPiece spki_piece; - asn1::ExtractSPKIFromDERCert(cert, &spki_piece); - std::vector<uint8> spki(spki_piece.data(), - spki_piece.data() + spki_piece.size()); - scoped_ptr<crypto::ECPrivateKey> private_key( - crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo( - ServerBoundCertService::kEPKIPassword, key_data, spki)); - scoped_ptr<crypto::ECSignatureCreator> creator( - crypto::ECSignatureCreator::Create(private_key.get())); - creator->Sign(secret, arraysize(secret), &proof); - break; - } - default: - NOTREACHED(); - } + SSLClientSocket* ssl_socket = GetSSLClientSocket(); + DCHECK(ssl_socket); + DCHECK(ssl_socket->WasChannelIDSent()); SpdyCredential credential; - GURL origin_url(origin); - credential.slot = - credential_state_.SetHasCredential(origin_url); - credential.certs.push_back(cert); - credential.proof.assign(proof.begin(), proof.end()); + std::string tls_unique; + ssl_socket->GetTLSUniqueChannelBinding(&tls_unique); + size_t slot = credential_state_.SetHasCredential(GURL(origin)); + int rv = SpdyCredentialBuilder::Build(tls_unique, type, key, cert, slot, + &credential); + DCHECK_EQ(OK, rv); + if (rv != OK) + return NULL; DCHECK(buffered_spdy_framer_.get()); scoped_ptr<SpdyCredentialControlFrame> credential_frame( |