Commit fc58ffa8 authored by rkn@chromium.org's avatar rkn@chromium.org

Gave the GetOriginBoundCertificate an asynchronous interface because certificate

generation is a blocking operation.

BUG=88782
TEST=None

Review URL: http://codereview.chromium.org/7565023

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@96229 0039d316-1c4b-4281-b951-d872f2087c98
parent fc1bb644
......@@ -574,6 +574,18 @@ NET_ERROR(PKCS12_IMPORT_INVALID_FILE, -708)
// PKCS #12 import failed due to unsupported features.
NET_ERROR(PKCS12_IMPORT_UNSUPPORTED, -709)
// Key generation failed.
NET_ERROR(KEY_GENERATION_FAILED, -710)
// Origin-bound certificate generation failed.
NET_ERROR(ORIGIN_BOUND_CERT_GENERATION_FAILED, -711)
// Failure to export private key.
NET_ERROR(PRIVATE_KEY_EXPORT_FAILED, -712)
// Failure to get certificate bytes.
NET_ERROR(GET_CERT_BYTES_FAILED, -713)
// DNS error codes.
// DNS resolver received a malformed response.
......
This diff is collapsed.
......@@ -6,20 +6,30 @@
#define NET_BASE_ORIGIN_BOUND_CERT_SERVICE_H_
#pragma once
#include <map>
#include <string>
#include "base/memory/ref_counted.h"
#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "base/threading/non_thread_safe.h"
#include "net/base/completion_callback.h"
#include "net/base/net_api.h"
namespace net {
class OriginBoundCertServiceJob;
class OriginBoundCertServiceWorker;
class OriginBoundCertStore;
// A class for creating and fetching origin bound certs.
// Inherits from NonThreadSafe in order to use the function
// |CalledOnValidThread|.
class NET_API OriginBoundCertService
: public base::RefCountedThreadSafe<OriginBoundCertService> {
: NON_EXPORTED_BASE(public base::NonThreadSafe) {
public:
// Opaque type used to cancel a request.
typedef void* RequestHandle;
// This object owns origin_bound_cert_store.
explicit OriginBoundCertService(
OriginBoundCertStore* origin_bound_cert_store);
......@@ -27,21 +37,68 @@ class NET_API OriginBoundCertService
~OriginBoundCertService();
// TODO(rkn): Specify certificate type (RSA or DSA).
// TODO(rkn): Key generation can be time consuming, so this should have an
// asynchronous interface.
//
// Fetches the origin bound cert for the specified origin if one exists
// and creates one otherwise. On success, |private_key_result| stores a
// DER-encoded PrivateKeyInfo struct, and |cert_result| stores a DER-encoded
// certificate.
bool GetOriginBoundCert(const std::string& origin,
std::string* private_key_result,
std::string* cert_result);
// and creates one otherwise. Returns OK if successful or an error code upon
// failure.
//
// On successful completion, |private_key| stores a DER-encoded
// PrivateKeyInfo struct, and |cert| stores a DER-encoded certificate.
//
// |callback| must not be null. ERR_IO_PENDING is returned if the operation
// could not be completed immediately, in which case the result code will
// be passed to the callback when available.
//
// If |out_req| is non-NULL, then |*out_req| will be filled with a handle to
// the async request. This handle is not valid after the request has
// completed.
int GetOriginBoundCert(const std::string& origin,
std::string* private_key,
std::string* cert,
CompletionCallback* callback,
RequestHandle* out_req);
// Cancels the specified request. |req| is the handle returned by
// GetOriginBoundCert(). After a request is canceled, its completion
// callback will not be called.
void CancelRequest(RequestHandle req);
// Public only for unit testing.
int GetCertCount();
int cert_count();
uint64 requests() const { return requests_; }
uint64 cert_store_hits() const { return cert_store_hits_; }
uint64 inflight_joins() const { return inflight_joins_; }
private:
friend class OriginBoundCertServiceWorker; // Calls HandleResult.
// On success, |private_key| stores a DER-encoded PrivateKeyInfo
// struct, and |cert| stores a DER-encoded certificate. Returns
// OK if successful and an error code otherwise.
// |serial_number| is passed in because it is created with the function
// base::RandInt, which opens the file /dev/urandom. /dev/urandom is opened
// with a LazyInstance, which is not allowed on a worker thread.
static int GenerateCert(const std::string& origin,
uint32 serial_number,
std::string* private_key,
std::string* cert);
void HandleResult(const std::string& origin,
int error,
const std::string& private_key,
const std::string& cert);
scoped_ptr<OriginBoundCertStore> origin_bound_cert_store_;
// inflight_ maps from an origin to an active generation which is taking
// place.
std::map<std::string, OriginBoundCertServiceJob*> inflight_;
uint64 requests_;
uint64 cert_store_hits_;
uint64 inflight_joins_;
DISALLOW_COPY_AND_ASSIGN(OriginBoundCertService);
};
} // namespace net
......
......@@ -7,57 +7,165 @@
#include <string>
#include <vector>
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "crypto/rsa_private_key.h"
#include "net/base/default_origin_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 {
class OriginBoundCertServiceTest : public testing::Test {
};
class ExplodingCallback : public CallbackRunner<Tuple1<int> > {
public:
virtual void RunWithParams(const Tuple1<int>& params) {
FAIL();
}
};
// See http://crbug.com/91512 - implement OpenSSL version of CreateSelfSigned.
#if !defined(USE_OPENSSL)
TEST(OriginBoundCertServiceTest, DuplicateCertTest) {
scoped_refptr<OriginBoundCertService> service(
TEST_F(OriginBoundCertServiceTest, CacheHit) {
scoped_ptr<OriginBoundCertService> service(
new OriginBoundCertService(new DefaultOriginBoundCertStore(NULL)));
std::string origin("https://encrypted.google.com:443");
int error;
TestCompletionCallback callback;
OriginBoundCertService::RequestHandle request_handle;
// Asynchronous completion.
std::string private_key_info1, der_cert1;
EXPECT_EQ(0, service->cert_count());
error = service->GetOriginBoundCert(
origin, &private_key_info1, &der_cert1, &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_FALSE(private_key_info1.empty());
EXPECT_FALSE(der_cert1.empty());
// Synchronous completion.
std::string private_key_info2, der_cert2;
error = service->GetOriginBoundCert(
origin, &private_key_info2, &der_cert2, &callback, &request_handle);
EXPECT_TRUE(request_handle == NULL);
EXPECT_EQ(OK, error);
EXPECT_EQ(1, service->cert_count());
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(OriginBoundCertServiceTest, StoreCerts) {
scoped_ptr<OriginBoundCertService> service(
new OriginBoundCertService(new DefaultOriginBoundCertStore(NULL)));
std::string origin1("https://encrypted.google.com/");
std::string origin2("https://www.verisign.com/");
// The store should start out empty and should increment appropriately.
std::string private_key_info_1a, der_cert_1a;
EXPECT_EQ(0, service->GetCertCount());
EXPECT_TRUE(service->GetOriginBoundCert(
origin1, &private_key_info_1a, &der_cert_1a));
EXPECT_EQ(1, service->GetCertCount());
// We should get the same cert and key for the same origin.
std::string private_key_info_1b, der_cert_1b;
EXPECT_TRUE(service->GetOriginBoundCert(
origin1, &private_key_info_1b, &der_cert_1b));
EXPECT_EQ(1, service->GetCertCount());
EXPECT_EQ(private_key_info_1a, private_key_info_1b);
EXPECT_EQ(der_cert_1a, der_cert_1b);
// We should get a different cert and key for different origins.
std::string private_key_info_2, der_cert_2;
EXPECT_TRUE(service->GetOriginBoundCert(
origin2, &private_key_info_2, &der_cert_2));
EXPECT_EQ(2, service->GetCertCount());
EXPECT_NE(private_key_info_1a, private_key_info_2);
EXPECT_NE(der_cert_1a, der_cert_2);
int error;
TestCompletionCallback callback;
OriginBoundCertService::RequestHandle request_handle;
std::string origin1("https://encrypted.google.com:443");
std::string private_key_info1, der_cert1;
EXPECT_EQ(0, service->cert_count());
error = service->GetOriginBoundCert(
origin1, &private_key_info1, &der_cert1, &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");
std::string private_key_info2, der_cert2;
error = service->GetOriginBoundCert(
origin2, &private_key_info2, &der_cert2, &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");
std::string private_key_info3, der_cert3;
error = service->GetOriginBoundCert(
origin3, &private_key_info3, &der_cert3, &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);
}
TEST(OriginBoundCertServiceTest, ExtractValuesFromBytes) {
scoped_refptr<OriginBoundCertService> service(
// Tests an inflight join.
TEST_F(OriginBoundCertServiceTest, InflightJoin) {
scoped_ptr<OriginBoundCertService> service(
new OriginBoundCertService(new DefaultOriginBoundCertStore(NULL)));
std::string origin("https://encrypted.google.com/");
std::string origin("https://encrypted.google.com:443");
int error;
std::string private_key_info1, der_cert1;
TestCompletionCallback callback1;
OriginBoundCertService::RequestHandle request_handle1;
std::string private_key_info2, der_cert2;
TestCompletionCallback callback2;
OriginBoundCertService::RequestHandle request_handle2;
error = service->GetOriginBoundCert(
origin, &private_key_info1, &der_cert1, &callback1, &request_handle1);
EXPECT_EQ(ERR_IO_PENDING, error);
EXPECT_TRUE(request_handle1 != NULL);
error = service->GetOriginBoundCert(
origin, &private_key_info2, &der_cert2, &callback2, &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(2u, service->requests());
EXPECT_EQ(0u, service->cert_store_hits());
EXPECT_EQ(1u, service->inflight_joins());
}
TEST_F(OriginBoundCertServiceTest, ExtractValuesFromBytes) {
scoped_ptr<OriginBoundCertService> service(
new OriginBoundCertService(new DefaultOriginBoundCertStore(NULL)));
std::string origin("https://encrypted.google.com:443");
std::string private_key_info, der_cert;
EXPECT_TRUE(service->GetOriginBoundCert(
origin, &private_key_info, &der_cert));
std::vector<uint8> key_vec(private_key_info.begin(), private_key_info.end());
int error;
TestCompletionCallback callback;
OriginBoundCertService::RequestHandle request_handle;
error = service->GetOriginBoundCert(
origin, &private_key_info, &der_cert, &callback, &request_handle);
EXPECT_EQ(ERR_IO_PENDING, error);
EXPECT_TRUE(request_handle != NULL);
error = callback.WaitForResult();
EXPECT_EQ(OK, error);
// Check that we can retrieve the key pair from the bytes.
// Check that we can retrieve the key from the bytes.
std::vector<uint8> key_vec(private_key_info.begin(), private_key_info.end());
scoped_ptr<crypto::RSAPrivateKey> private_key(
crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(key_vec));
EXPECT_TRUE(private_key != NULL);
......@@ -68,6 +176,42 @@ TEST(OriginBoundCertServiceTest, ExtractValuesFromBytes) {
EXPECT_TRUE(x509cert != NULL);
}
// Tests that the callback of a canceled request is never made.
TEST_F(OriginBoundCertServiceTest, CancelRequest) {
scoped_ptr<OriginBoundCertService> service(
new OriginBoundCertService(new DefaultOriginBoundCertStore(NULL)));
std::string origin("https://encrypted.google.com:443");
std::string private_key_info, der_cert;
int error;
ExplodingCallback exploding_callback;
OriginBoundCertService::RequestHandle request_handle;
error = service->GetOriginBoundCert(origin,
&private_key_info,
&der_cert,
&exploding_callback,
&request_handle);
EXPECT_EQ(ERR_IO_PENDING, error);
EXPECT_TRUE(request_handle != NULL);
service->CancelRequest(request_handle);
// Issue a few more requests to the worker pool and wait for their
// completion, so that the task of the canceled request (which runs on a
// worker thread) is likely to complete by the end of this test.
TestCompletionCallback callback;
for (int i = 0; i < 5; ++i) {
error = service->GetOriginBoundCert(
"https://encrypted.google.com:" + std::string(1, (char) ('1' + i)),
&private_key_info,
&der_cert,
&callback,
&request_handle);
EXPECT_EQ(ERR_IO_PENDING, error);
EXPECT_TRUE(request_handle != NULL);
error = callback.WaitForResult();
}
}
#endif // !defined(USE_OPENSSL)
} // namespace net
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment