diff options
author | mattm@chromium.org <mattm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-18 01:47:04 +0000 |
---|---|---|
committer | mattm@chromium.org <mattm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-18 01:47:04 +0000 |
commit | 3c5f6db2ef15a4ac179351764f320e16364fa457 (patch) | |
tree | c69a4d6684d81d73a622343311c2e962501819e4 /net/base | |
parent | 091a5b6f0dede5cedee54ec1aae623a946ab405f (diff) | |
download | chromium_src-3c5f6db2ef15a4ac179351764f320e16364fa457.zip chromium_src-3c5f6db2ef15a4ac179351764f320e16364fa457.tar.gz chromium_src-3c5f6db2ef15a4ac179351764f320e16364fa457.tar.bz2 |
Use NSS to generate Origin-Bound Certs on Win and Mac.
The platform RSAPrivateKey is used to generate the private key, which is then imported into NSS to generate the certificate.
X509Certificate::CreateOriginBound is moved to x509_util::CreateOriginBoundCert so it can be shared by those platforms, and removes the unnecessary X509Certificate generation step.
BUG=88782
TEST=X509UtilNSSTest.CreateOriginBoundCert & manual testing: try on win or mac, check if generated cert has the OBC extension.
Review URL: http://codereview.chromium.org/8296014
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@105997 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/base')
-rw-r--r-- | net/base/net_error_list.h | 3 | ||||
-rw-r--r-- | net/base/origin_bound_cert_service.cc | 22 | ||||
-rw-r--r-- | net/base/x509_certificate.h | 12 | ||||
-rw-r--r-- | net/base/x509_certificate_mac.cc | 19 | ||||
-rw-r--r-- | net/base/x509_certificate_nss.cc | 239 | ||||
-rw-r--r-- | net/base/x509_certificate_openssl.cc | 11 | ||||
-rw-r--r-- | net/base/x509_certificate_unittest.cc | 79 | ||||
-rw-r--r-- | net/base/x509_certificate_win.cc | 10 | ||||
-rw-r--r-- | net/base/x509_util.h | 39 | ||||
-rw-r--r-- | net/base/x509_util_nss.cc | 318 | ||||
-rw-r--r-- | net/base/x509_util_nss.h | 37 | ||||
-rw-r--r-- | net/base/x509_util_nss_unittest.cc | 106 | ||||
-rw-r--r-- | net/base/x509_util_openssl.cc | 11 | ||||
-rw-r--r-- | net/base/x509_util_openssl_unittest.cc | 29 |
14 files changed, 553 insertions, 382 deletions
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h index cc012e0..3097d31 100644 --- a/net/base/net_error_list.h +++ b/net/base/net_error_list.h @@ -613,9 +613,6 @@ 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. diff --git a/net/base/origin_bound_cert_service.cc b/net/base/origin_bound_cert_service.cc index 4b48e61..e5c1e9d 100644 --- a/net/base/origin_bound_cert_service.cc +++ b/net/base/origin_bound_cert_service.cc @@ -19,6 +19,7 @@ #include "net/base/net_errors.h" #include "net/base/origin_bound_cert_store.h" #include "net/base/x509_certificate.h" +#include "net/base/x509_util.h" #if defined(USE_NSS) #include <private/pprthred.h> // PR_DetachThread @@ -324,20 +325,13 @@ int OriginBoundCertService::GenerateCert(const std::string& origin, LOG(WARNING) << "Unable to create key pair for client"; return ERR_KEY_GENERATION_FAILED; } -#if defined(USE_NSS) - scoped_refptr<X509Certificate> x509_cert = X509Certificate::CreateOriginBound( + std::string der_cert; + if (!x509_util::CreateOriginBoundCert( key.get(), origin, serial_number, - base::TimeDelta::FromDays(kValidityPeriodInDays)); -#else - scoped_refptr<X509Certificate> x509_cert = X509Certificate::CreateSelfSigned( - key.get(), - "CN=anonymous.invalid", - serial_number, - base::TimeDelta::FromDays(kValidityPeriodInDays)); -#endif - if (!x509_cert) { + base::TimeDelta::FromDays(kValidityPeriodInDays), + &der_cert)) { LOG(WARNING) << "Unable to create x509 cert for client"; return ERR_ORIGIN_BOUND_CERT_GENERATION_FAILED; } @@ -351,12 +345,6 @@ int OriginBoundCertService::GenerateCert(const std::string& origin, // std::string* to prevent this copying. std::string key_out(private_key_info.begin(), private_key_info.end()); - std::string der_cert; - if (!x509_cert->GetDEREncoded(&der_cert)) { - LOG(WARNING) << "Unable to get DER-encoded cert"; - return ERR_GET_CERT_BYTES_FAILED; - } - private_key->swap(key_out); cert->swap(der_cert); return OK; diff --git a/net/base/x509_certificate.h b/net/base/x509_certificate.h index 1a8b233..94a6f4a 100644 --- a/net/base/x509_certificate.h +++ b/net/base/x509_certificate.h @@ -187,18 +187,6 @@ class NET_EXPORT X509Certificate uint32 serial_number, base::TimeDelta valid_duration); - // Create an origin bound certificate containing the public key in |key|. - // Web origin, serial number and validity period are given as - // parameters. The certificate is signed by the private key in |key|. - // The hashing algorithm for the signature is SHA-1. - // - // See Internet Draft draft-balfanz-tls-obc-00 for more details: - // http://balfanz.github.com/tls-obc-spec/draft-balfanz-tls-obc-00.html - static X509Certificate* CreateOriginBound(crypto::RSAPrivateKey* key, - const std::string& origin, - uint32 serial_number, - base::TimeDelta valid_duration); - // Appends a representation of this object to the given pickle. void Persist(Pickle* pickle); diff --git a/net/base/x509_certificate_mac.cc b/net/base/x509_certificate_mac.cc index 2c95981..c47d1a5 100644 --- a/net/base/x509_certificate_mac.cc +++ b/net/base/x509_certificate_mac.cc @@ -695,25 +695,6 @@ X509Certificate* X509Certificate::CreateSelfSigned( return CreateFromHandle(scoped_cert, X509Certificate::OSCertHandles()); } -// static -X509Certificate* X509Certificate::CreateOriginBound( - crypto::RSAPrivateKey* key, - const std::string& origin, - uint32 serial_number, - base::TimeDelta valid_duration) { - // TODO(wtc): this cannot be implemented by creating a CE_DataAndType for - // the origin-bound extension and adding it to certReq.extensions because - // it is not one of the supported extensions in the CE_DataType enum type. - // Using the DT_Other enum constant does not work. - // - // The relevant Apple headers are: - // - CSSM_APPLE_TP_CERT_REQUEST is defined in cssmapple.h. - // - CE_DataAndType, CE_DataType, and CE_Data are defined in - // certextensions.h. - NOTIMPLEMENTED(); - return NULL; -} - void X509Certificate::GetSubjectAltName( std::vector<std::string>* dns_names, std::vector<std::string>* ip_addrs) const { diff --git a/net/base/x509_certificate_nss.cc b/net/base/x509_certificate_nss.cc index c226132..039edad 100644 --- a/net/base/x509_certificate_nss.cc +++ b/net/base/x509_certificate_nss.cc @@ -6,7 +6,6 @@ #include <cert.h> #include <cryptohi.h> -#include <keyhi.h> #include <nss.h> #include <pk11pub.h> #include <prerror.h> @@ -18,7 +17,6 @@ #include "base/logging.h" #include "base/memory/scoped_ptr.h" -#include "base/memory/singleton.h" #include "base/pickle.h" #include "base/time.h" #include "crypto/nss_util.h" @@ -27,55 +25,12 @@ #include "net/base/cert_verify_result.h" #include "net/base/ev_root_ca_metadata.h" #include "net/base/net_errors.h" +#include "net/base/x509_util_nss.h" namespace net { namespace { -class ObCertOIDWrapper { - public: - static ObCertOIDWrapper* GetInstance() { - // Instantiated as a leaky singleton to allow the singleton to be - // constructed on a worker thead that is not joined when a process - // shuts down. - return Singleton<ObCertOIDWrapper, - LeakySingletonTraits<ObCertOIDWrapper> >::get(); - } - - SECOidTag ob_cert_oid_tag() const { - return ob_cert_oid_tag_; - } - - private: - friend struct DefaultSingletonTraits<ObCertOIDWrapper>; - - ObCertOIDWrapper(); - - SECOidTag ob_cert_oid_tag_; - - DISALLOW_COPY_AND_ASSIGN(ObCertOIDWrapper); -}; - -ObCertOIDWrapper::ObCertOIDWrapper(): ob_cert_oid_tag_(SEC_OID_UNKNOWN) { - // 1.3.6.1.4.1.11129.2.1.6 - // (iso.org.dod.internet.private.enterprises.google.googleSecurity. - // certificateExtensions.originBoundCertificate) - static const uint8 kObCertOID[] = { - 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x06 - }; - SECOidData oid_data; - memset(&oid_data, 0, sizeof(oid_data)); - oid_data.oid.data = const_cast<uint8*>(kObCertOID); - oid_data.oid.len = sizeof(kObCertOID); - oid_data.offset = SEC_OID_UNKNOWN; - oid_data.desc = "Origin Bound Certificate"; - oid_data.mechanism = CKM_INVALID_MECHANISM; - oid_data.supportedExtension = SUPPORTED_CERT_EXTENSION; - ob_cert_oid_tag_ = SECOID_AddEntry(&oid_data); - if (ob_cert_oid_tag_ == SEC_OID_UNKNOWN) - LOG(ERROR) << "OB_CERT OID tag creation failed"; -} - class ScopedCERTCertificatePolicies { public: explicit ScopedCERTCertificatePolicies(CERTCertificatePolicies* policies) @@ -668,125 +623,6 @@ void X509Certificate::Initialize() { serial_number_ = serial_number_.substr(1, serial_number_.size() - 1); } -// Creates a Certificate object that may be passed to the SignCertificate -// method to generate an X509 certificate. -// Returns NULL if an error is encountered in the certificate creation -// process. -// Caller responsible for freeing returned certificate object. -static CERTCertificate* CreateCertificate( - crypto::RSAPrivateKey* key, - const std::string& subject, - uint32 serial_number, - base::TimeDelta valid_duration) { - // Create info about public key. - CERTSubjectPublicKeyInfo* spki = - SECKEY_CreateSubjectPublicKeyInfo(key->public_key()); - if (!spki) - return NULL; - - // Create the certificate request. - CERTName* subject_name = - CERT_AsciiToName(const_cast<char*>(subject.c_str())); - CERTCertificateRequest* cert_request = - CERT_CreateCertificateRequest(subject_name, spki, NULL); - SECKEY_DestroySubjectPublicKeyInfo(spki); - - if (!cert_request) { - PRErrorCode prerr = PR_GetError(); - LOG(ERROR) << "Failed to create certificate request: " << prerr; - CERT_DestroyName(subject_name); - return NULL; - } - - PRTime now = PR_Now(); - PRTime not_after = now + valid_duration.InMicroseconds(); - - // Note that the time is now in micro-second unit. - CERTValidity* validity = CERT_CreateValidity(now, not_after); - CERTCertificate* cert = CERT_CreateCertificate(serial_number, subject_name, - validity, cert_request); - if (!cert) { - PRErrorCode prerr = PR_GetError(); - LOG(ERROR) << "Failed to create certificate: " << prerr; - } - - // Cleanup for resources used to generate the cert. - CERT_DestroyName(subject_name); - CERT_DestroyValidity(validity); - CERT_DestroyCertificateRequest(cert_request); - - return cert; -} - -// Signs a certificate object, with |key| generating a new X509Certificate -// and destroying the passed certificate object (even when NULL is returned). -// The logic of this method references SignCert() in NSS utility certutil: -// http://mxr.mozilla.org/security/ident?i=SignCert. -// Returns NULL if an error is encountered in the certificate signing -// process. -// Caller responsible for freeing returned X509Certificate object. -// -// TODO: change this function to return -// a success/failure status, and not create an X509Certificate -// object, and not destroy |cert| on failure. Let the caller -// create the X509Certificate object and destroy |cert|. -static X509Certificate* SignCertificate( - CERTCertificate* cert, - crypto::RSAPrivateKey* key) { - // |arena| is used to encode the cert. - PRArenaPool* arena = cert->arena; - SECOidTag algo_id = SEC_GetSignatureAlgorithmOidTag(key->key()->keyType, - SEC_OID_SHA1); - if (algo_id == SEC_OID_UNKNOWN) { - CERT_DestroyCertificate(cert); - return NULL; - } - - SECStatus rv = SECOID_SetAlgorithmID(arena, &cert->signature, algo_id, 0); - if (rv != SECSuccess) { - CERT_DestroyCertificate(cert); - return NULL; - } - - // Generate a cert of version 3. - *(cert->version.data) = 2; - cert->version.len = 1; - - SECItem der; - der.len = 0; - der.data = NULL; - - // Use ASN1 DER to encode the cert. - void* encode_result = SEC_ASN1EncodeItem( - arena, &der, cert, SEC_ASN1_GET(CERT_CertificateTemplate)); - if (!encode_result) { - CERT_DestroyCertificate(cert); - return NULL; - } - - // Allocate space to contain the signed cert. - SECItem* result = SECITEM_AllocItem(arena, NULL, 0); - if (!result) { - CERT_DestroyCertificate(cert); - return NULL; - } - - // Sign the ASN1 encoded cert and save it to |result|. - rv = SEC_DerSignData(arena, result, der.data, der.len, key->key(), algo_id); - if (rv != SECSuccess) { - CERT_DestroyCertificate(cert); - return NULL; - } - - // Save the signed result to the cert. - cert->derCert = *result; - - X509Certificate* x509_cert = - X509Certificate::CreateFromHandle(cert, X509Certificate::OSCertHandles()); - CERT_DestroyCertificate(cert); - return x509_cert; -} - // static X509Certificate* X509Certificate::CreateSelfSigned( crypto::RSAPrivateKey* key, @@ -795,76 +631,17 @@ X509Certificate* X509Certificate::CreateSelfSigned( base::TimeDelta valid_duration) { DCHECK(key); - CERTCertificate* cert = CreateCertificate(key, - subject, - serial_number, - valid_duration); + CERTCertificate* cert = x509_util::CreateSelfSignedCert(key->public_key(), + key->key(), + subject, + serial_number, + valid_duration); if (!cert) return NULL; - X509Certificate* x509_cert = SignCertificate(cert, key); - - return x509_cert; -} - -// static -X509Certificate* X509Certificate::CreateOriginBound( - crypto::RSAPrivateKey* key, - const std::string& origin, - uint32 serial_number, - base::TimeDelta valid_duration) { - DCHECK(key); - - CERTCertificate* cert = CreateCertificate(key, - "CN=anonymous.invalid", - serial_number, - valid_duration); - - if (!cert) - return NULL; - - // Create opaque handle used to add extensions later. - void* cert_handle; - if ((cert_handle = CERT_StartCertExtensions(cert)) == NULL) { - LOG(ERROR) << "Unable to get opaque handle for adding extensions"; - return NULL; - } - - // Create SECItem for IA5String encoding. - SECItem origin_string_item = { - siAsciiString, - (unsigned char*)origin.data(), - origin.size() - }; - - // IA5Encode and arena allocate SECItem - SECItem* asn1_origin_string = SEC_ASN1EncodeItem( - cert->arena, NULL, &origin_string_item, - SEC_ASN1_GET(SEC_IA5StringTemplate)); - if (asn1_origin_string == NULL) { - LOG(ERROR) << "Unable to get ASN1 encoding for origin in ob_cert extension"; - return NULL; - } - - // Add the extension to the opaque handle - if (CERT_AddExtension(cert_handle, - ObCertOIDWrapper::GetInstance()->ob_cert_oid_tag(), - asn1_origin_string, - PR_TRUE, PR_TRUE) != SECSuccess){ - LOG(ERROR) << "Unable to add origin bound cert extension to opaque handle"; - return NULL; - } - - // Copy extension into x509 cert - if (CERT_FinishExtensions(cert_handle) != SECSuccess){ - LOG(ERROR) << "Unable to copy extension to X509 cert"; - return NULL; - } - - X509Certificate* x509_cert = SignCertificate(cert, key); - - return x509_cert; + return X509Certificate::CreateFromHandle(cert, + X509Certificate::OSCertHandles()); } void X509Certificate::GetSubjectAltName( diff --git a/net/base/x509_certificate_openssl.cc b/net/base/x509_certificate_openssl.cc index 0092577..80e2517 100644 --- a/net/base/x509_certificate_openssl.cc +++ b/net/base/x509_certificate_openssl.cc @@ -409,17 +409,6 @@ X509Certificate* X509Certificate::CreateSelfSigned( return NULL; } -// static -X509Certificate* X509Certificate::CreateOriginBound( - crypto::RSAPrivateKey* key, - const std::string& origin, - uint32 serial_number, - base::TimeDelta valid_duration) { - // TODO(port): Implement. - NOTIMPLEMENTED(); - return NULL; -} - void X509Certificate::GetSubjectAltName( std::vector<std::string>* dns_names, std::vector<std::string>* ip_addrs) const { diff --git a/net/base/x509_certificate_unittest.cc b/net/base/x509_certificate_unittest.cc index f0d3339..9ba1124 100644 --- a/net/base/x509_certificate_unittest.cc +++ b/net/base/x509_certificate_unittest.cc @@ -22,7 +22,6 @@ #if defined(USE_NSS) #include <cert.h> -#include <secoid.h> #endif // Unit tests aren't allowed to access external resources. Unfortunately, to @@ -1178,84 +1177,6 @@ TEST(X509CertificateTest, GetDEREncoded) { } #endif -#if defined(USE_NSS) -// This test creates an origin-bound cert from a private key and -// then verifies the content of the certificate. -TEST(X509CertificateTest, CreateOriginBound) { - // Origin Bound Cert OID. - static const char oid_string[] = "1.3.6.1.4.1.11129.2.1.6"; - - // Create a sample ASCII weborigin. - std::string origin = "http://weborigin.com:443"; - - // Create object neccissary for extension lookup call. - SECItem extension_object = { - siAsciiString, - (unsigned char*)origin.data(), - origin.size() - }; - - scoped_ptr<crypto::RSAPrivateKey> private_key( - crypto::RSAPrivateKey::Create(1024)); - scoped_refptr<X509Certificate> cert = - X509Certificate::CreateOriginBound(private_key.get(), - origin, 1, - base::TimeDelta::FromDays(1)); - - EXPECT_EQ("anonymous.invalid", cert->subject().GetDisplayName()); - EXPECT_FALSE(cert->HasExpired()); - - // IA5Encode and arena allocate SECItem. - PLArenaPool* arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); - SECItem* expected = SEC_ASN1EncodeItem(arena, - NULL, - &extension_object, - SEC_ASN1_GET(SEC_IA5StringTemplate)); - - ASSERT_NE(static_cast<SECItem*>(NULL), expected); - - // Create OID SECItem. - SECItem ob_cert_oid = { siDEROID, NULL, 0 }; - SECStatus ok = SEC_StringToOID(arena, &ob_cert_oid, - oid_string, 0); - - ASSERT_EQ(SECSuccess, ok); - - SECOidTag ob_cert_oid_tag = SECOID_FindOIDTag(&ob_cert_oid); - - ASSERT_NE(SEC_OID_UNKNOWN, ob_cert_oid_tag); - - // Lookup Origin Bound Cert extension in generated cert. - SECItem actual = { siBuffer, NULL, 0 }; - ok = CERT_FindCertExtension(cert->os_cert_handle(), - ob_cert_oid_tag, - &actual); - ASSERT_EQ(SECSuccess, ok); - - // Compare expected and actual extension values. - PRBool result = SECITEM_ItemsAreEqual(expected, &actual); - ASSERT_TRUE(result); - - // Do Cleanup. - SECITEM_FreeItem(&actual, PR_FALSE); - PORT_FreeArena(arena, PR_FALSE); -} -#else // defined(USE_NSS) -// On other platforms, X509Certificate::CreateOriginBound() is not implemented -// and should return NULL. This unit test ensures that a stub implementation -// is present. -TEST(X509CertificateTest, CreateOriginBoundNotImplemented) { - std::string origin = "http://weborigin.com:443"; - scoped_ptr<crypto::RSAPrivateKey> private_key( - crypto::RSAPrivateKey::Create(1024)); - scoped_refptr<X509Certificate> cert = - X509Certificate::CreateOriginBound(private_key.get(), - origin, 2, - base::TimeDelta::FromDays(1)); - EXPECT_FALSE(cert); -} -#endif // defined(USE_NSS) - class X509CertificateParseTest : public testing::TestWithParam<CertificateFormatTestData> { public: diff --git a/net/base/x509_certificate_win.cc b/net/base/x509_certificate_win.cc index 1336f8c..5c53a15 100644 --- a/net/base/x509_certificate_win.cc +++ b/net/base/x509_certificate_win.cc @@ -630,16 +630,6 @@ X509Certificate* X509Certificate::CreateSelfSigned( return cert; } -// static -X509Certificate* X509Certificate::CreateOriginBound( - crypto::RSAPrivateKey* key, - const std::string& origin, - uint32 serial_number, - base::TimeDelta valid_duration) { - NOTIMPLEMENTED(); - return NULL; -} - void X509Certificate::GetSubjectAltName( std::vector<std::string>* dns_names, std::vector<std::string>* ip_addrs) const { diff --git a/net/base/x509_util.h b/net/base/x509_util.h new file mode 100644 index 0000000..d110c939 --- /dev/null +++ b/net/base/x509_util.h @@ -0,0 +1,39 @@ +// Copyright (c) 2011 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_BASE_X509_UTIL_H_ +#define NET_BASE_X509_UTIL_H_ +#pragma once + +#include <string> + +#include "base/time.h" +#include "net/base/net_export.h" + +namespace crypto { +class RSAPrivateKey; +} + +namespace net { + +namespace x509_util { + +// Creates an origin bound certificate containing the public key in |key|. +// Web origin, serial number and validity period are given as +// parameters. The certificate is signed by the private key in |key|. +// The hashing algorithm for the signature is SHA-1. +// +// See Internet Draft draft-balfanz-tls-obc-00 for more details: +// http://tools.ietf.org/html/draft-balfanz-tls-obc-00 +bool NET_EXPORT_PRIVATE CreateOriginBoundCert(crypto::RSAPrivateKey* key, + const std::string& origin, + uint32 serial_number, + base::TimeDelta valid_duration, + std::string* der_cert); + +} // namespace x509_util + +} // namespace net + +#endif // NET_BASE_X509_UTIL_H_ diff --git a/net/base/x509_util_nss.cc b/net/base/x509_util_nss.cc new file mode 100644 index 0000000..fe3fb17 --- /dev/null +++ b/net/base/x509_util_nss.cc @@ -0,0 +1,318 @@ +// Copyright (c) 2011 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/x509_util.h" +#include "net/base/x509_util_nss.h" + +#include <cert.h> +#include <cryptohi.h> +#include <pk11pub.h> +#include <prerror.h> +#include <secmod.h> +#include <secport.h> + +#include "base/debug/leak_annotations.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "crypto/nss_util.h" +#include "crypto/nss_util_internal.h" +#include "crypto/rsa_private_key.h" +#include "crypto/scoped_nss_types.h" + +namespace { + +class ObCertOIDWrapper { + public: + static ObCertOIDWrapper* GetInstance() { + // Instantiated as a leaky singleton to allow the singleton to be + // constructed on a worker thead that is not joined when a process + // shuts down. + return Singleton<ObCertOIDWrapper, + LeakySingletonTraits<ObCertOIDWrapper> >::get(); + } + + SECOidTag ob_cert_oid_tag() const { + return ob_cert_oid_tag_; + } + + private: + friend struct DefaultSingletonTraits<ObCertOIDWrapper>; + + ObCertOIDWrapper(); + + SECOidTag ob_cert_oid_tag_; + + DISALLOW_COPY_AND_ASSIGN(ObCertOIDWrapper); +}; + +ObCertOIDWrapper::ObCertOIDWrapper(): ob_cert_oid_tag_(SEC_OID_UNKNOWN) { + // 1.3.6.1.4.1.11129.2.1.6 + // (iso.org.dod.internet.private.enterprises.google.googleSecurity. + // certificateExtensions.originBoundCertificate) + static const uint8 kObCertOID[] = { + 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x06 + }; + SECOidData oid_data; + memset(&oid_data, 0, sizeof(oid_data)); + oid_data.oid.data = const_cast<uint8*>(kObCertOID); + oid_data.oid.len = sizeof(kObCertOID); + oid_data.offset = SEC_OID_UNKNOWN; + oid_data.desc = "Origin Bound Certificate"; + oid_data.mechanism = CKM_INVALID_MECHANISM; + oid_data.supportedExtension = SUPPORTED_CERT_EXTENSION; + ob_cert_oid_tag_ = SECOID_AddEntry(&oid_data); + if (ob_cert_oid_tag_ == SEC_OID_UNKNOWN) + LOG(ERROR) << "OB_CERT OID tag creation failed"; +} + +// Creates a Certificate object that may be passed to the SignCertificate +// method to generate an X509 certificate. +// Returns NULL if an error is encountered in the certificate creation +// process. +// Caller responsible for freeing returned certificate object. +CERTCertificate* CreateCertificate( + SECKEYPublicKey* public_key, + const std::string& subject, + uint32 serial_number, + base::TimeDelta valid_duration) { + // Create info about public key. + CERTSubjectPublicKeyInfo* spki = + SECKEY_CreateSubjectPublicKeyInfo(public_key); + if (!spki) + return NULL; + + // Create the certificate request. + CERTName* subject_name = + CERT_AsciiToName(const_cast<char*>(subject.c_str())); + CERTCertificateRequest* cert_request = + CERT_CreateCertificateRequest(subject_name, spki, NULL); + SECKEY_DestroySubjectPublicKeyInfo(spki); + + if (!cert_request) { + PRErrorCode prerr = PR_GetError(); + LOG(ERROR) << "Failed to create certificate request: " << prerr; + CERT_DestroyName(subject_name); + return NULL; + } + + PRTime now = PR_Now(); + PRTime not_after = now + valid_duration.InMicroseconds(); + + // Note that the time is now in micro-second unit. + CERTValidity* validity = CERT_CreateValidity(now, not_after); + CERTCertificate* cert = CERT_CreateCertificate(serial_number, subject_name, + validity, cert_request); + if (!cert) { + PRErrorCode prerr = PR_GetError(); + LOG(ERROR) << "Failed to create certificate: " << prerr; + } + + // Cleanup for resources used to generate the cert. + CERT_DestroyName(subject_name); + CERT_DestroyValidity(validity); + CERT_DestroyCertificateRequest(cert_request); + + return cert; +} + +// Signs a certificate object, with |key| generating a new X509Certificate +// and destroying the passed certificate object (even when NULL is returned). +// The logic of this method references SignCert() in NSS utility certutil: +// http://mxr.mozilla.org/security/ident?i=SignCert. +// Returns true on success or false if an error is encountered in the +// certificate signing process. +bool SignCertificate( + CERTCertificate* cert, + SECKEYPrivateKey* key) { + // |arena| is used to encode the cert. + PLArenaPool* arena = cert->arena; + SECOidTag algo_id = SEC_GetSignatureAlgorithmOidTag(key->keyType, + SEC_OID_SHA1); + if (algo_id == SEC_OID_UNKNOWN) + return false; + + SECStatus rv = SECOID_SetAlgorithmID(arena, &cert->signature, algo_id, 0); + if (rv != SECSuccess) + return false; + + // Generate a cert of version 3. + *(cert->version.data) = 2; + cert->version.len = 1; + + SECItem der; + der.len = 0; + der.data = NULL; + + // Use ASN1 DER to encode the cert. + void* encode_result = SEC_ASN1EncodeItem( + arena, &der, cert, SEC_ASN1_GET(CERT_CertificateTemplate)); + if (!encode_result) + return false; + + // Allocate space to contain the signed cert. + SECItem* result = SECITEM_AllocItem(arena, NULL, 0); + if (!result) + return false; + + // Sign the ASN1 encoded cert and save it to |result|. + rv = SEC_DerSignData(arena, result, der.data, der.len, key, algo_id); + if (rv != SECSuccess) + return false; + + // Save the signed result to the cert. + cert->derCert = *result; + + return true; +} + +} // namespace + +namespace net { + +namespace x509_util { + +CERTCertificate* CreateSelfSignedCert( + SECKEYPublicKey* public_key, + SECKEYPrivateKey* private_key, + const std::string& subject, + uint32 serial_number, + base::TimeDelta valid_duration) { + CERTCertificate* cert = CreateCertificate(public_key, + subject, + serial_number, + valid_duration); + if (!cert) + return NULL; + + if (!SignCertificate(cert, private_key)) { + CERT_DestroyCertificate(cert); + return NULL; + } + + return cert; +} + +bool CreateOriginBoundCert( + crypto::RSAPrivateKey* key, + const std::string& origin, + uint32 serial_number, + base::TimeDelta valid_duration, + std::string* der_cert) { + DCHECK(key); + + SECKEYPublicKey* public_key; + SECKEYPrivateKey* private_key; +#if defined(USE_NSS) + public_key = key->public_key(); + private_key = key->key(); +#else + crypto::ScopedSECKEYPublicKey scoped_public_key; + crypto::ScopedSECKEYPrivateKey scoped_private_key; + { + // Based on the NSS RSAPrivateKey::CreateFromPrivateKeyInfoWithParams. + // This method currently leaks some memory. + // See http://crbug.com/34742. + ANNOTATE_SCOPED_MEMORY_LEAK; + crypto::EnsureNSSInit(); + + std::vector<uint8> key_data; + key->ExportPrivateKey(&key_data); + + crypto::ScopedPK11Slot slot(crypto::GetPrivateNSSKeySlot()); + if (!slot.get()) + return NULL; + + SECItem der_private_key_info; + der_private_key_info.data = const_cast<unsigned char*>(&key_data[0]); + der_private_key_info.len = key_data.size(); + // Allow the private key to be used for key unwrapping, data decryption, + // and signature generation. + const unsigned int key_usage = KU_KEY_ENCIPHERMENT | KU_DATA_ENCIPHERMENT | + KU_DIGITAL_SIGNATURE; + SECStatus rv = PK11_ImportDERPrivateKeyInfoAndReturnKey( + slot.get(), &der_private_key_info, NULL, NULL, PR_FALSE, PR_FALSE, + key_usage, &private_key, NULL); + scoped_private_key.reset(private_key); + if (rv != SECSuccess) { + NOTREACHED(); + return NULL; + } + + public_key = SECKEY_ConvertToPublicKey(private_key); + if (!public_key) { + NOTREACHED(); + return NULL; + } + scoped_public_key.reset(public_key); + } +#endif + + CERTCertificate* cert = CreateCertificate(public_key, + "CN=anonymous.invalid", + serial_number, + valid_duration); + + if (!cert) + return false; + + // Create opaque handle used to add extensions later. + void* cert_handle; + if ((cert_handle = CERT_StartCertExtensions(cert)) == NULL) { + LOG(ERROR) << "Unable to get opaque handle for adding extensions"; + CERT_DestroyCertificate(cert); + return false; + } + + // Create SECItem for IA5String encoding. + SECItem origin_string_item = { + siAsciiString, + (unsigned char*)origin.data(), + origin.size() + }; + + // IA5Encode and arena allocate SECItem + SECItem* asn1_origin_string = SEC_ASN1EncodeItem( + cert->arena, NULL, &origin_string_item, + SEC_ASN1_GET(SEC_IA5StringTemplate)); + if (asn1_origin_string == NULL) { + LOG(ERROR) << "Unable to get ASN1 encoding for origin in ob_cert extension"; + CERT_DestroyCertificate(cert); + return false; + } + + // Add the extension to the opaque handle + if (CERT_AddExtension(cert_handle, + ObCertOIDWrapper::GetInstance()->ob_cert_oid_tag(), + asn1_origin_string, + PR_TRUE, PR_TRUE) != SECSuccess){ + LOG(ERROR) << "Unable to add origin bound cert extension to opaque handle"; + CERT_DestroyCertificate(cert); + return false; + } + + // Copy extension into x509 cert + if (CERT_FinishExtensions(cert_handle) != SECSuccess){ + LOG(ERROR) << "Unable to copy extension to X509 cert"; + CERT_DestroyCertificate(cert); + return false; + } + + if (!SignCertificate(cert, private_key)) { + CERT_DestroyCertificate(cert); + return false; + } + + DCHECK(cert->derCert.len); + // XXX copied from X509Certificate::GetDEREncoded + der_cert->clear(); + der_cert->append(reinterpret_cast<char*>(cert->derCert.data), + cert->derCert.len); + CERT_DestroyCertificate(cert); + return true; +} + +} // namespace x509_util + +} // namespace net diff --git a/net/base/x509_util_nss.h b/net/base/x509_util_nss.h new file mode 100644 index 0000000..82dd4f9 --- /dev/null +++ b/net/base/x509_util_nss.h @@ -0,0 +1,37 @@ +// Copyright (c) 2011 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_BASE_X509_UTIL_NSS_H_ +#define NET_BASE_X509_UTIL_NSS_H_ +#pragma once + +#include <string> + +#include "base/time.h" + +typedef struct CERTCertificateStr CERTCertificate; +typedef struct SECKEYPrivateKeyStr SECKEYPrivateKey; +typedef struct SECKEYPublicKeyStr SECKEYPublicKey; + + +namespace net { + +namespace x509_util { + +// Creates a self-signed certificate containing |public_key|. Subject, serial +// number and validity period are given as parameters. The certificate is +// signed by |private_key|. The hashing algorithm for the signature is SHA-1. +// |subject| is a distinguished name defined in RFC4514. +CERTCertificate* CreateSelfSignedCert( + SECKEYPublicKey* public_key, + SECKEYPrivateKey* private_key, + const std::string& subject, + uint32 serial_number, + base::TimeDelta valid_duration); + +} // namespace x509_util + +} // namespace net + +#endif // NET_BASE_X509_UTIL_NSS_H_ diff --git a/net/base/x509_util_nss_unittest.cc b/net/base/x509_util_nss_unittest.cc new file mode 100644 index 0000000..79146b6 --- /dev/null +++ b/net/base/x509_util_nss_unittest.cc @@ -0,0 +1,106 @@ +// Copyright (c) 2011 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/x509_util.h" +#include "net/base/x509_util_nss.h" + +#include <cert.h> +#include <secoid.h> + +#include "base/memory/scoped_ptr.h" +#include "base/memory/ref_counted.h" +#include "crypto/rsa_private_key.h" +#include "net/base/x509_certificate.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +CERTCertificate* CreateNSSCertHandleFromBytes(const char* data, size_t length) { + SECItem der_cert; + der_cert.data = reinterpret_cast<unsigned char*>(const_cast<char*>(data)); + der_cert.len = length; + der_cert.type = siDERCertBuffer; + + // Parse into a certificate structure. + return CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &der_cert, NULL, + PR_FALSE, PR_TRUE); +} + +} // namespace + +namespace net { + +// This test creates an origin-bound cert from a private key and +// then verifies the content of the certificate. +TEST(X509UtilNSSTest, CreateOriginBoundCert) { + // Origin Bound Cert OID. + static const char oid_string[] = "1.3.6.1.4.1.11129.2.1.6"; + + // Create a sample ASCII weborigin. + std::string origin = "http://weborigin.com:443"; + + // Create object neccessary for extension lookup call. + SECItem extension_object = { + siAsciiString, + (unsigned char*)origin.data(), + origin.size() + }; + + scoped_ptr<crypto::RSAPrivateKey> private_key( + crypto::RSAPrivateKey::Create(1024)); + std::string der_cert; + ASSERT_TRUE(x509_util::CreateOriginBoundCert(private_key.get(), + origin, 1, + base::TimeDelta::FromDays(1), + &der_cert)); + + scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromBytes( + der_cert.data(), der_cert.size()); + + EXPECT_EQ("anonymous.invalid", cert->subject().GetDisplayName()); + EXPECT_FALSE(cert->HasExpired()); + + // IA5Encode and arena allocate SECItem. + PLArenaPool* arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + SECItem* expected = SEC_ASN1EncodeItem(arena, + NULL, + &extension_object, + SEC_ASN1_GET(SEC_IA5StringTemplate)); + + ASSERT_NE(static_cast<SECItem*>(NULL), expected); + + // Create OID SECItem. + SECItem ob_cert_oid = { siDEROID, NULL, 0 }; + SECStatus ok = SEC_StringToOID(arena, &ob_cert_oid, + oid_string, 0); + + ASSERT_EQ(SECSuccess, ok); + + SECOidTag ob_cert_oid_tag = SECOID_FindOIDTag(&ob_cert_oid); + + ASSERT_NE(SEC_OID_UNKNOWN, ob_cert_oid_tag); + + // This test is run on Mac and Win where X509Certificate::os_cert_handle isn't + // an NSS type, so we have to manually create a NSS certificate object so we + // can use CERT_FindCertExtension. + CERTCertificate* nss_cert = CreateNSSCertHandleFromBytes( + der_cert.data(), der_cert.size()); + // Lookup Origin Bound Cert extension in generated cert. + SECItem actual = { siBuffer, NULL, 0 }; + ok = CERT_FindCertExtension(nss_cert, + ob_cert_oid_tag, + &actual); + CERT_DestroyCertificate(nss_cert); + ASSERT_EQ(SECSuccess, ok); + + // Compare expected and actual extension values. + PRBool result = SECITEM_ItemsAreEqual(expected, &actual); + ASSERT_TRUE(result); + + // Do Cleanup. + SECITEM_FreeItem(&actual, PR_FALSE); + PORT_FreeArena(arena, PR_FALSE); +} + +} // namespace net diff --git a/net/base/x509_util_openssl.cc b/net/base/x509_util_openssl.cc index 1631d6f..8e4fb27 100644 --- a/net/base/x509_util_openssl.cc +++ b/net/base/x509_util_openssl.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "net/base/x509_util.h" #include "net/base/x509_util_openssl.h" #include <algorithm> @@ -14,6 +15,16 @@ namespace net { namespace x509_util { +bool CreateOriginBoundCert( + crypto::RSAPrivateKey* key, + const std::string& origin, + uint32 serial_number, + base::TimeDelta valid_duration, + std::string* der_cert) { + NOTIMPLEMENTED(); + return false; +} + bool ParsePrincipalKeyAndValueByIndex(X509_NAME* name, int index, std::string* key, diff --git a/net/base/x509_util_openssl_unittest.cc b/net/base/x509_util_openssl_unittest.cc new file mode 100644 index 0000000..0647a62 --- /dev/null +++ b/net/base/x509_util_openssl_unittest.cc @@ -0,0 +1,29 @@ +// Copyright (c) 2011 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 "base/memory/scoped_ptr.h" +#include "crypto/rsa_private_key.h" +#include "net/base/x509_util.h" +#include "net/base/x509_util_openssl.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +// For OpenSSL, x509_util::CreateOriginBoundCert() is not yet implemented +// and should return false. This unit test ensures that a stub implementation +// is present. +TEST(X509UtilOpenSSLTest, CreateOriginBoundCertNotImplemented) { + std::string origin = "http://weborigin.com:443"; + scoped_ptr<crypto::RSAPrivateKey> private_key( + crypto::RSAPrivateKey::Create(1024)); + std::string der_cert; + EXPECT_FALSE(x509_util::CreateOriginBoundCert(private_key.get(), + origin, 1, + base::TimeDelta::FromDays(1), + &der_cert)); + EXPECT_TRUE(der_cert.empty()); + +} + +} // namespace net |