// Copyright (c) 2013 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 "content/browser/loader/resource_loader.h" #include "base/run_loop.h" #include "content/browser/browser_thread_impl.h" #include "content/browser/loader/resource_loader_delegate.h" #include "content/public/browser/resource_request_info.h" #include "content/public/test/mock_resource_context.h" #include "content/public/test/test_browser_thread_bundle.h" #include "content/test/test_content_browser_client.h" #include "net/base/request_priority.h" #include "net/cert/x509_certificate.h" #include "net/ssl/client_cert_store.h" #include "net/ssl/ssl_cert_request_info.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_test_util.h" #include "testing/gtest/include/gtest/gtest.h" namespace content { namespace { // Stub client certificate store that returns a preset list of certificates for // each request and records the arguments of the most recent request for later // inspection. class ClientCertStoreStub : public net::ClientCertStore { public: ClientCertStoreStub(const net::CertificateList& certs) : response_(certs), request_count_(0) {} virtual ~ClientCertStoreStub() {} // Returns |cert_authorities| field of the certificate request passed in the // most recent call to GetClientCerts(). // TODO(ppi): Make the stub independent from the internal representation of // SSLCertRequestInfo. For now it seems that we cannot neither save the // scoped_refptr<> (since it is never passed to us) nor copy the entire // CertificateRequestInfo (since there is no copy constructor). std::vector<std::string> requested_authorities() { return requested_authorities_; } // Returns the number of calls to GetClientCerts(). int request_count() { return request_count_; } // net::ClientCertStore: virtual void GetClientCerts(const net::SSLCertRequestInfo& cert_request_info, net::CertificateList* selected_certs, const base::Closure& callback) OVERRIDE { ++request_count_; requested_authorities_ = cert_request_info.cert_authorities; *selected_certs = response_; callback.Run(); } private: const net::CertificateList response_; int request_count_; std::vector<std::string> requested_authorities_; }; // Dummy implementation of ResourceHandler, instance of which is needed to // initialize ResourceLoader. class ResourceHandlerStub : public ResourceHandler { public: ResourceHandlerStub() : ResourceHandler(NULL) {} virtual bool OnUploadProgress(int request_id, uint64 position, uint64 size) OVERRIDE { return true; } virtual bool OnRequestRedirected(int request_id, const GURL& url, ResourceResponse* response, bool* defer) OVERRIDE { return true; } virtual bool OnResponseStarted(int request_id, ResourceResponse* response, bool* defer) OVERRIDE { return true; } virtual bool OnWillStart(int request_id, const GURL& url, bool* defer) OVERRIDE { return true; } virtual bool OnWillRead(int request_id, scoped_refptr<net::IOBuffer>* buf, int* buf_size, int min_size) OVERRIDE { return true; } virtual bool OnReadCompleted(int request_id, int bytes_read, bool* defer) OVERRIDE { return true; } virtual void OnResponseCompleted(int request_id, const net::URLRequestStatus& status, const std::string& security_info, bool* defer) OVERRIDE { } virtual void OnDataDownloaded(int request_id, int bytes_downloaded) OVERRIDE {} }; // Test browser client that captures calls to SelectClientCertificates and // records the arguments of the most recent call for later inspection. class SelectCertificateBrowserClient : public TestContentBrowserClient { public: SelectCertificateBrowserClient() : call_count_(0) {} virtual void SelectClientCertificate( int render_process_id, int render_view_id, const net::HttpNetworkSession* network_session, net::SSLCertRequestInfo* cert_request_info, const base::Callback<void(net::X509Certificate*)>& callback) OVERRIDE { ++call_count_; passed_certs_ = cert_request_info->client_certs; } int call_count() { return call_count_; } net::CertificateList passed_certs() { return passed_certs_; } private: net::CertificateList passed_certs_; int call_count_; }; class ResourceContextStub : public MockResourceContext { public: explicit ResourceContextStub(net::URLRequestContext* test_request_context) : MockResourceContext(test_request_context) {} virtual scoped_ptr<net::ClientCertStore> CreateClientCertStore() OVERRIDE { return dummy_cert_store_.Pass(); } void SetClientCertStore(scoped_ptr<net::ClientCertStore> store) { dummy_cert_store_ = store.Pass(); } private: scoped_ptr<net::ClientCertStore> dummy_cert_store_; }; } // namespace class ResourceLoaderTest : public testing::Test, public ResourceLoaderDelegate { protected: ResourceLoaderTest() : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP), resource_context_(&test_url_request_context_) { } // ResourceLoaderDelegate: virtual ResourceDispatcherHostLoginDelegate* CreateLoginDelegate( ResourceLoader* loader, net::AuthChallengeInfo* auth_info) OVERRIDE { return NULL; } virtual bool AcceptAuthRequest( ResourceLoader* loader, net::AuthChallengeInfo* auth_info) OVERRIDE { return false; }; virtual bool AcceptSSLClientCertificateRequest( ResourceLoader* loader, net::SSLCertRequestInfo* cert_info) OVERRIDE { return true; } virtual bool HandleExternalProtocol(ResourceLoader* loader, const GURL& url) OVERRIDE { return false; } virtual void DidStartRequest(ResourceLoader* loader) OVERRIDE {} virtual void DidReceiveRedirect(ResourceLoader* loader, const GURL& new_url) OVERRIDE {} virtual void DidReceiveResponse(ResourceLoader* loader) OVERRIDE {} virtual void DidFinishLoading(ResourceLoader* loader) OVERRIDE {} content::TestBrowserThreadBundle thread_bundle_; net::TestURLRequestContext test_url_request_context_; ResourceContextStub resource_context_; }; // Verifies if a call to net::UrlRequest::Delegate::OnCertificateRequested() // causes client cert store to be queried for certificates and if the returned // certificates are correctly passed to the content browser client for // selection. TEST_F(ResourceLoaderTest, ClientCertStoreLookup) { const int kRenderProcessId = 1; const int kRenderViewId = 2; scoped_ptr<net::URLRequest> request( new net::URLRequest(GURL("dummy"), net::DEFAULT_PRIORITY, NULL, resource_context_.GetRequestContext())); ResourceRequestInfo::AllocateForTesting(request.get(), ResourceType::MAIN_FRAME, &resource_context_, kRenderProcessId, kRenderViewId, false); // Set up the test client cert store. net::CertificateList dummy_certs(1, scoped_refptr<net::X509Certificate>( new net::X509Certificate("test", "test", base::Time(), base::Time()))); scoped_ptr<ClientCertStoreStub> test_store( new ClientCertStoreStub(dummy_certs)); EXPECT_EQ(0, test_store->request_count()); // Ownership of the |request| and |test_store| is about to be turned over to // ResourceLoader. We need to keep raw pointer copies to access these objects // later. net::URLRequest* raw_ptr_to_request = request.get(); ClientCertStoreStub* raw_ptr_to_store = test_store.get(); resource_context_.SetClientCertStore( test_store.PassAs<net::ClientCertStore>()); scoped_ptr<ResourceHandler> resource_handler(new ResourceHandlerStub()); ResourceLoader loader(request.Pass(), resource_handler.Pass(), this); // Prepare a dummy certificate request. scoped_refptr<net::SSLCertRequestInfo> cert_request_info( new net::SSLCertRequestInfo()); std::vector<std::string> dummy_authority(1, "dummy"); cert_request_info->cert_authorities = dummy_authority; // Plug in test content browser client. SelectCertificateBrowserClient test_client; ContentBrowserClient* old_client = SetBrowserClientForTesting(&test_client); // Everything is set up. Trigger the resource loader certificate request event // and run the message loop. loader.OnCertificateRequested(raw_ptr_to_request, cert_request_info.get()); base::RunLoop().RunUntilIdle(); // Restore the original content browser client. SetBrowserClientForTesting(old_client); // Check if the test store was queried against correct |cert_authorities|. EXPECT_EQ(1, raw_ptr_to_store->request_count()); EXPECT_EQ(dummy_authority, raw_ptr_to_store->requested_authorities()); // Check if the retrieved certificates were passed to the content browser // client. EXPECT_EQ(1, test_client.call_count()); EXPECT_EQ(dummy_certs, test_client.passed_certs()); } // Verifies if a call to net::URLRequest::Delegate::OnCertificateRequested() // on a platform with a NULL client cert store still calls the content browser // client for selection. TEST_F(ResourceLoaderTest, ClientCertStoreNull) { const int kRenderProcessId = 1; const int kRenderViewId = 2; scoped_ptr<net::URLRequest> request( new net::URLRequest(GURL("dummy"), net::DEFAULT_PRIORITY, NULL, resource_context_.GetRequestContext())); ResourceRequestInfo::AllocateForTesting(request.get(), ResourceType::MAIN_FRAME, &resource_context_, kRenderProcessId, kRenderViewId, false); // Ownership of the |request| is about to be turned over to ResourceLoader. We // need to keep a raw pointer copy to access this object later. net::URLRequest* raw_ptr_to_request = request.get(); scoped_ptr<ResourceHandler> resource_handler(new ResourceHandlerStub()); ResourceLoader loader(request.Pass(), resource_handler.Pass(), this); // Prepare a dummy certificate request. scoped_refptr<net::SSLCertRequestInfo> cert_request_info( new net::SSLCertRequestInfo()); std::vector<std::string> dummy_authority(1, "dummy"); cert_request_info->cert_authorities = dummy_authority; // Plug in test content browser client. SelectCertificateBrowserClient test_client; ContentBrowserClient* old_client = SetBrowserClientForTesting(&test_client); // Everything is set up. Trigger the resource loader certificate request event // and run the message loop. loader.OnCertificateRequested(raw_ptr_to_request, cert_request_info.get()); base::RunLoop().RunUntilIdle(); // Restore the original content browser client. SetBrowserClientForTesting(old_client); // Check if the SelectClientCertificate was called on the content browser // client. EXPECT_EQ(1, test_client.call_count()); EXPECT_EQ(net::CertificateList(), test_client.passed_certs()); } } // namespace content