// 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/base/server_bound_cert_service.h" #include #include #include "base/bind.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop.h" #include "base/threading/sequenced_worker_pool.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/net_errors.h" #include "net/base/test_completion_callback.h" #include "net/base/x509_certificate.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { namespace { void FailTest(int /* result */) { FAIL(); } class ServerBoundCertServiceTest : public testing::Test { protected: virtual void SetUp() OVERRIDE { sequenced_worker_pool_ = new base::SequencedWorkerPool( 3, "ServerBoundCertServiceTest"); service_.reset(new ServerBoundCertService( new DefaultServerBoundCertStore(NULL), sequenced_worker_pool_)); } virtual void TearDown() OVERRIDE { sequenced_worker_pool_->Shutdown(); } scoped_refptr sequenced_worker_pool_; scoped_ptr service_; }; TEST_F(ServerBoundCertServiceTest, GetDomainForHost) { EXPECT_EQ("google.com", ServerBoundCertService::GetDomainForHost("google.com")); EXPECT_EQ("google.com", ServerBoundCertService::GetDomainForHost("www.google.com")); // NOTE(rch): we would like to segregate cookies and certificates for // *.appspot.com, but currently we can not do that becaues we want to // allow direct navigation to appspot.com. EXPECT_EQ("appspot.com", ServerBoundCertService::GetDomainForHost("foo.appspot.com")); EXPECT_EQ("google.com", ServerBoundCertService::GetDomainForHost("www.mail.google.com")); EXPECT_EQ("goto", ServerBoundCertService::GetDomainForHost("goto")); EXPECT_EQ("127.0.0.1", ServerBoundCertService::GetDomainForHost("127.0.0.1")); } // See http://crbug.com/91512 - implement OpenSSL version of CreateSelfSigned. #if !defined(USE_OPENSSL) TEST_F(ServerBoundCertServiceTest, CacheHit) { std::string origin("https://encrypted.google.com:443"); int error; std::vector types; types.push_back(CLIENT_CERT_ECDSA_SIGN); TestCompletionCallback callback; ServerBoundCertService::RequestHandle request_handle; // Asynchronous completion. SSLClientCertType type1; std::string private_key_info1, der_cert1; EXPECT_EQ(0, service_->cert_count()); error = service_->GetDomainBoundCert( origin, types, &type1, &private_key_info1, &der_cert1, callback.callback(), &request_handle); EXPECT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request_handle != NULL); error = callback.WaitForResult(); EXPECT_EQ(OK, error); EXPECT_EQ(1, service_->cert_count()); EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type1); EXPECT_FALSE(private_key_info1.empty()); EXPECT_FALSE(der_cert1.empty()); // Synchronous completion. SSLClientCertType type2; std::string private_key_info2, der_cert2; error = service_->GetDomainBoundCert( origin, types, &type2, &private_key_info2, &der_cert2, callback.callback(), &request_handle); EXPECT_TRUE(request_handle == NULL); EXPECT_EQ(OK, error); EXPECT_EQ(1, service_->cert_count()); EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type2); EXPECT_EQ(private_key_info1, private_key_info2); EXPECT_EQ(der_cert1, der_cert2); EXPECT_EQ(2u, service_->requests()); EXPECT_EQ(1u, service_->cert_store_hits()); EXPECT_EQ(0u, service_->inflight_joins()); } TEST_F(ServerBoundCertServiceTest, UnsupportedTypes) { std::string origin("https://encrypted.google.com:443"); int error; std::vector types; TestCompletionCallback callback; ServerBoundCertService::RequestHandle request_handle; // Empty requested_types. SSLClientCertType type1; std::string private_key_info1, der_cert1; error = service_->GetDomainBoundCert( origin, types, &type1, &private_key_info1, &der_cert1, callback.callback(), &request_handle); EXPECT_EQ(ERR_INVALID_ARGUMENT, error); EXPECT_TRUE(request_handle == NULL); // No supported types in requested_types. types.push_back(CLIENT_CERT_RSA_SIGN); types.push_back(2); types.push_back(3); error = service_->GetDomainBoundCert( origin, types, &type1, &private_key_info1, &der_cert1, callback.callback(), &request_handle); EXPECT_EQ(ERR_CLIENT_AUTH_CERT_TYPE_UNSUPPORTED, error); EXPECT_TRUE(request_handle == NULL); // Supported types after unsupported ones in requested_types. types.push_back(CLIENT_CERT_ECDSA_SIGN); // Asynchronous completion. EXPECT_EQ(0, service_->cert_count()); error = service_->GetDomainBoundCert( origin, types, &type1, &private_key_info1, &der_cert1, callback.callback(), &request_handle); EXPECT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request_handle != NULL); error = callback.WaitForResult(); EXPECT_EQ(OK, error); EXPECT_EQ(1, service_->cert_count()); EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type1); EXPECT_FALSE(private_key_info1.empty()); EXPECT_FALSE(der_cert1.empty()); // Now that the cert is created, doing requests for unsupported types // shouldn't affect the created cert. // Empty requested_types. types.clear(); SSLClientCertType type2; std::string private_key_info2, der_cert2; error = service_->GetDomainBoundCert( origin, types, &type2, &private_key_info2, &der_cert2, callback.callback(), &request_handle); EXPECT_EQ(ERR_INVALID_ARGUMENT, error); EXPECT_TRUE(request_handle == NULL); // No supported types in requested_types. types.push_back(CLIENT_CERT_RSA_SIGN); types.push_back(2); types.push_back(3); error = service_->GetDomainBoundCert( origin, types, &type2, &private_key_info2, &der_cert2, callback.callback(), &request_handle); EXPECT_EQ(ERR_CLIENT_AUTH_CERT_TYPE_UNSUPPORTED, error); EXPECT_TRUE(request_handle == NULL); // If we request EC, the cert we created before should still be there. types.push_back(CLIENT_CERT_ECDSA_SIGN); error = service_->GetDomainBoundCert( origin, types, &type2, &private_key_info2, &der_cert2, callback.callback(), &request_handle); EXPECT_TRUE(request_handle == NULL); EXPECT_EQ(OK, error); EXPECT_EQ(1, service_->cert_count()); EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type2); EXPECT_EQ(private_key_info1, private_key_info2); EXPECT_EQ(der_cert1, der_cert2); } TEST_F(ServerBoundCertServiceTest, StoreCerts) { int error; std::vector types; types.push_back(CLIENT_CERT_ECDSA_SIGN); TestCompletionCallback callback; ServerBoundCertService::RequestHandle request_handle; std::string origin1("https://encrypted.google.com:443"); SSLClientCertType type1; std::string private_key_info1, der_cert1; EXPECT_EQ(0, service_->cert_count()); error = service_->GetDomainBoundCert( origin1, types, &type1, &private_key_info1, &der_cert1, callback.callback(), &request_handle); EXPECT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request_handle != NULL); error = callback.WaitForResult(); EXPECT_EQ(OK, error); EXPECT_EQ(1, service_->cert_count()); std::string origin2("https://www.verisign.com:443"); SSLClientCertType type2; std::string private_key_info2, der_cert2; error = service_->GetDomainBoundCert( origin2, types, &type2, &private_key_info2, &der_cert2, callback.callback(), &request_handle); EXPECT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request_handle != NULL); error = callback.WaitForResult(); EXPECT_EQ(OK, error); EXPECT_EQ(2, service_->cert_count()); std::string origin3("https://www.twitter.com:443"); SSLClientCertType type3; std::string private_key_info3, der_cert3; error = service_->GetDomainBoundCert( origin3, types, &type3, &private_key_info3, &der_cert3, callback.callback(), &request_handle); EXPECT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request_handle != NULL); error = callback.WaitForResult(); EXPECT_EQ(OK, error); EXPECT_EQ(3, service_->cert_count()); EXPECT_NE(private_key_info1, private_key_info2); EXPECT_NE(der_cert1, der_cert2); EXPECT_NE(private_key_info1, private_key_info3); EXPECT_NE(der_cert1, der_cert3); EXPECT_NE(private_key_info2, private_key_info3); EXPECT_NE(der_cert2, der_cert3); EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type1); EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type2); EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type3); } // Tests an inflight join. TEST_F(ServerBoundCertServiceTest, InflightJoin) { std::string origin("https://encrypted.google.com:443"); int error; std::vector types; types.push_back(CLIENT_CERT_ECDSA_SIGN); SSLClientCertType type1; std::string private_key_info1, der_cert1; TestCompletionCallback callback1; ServerBoundCertService::RequestHandle request_handle1; SSLClientCertType type2; std::string private_key_info2, der_cert2; TestCompletionCallback callback2; ServerBoundCertService::RequestHandle request_handle2; error = service_->GetDomainBoundCert( origin, types, &type1, &private_key_info1, &der_cert1, callback1.callback(), &request_handle1); EXPECT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request_handle1 != NULL); // If we request RSA and EC in the 2nd request, should still join with the // original request. types.insert(types.begin(), CLIENT_CERT_RSA_SIGN); error = service_->GetDomainBoundCert( origin, types, &type2, &private_key_info2, &der_cert2, callback2.callback(), &request_handle2); EXPECT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request_handle2 != NULL); error = callback1.WaitForResult(); EXPECT_EQ(OK, error); error = callback2.WaitForResult(); EXPECT_EQ(OK, error); EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type1); EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type2); EXPECT_EQ(2u, service_->requests()); EXPECT_EQ(0u, service_->cert_store_hits()); EXPECT_EQ(1u, service_->inflight_joins()); } TEST_F(ServerBoundCertServiceTest, ExtractValuesFromBytesEC) { std::string origin("https://encrypted.google.com:443"); SSLClientCertType type; std::string private_key_info, der_cert; int error; std::vector types; types.push_back(CLIENT_CERT_ECDSA_SIGN); TestCompletionCallback callback; ServerBoundCertService::RequestHandle request_handle; error = service_->GetDomainBoundCert( origin, types, &type, &private_key_info, &der_cert, callback.callback(), &request_handle); EXPECT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request_handle != NULL); error = callback.WaitForResult(); EXPECT_EQ(OK, error); base::StringPiece spki_piece; ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(der_cert, &spki_piece)); std::vector spki( spki_piece.data(), spki_piece.data() + spki_piece.size()); // Check that we can retrieve the key from the bytes. std::vector key_vec(private_key_info.begin(), private_key_info.end()); scoped_ptr private_key( crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo( ServerBoundCertService::kEPKIPassword, key_vec, spki)); EXPECT_TRUE(private_key != NULL); // Check that we can retrieve the cert from the bytes. scoped_refptr x509cert( X509Certificate::CreateFromBytes(der_cert.data(), der_cert.size())); EXPECT_TRUE(x509cert != NULL); } // Tests that the callback of a canceled request is never made. TEST_F(ServerBoundCertServiceTest, CancelRequest) { std::string origin("https://encrypted.google.com:443"); SSLClientCertType type; std::string private_key_info, der_cert; int error; std::vector types; types.push_back(CLIENT_CERT_ECDSA_SIGN); ServerBoundCertService::RequestHandle request_handle; error = service_->GetDomainBoundCert(origin, types, &type, &private_key_info, &der_cert, base::Bind(&FailTest), &request_handle); EXPECT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request_handle != NULL); service_->CancelRequest(request_handle); // Wait for generation to finish. sequenced_worker_pool_->FlushForTesting(); // Wait for reply from ServerBoundCertServiceWorker to be posted back to the // ServerBoundCertService. MessageLoop::current()->RunAllPending(); // Even though the original request was cancelled, the service will still // store the result, it just doesn't call the callback. EXPECT_EQ(1, service_->cert_count()); } TEST_F(ServerBoundCertServiceTest, DestructionWithPendingRequest) { std::string origin("https://encrypted.google.com:443"); SSLClientCertType type; std::string private_key_info, der_cert; int error; std::vector types; types.push_back(CLIENT_CERT_ECDSA_SIGN); ServerBoundCertService::RequestHandle request_handle; error = service_->GetDomainBoundCert(origin, types, &type, &private_key_info, &der_cert, base::Bind(&FailTest), &request_handle); EXPECT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request_handle != NULL); // Cancel request and destroy the ServerBoundCertService. service_->CancelRequest(request_handle); service_.reset(); // Wait for generation to finish. sequenced_worker_pool_->FlushForTesting(); // ServerBoundCertServiceWorker should not post anything back to the // non-existant ServerBoundCertService, but run the loop just to be sure it // doesn't. MessageLoop::current()->RunAllPending(); // If we got here without crashing or a valgrind error, it worked. } // Tests that simultaneous creation of different certs works. TEST_F(ServerBoundCertServiceTest, SimultaneousCreation) { int error; std::vector types; types.push_back(CLIENT_CERT_ECDSA_SIGN); ServerBoundCertService::RequestHandle request_handle; std::string origin1("https://encrypted.google.com:443"); SSLClientCertType type1; std::string private_key_info1, der_cert1; TestCompletionCallback callback1; std::string origin2("https://foo.com:443"); SSLClientCertType type2; std::string private_key_info2, der_cert2; TestCompletionCallback callback2; std::string origin3("https://bar.com:443"); SSLClientCertType type3; std::string private_key_info3, der_cert3; TestCompletionCallback callback3; error = service_->GetDomainBoundCert(origin1, types, &type1, &private_key_info1, &der_cert1, callback1.callback(), &request_handle); EXPECT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request_handle != NULL); error = service_->GetDomainBoundCert(origin2, types, &type2, &private_key_info2, &der_cert2, callback2.callback(), &request_handle); EXPECT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request_handle != NULL); error = service_->GetDomainBoundCert(origin3, types, &type3, &private_key_info3, &der_cert3, callback3.callback(), &request_handle); EXPECT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request_handle != NULL); error = callback1.WaitForResult(); EXPECT_EQ(OK, error); EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type1); EXPECT_FALSE(private_key_info1.empty()); EXPECT_FALSE(der_cert1.empty()); error = callback2.WaitForResult(); EXPECT_EQ(OK, error); EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type2); EXPECT_FALSE(private_key_info2.empty()); EXPECT_FALSE(der_cert2.empty()); error = callback3.WaitForResult(); EXPECT_EQ(OK, error); EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type3); EXPECT_FALSE(private_key_info3.empty()); EXPECT_FALSE(der_cert3.empty()); EXPECT_NE(private_key_info1, private_key_info2); EXPECT_NE(der_cert1, der_cert2); EXPECT_NE(private_key_info1, private_key_info3); EXPECT_NE(der_cert1, der_cert3); EXPECT_NE(private_key_info2, private_key_info3); EXPECT_NE(der_cert2, der_cert3); EXPECT_EQ(3, service_->cert_count()); } TEST_F(ServerBoundCertServiceTest, Expiration) { ServerBoundCertStore* store = service_->GetCertStore(); base::Time now = base::Time::Now(); store->SetServerBoundCert("good", CLIENT_CERT_ECDSA_SIGN, now, now + base::TimeDelta::FromDays(1), "a", "b"); store->SetServerBoundCert("expired", CLIENT_CERT_ECDSA_SIGN, now - base::TimeDelta::FromDays(2), now - base::TimeDelta::FromDays(1), "c", "d"); EXPECT_EQ(2, service_->cert_count()); int error; std::vector types; types.push_back(CLIENT_CERT_ECDSA_SIGN); TestCompletionCallback callback; ServerBoundCertService::RequestHandle request_handle; // Cert still valid - synchronous completion. SSLClientCertType type1; std::string private_key_info1, der_cert1; error = service_->GetDomainBoundCert( "https://good", types, &type1, &private_key_info1, &der_cert1, callback.callback(), &request_handle); EXPECT_EQ(OK, error); EXPECT_TRUE(request_handle == NULL); EXPECT_EQ(2, service_->cert_count()); EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type1); EXPECT_STREQ("a", private_key_info1.c_str()); EXPECT_STREQ("b", der_cert1.c_str()); // Cert expired - New cert will be generated, asynchronous completion. SSLClientCertType type2; std::string private_key_info2, der_cert2; error = service_->GetDomainBoundCert( "https://expired", types, &type2, &private_key_info2, &der_cert2, callback.callback(), &request_handle); EXPECT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request_handle != NULL); error = callback.WaitForResult(); EXPECT_EQ(OK, error); EXPECT_EQ(2, service_->cert_count()); EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, type2); EXPECT_LT(1U, private_key_info2.size()); EXPECT_LT(1U, der_cert2.size()); } #endif // !defined(USE_OPENSSL) } // namespace } // namespace net