diff options
author | wtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-01 18:49:47 +0000 |
---|---|---|
committer | wtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-01 18:49:47 +0000 |
commit | 9aed77b6dace047bff676a7cdcfef345a858d1a7 (patch) | |
tree | fddd5e0fac4ee39b6c79af56871f022472f276d9 | |
parent | 2779491e15b4a157b6351e20bb0f2dd26f6d84de (diff) | |
download | chromium_src-9aed77b6dace047bff676a7cdcfef345a858d1a7.zip chromium_src-9aed77b6dace047bff676a7cdcfef345a858d1a7.tar.gz chromium_src-9aed77b6dace047bff676a7cdcfef345a858d1a7.tar.bz2 |
Adds support for the <keygen> element to Windows, matching
support present on Linux and Mac OS X.
Contributed by Ryan Sleevi <ryan.sleevi@gmail.com>.
Original review URL: http://codereview.chromium.org/843005
R=wtc
BUG=148
TEST=KeygenHandler.SmokeTest
Review URL: http://codereview.chromium.org/1591006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@43365 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | AUTHORS | 1 | ||||
-rw-r--r-- | net/base/cert_database.h | 7 | ||||
-rw-r--r-- | net/base/cert_database_mac.cc | 10 | ||||
-rw-r--r-- | net/base/cert_database_nss.cc | 11 | ||||
-rw-r--r-- | net/base/cert_database_win.cc | 110 | ||||
-rw-r--r-- | net/base/keygen_handler.cc | 36 | ||||
-rw-r--r-- | net/base/keygen_handler.h | 54 | ||||
-rw-r--r-- | net/base/keygen_handler_mac.cc | 11 | ||||
-rw-r--r-- | net/base/keygen_handler_nss.cc | 29 | ||||
-rw-r--r-- | net/base/keygen_handler_unittest.cc | 42 | ||||
-rw-r--r-- | net/base/keygen_handler_win.cc | 358 | ||||
-rw-r--r-- | net/base/net_error_list.h | 2 | ||||
-rw-r--r-- | net/net.gyp | 5 |
13 files changed, 633 insertions, 43 deletions
@@ -72,3 +72,4 @@ Jay Soffian <jaysoffian@gmail.com> Brian G. Merrell <bgmerrell@gmail.com> Matthew Willis <appamatto@gmail.com> Novell Inc. +Ryan Sleevi <ryan.sleevi@gmail.com> diff --git a/net/base/cert_database.h b/net/base/cert_database.h index 3eb2836..31e3401 100644 --- a/net/base/cert_database.h +++ b/net/base/cert_database.h @@ -1,14 +1,16 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 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_CERT_DATABASE_H_ #define NET_BASE_CERT_DATABASE_H_ -#include "net/base/x509_certificate.h" +#include "base/basictypes.h" namespace net { +class X509Certificate; + // This class provides functions to manipulate the local // certificate store. @@ -30,7 +32,6 @@ class CertDatabase { int AddUserCert(X509Certificate* cert); private: - void Init(); DISALLOW_COPY_AND_ASSIGN(CertDatabase); }; diff --git a/net/base/cert_database_mac.cc b/net/base/cert_database_mac.cc index 96ab9e5..2fa3340 100644 --- a/net/base/cert_database_mac.cc +++ b/net/base/cert_database_mac.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 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. @@ -8,15 +8,13 @@ #include "base/logging.h" #include "net/base/net_errors.h" +#include "net/base/x509_certificate.h" namespace net { CertDatabase::CertDatabase() { } -void CertDatabase::Init() { -} - int CertDatabase::CheckUserCert(X509Certificate* cert) { if (!cert) return ERR_CERT_INVALID; @@ -44,14 +42,14 @@ int CertDatabase::CheckUserCert(X509Certificate* cert) { int CertDatabase::AddUserCert(X509Certificate* cert) { OSStatus err = SecCertificateAddToKeychain(cert->os_cert_handle(), NULL); - switch(err) { + switch (err) { case noErr: case errSecDuplicateItem: return OK; default: LOG(ERROR) << "CertDatabase failed to add cert to keychain: " << err; // TODO(snej): Map the error code more intelligently. - return ERR_ERR_ADD_USER_CERT_FAILED; + return ERR_ADD_USER_CERT_FAILED; } } diff --git a/net/base/cert_database_nss.cc b/net/base/cert_database_nss.cc index 636224f..0701e6a 100644 --- a/net/base/cert_database_nss.cc +++ b/net/base/cert_database_nss.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 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. @@ -16,11 +16,12 @@ #include "base/scoped_ptr.h" #include "base/nss_util.h" #include "net/base/net_errors.h" +#include "net/base/x509_certificate.h" namespace net { CertDatabase::CertDatabase() { - Init(); + base::EnsureNSSInit(); } int CertDatabase::CheckUserCert(X509Certificate* cert_obj) { @@ -74,14 +75,10 @@ int CertDatabase::AddUserCert(X509Certificate* cert_obj) { NULL); if (!slot) { LOG(ERROR) << "Couldn't import user certificate."; - return ERR_ERR_ADD_USER_CERT_FAILED; + return ERR_ADD_USER_CERT_FAILED; } PK11_FreeSlot(slot); return OK; } -void CertDatabase::Init() { - base::EnsureNSSInit(); -} - } // namespace net diff --git a/net/base/cert_database_win.cc b/net/base/cert_database_win.cc index 7e9d862..34485b5 100644 --- a/net/base/cert_database_win.cc +++ b/net/base/cert_database_win.cc @@ -1,30 +1,122 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 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/cert_database.h" +#include <windows.h> +#include <wincrypt.h> +#pragma comment(lib, "crypt32.lib") + #include "base/logging.h" +#include "base/string_util.h" +#include "net/base/keygen_handler.h" #include "net/base/net_errors.h" +#include "net/base/x509_certificate.h" namespace net { +namespace { + +// Returns an encoded version of SubjectPublicKeyInfo from |cert| that is +// compatible with KeygenHandler::Cache. If the cert cannot be converted, an +// empty string is returned. +std::string GetSubjectPublicKeyInfo(const X509Certificate* cert) { + DCHECK(cert); + + std::string result; + if (!cert->os_cert_handle() || !cert->os_cert_handle()->pCertInfo) + return result; + + BOOL ok; + DWORD size = 0; + PCERT_PUBLIC_KEY_INFO key_info = + &(cert->os_cert_handle()->pCertInfo->SubjectPublicKeyInfo); + ok = CryptEncodeObject(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, key_info, + NULL, &size); + if (!ok) + return result; + + ok = CryptEncodeObject(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, key_info, + reinterpret_cast<BYTE*>(WriteInto(&result, size + 1)), + &size); + if (!ok) { + result.clear(); + return result; + } + + // Per MSDN, the resultant structure may be smaller than the original size + // supplied, so shrink to the actual size output. + result.resize(size); + + return result; +} + +// Returns true if |cert| was successfully modified to reference |location| to +// obtain the associated private key. +bool LinkCertToPrivateKey(X509Certificate* cert, + KeygenHandler::KeyLocation location) { + DCHECK(cert); + + CRYPT_KEY_PROV_INFO prov_info = { 0 }; + prov_info.pwszContainerName = + const_cast<LPWSTR>(location.container_name.c_str()); + prov_info.pwszProvName = + const_cast<LPWSTR>(location.provider_name.c_str()); + + // Implicit by it being from KeygenHandler, which only supports RSA keys. + prov_info.dwProvType = PROV_RSA_FULL; + prov_info.dwKeySpec = AT_KEYEXCHANGE; + + BOOL ok = CertSetCertificateContextProperty(cert->os_cert_handle(), + CERT_KEY_PROV_INFO_PROP_ID, 0, + &prov_info); + return ok != FALSE; +} + +} // namespace + CertDatabase::CertDatabase() { - NOTIMPLEMENTED(); } int CertDatabase::CheckUserCert(X509Certificate* cert) { - NOTIMPLEMENTED(); - return ERR_NOT_IMPLEMENTED; + if (!cert) + return ERR_CERT_INVALID; + if (cert->HasExpired()) + return ERR_CERT_DATE_INVALID; + + std::string encoded_info = GetSubjectPublicKeyInfo(cert); + KeygenHandler::Cache* cache = KeygenHandler::Cache::GetInstance(); + KeygenHandler::KeyLocation location; + + if (encoded_info.empty() || !cache->Find(encoded_info, &location) || + !LinkCertToPrivateKey(cert, location)) + return ERR_NO_PRIVATE_KEY_FOR_CERT; + + return OK; } int CertDatabase::AddUserCert(X509Certificate* cert) { - NOTIMPLEMENTED(); - return ERR_NOT_IMPLEMENTED; -} + // TODO(rsleevi): Would it be more appropriate to have the CertDatabase take + // construction parameters (Keychain filepath on Mac OS X, PKCS #11 slot on + // NSS, and Store Type / Path) here? For now, certs will be stashed into the + // user's personal store, which will not automatically mark them as trusted, + // but will allow them to be used for client auth. + HCERTSTORE cert_db = CertOpenSystemStore(NULL, L"MY"); + if (!cert_db) + return ERR_ADD_USER_CERT_FAILED; + + BOOL added = CertAddCertificateContextToStore(cert_db, + cert->os_cert_handle(), + CERT_STORE_ADD_USE_EXISTING, + NULL); + + CertCloseStore(cert_db, 0); + + if (!added) + return ERR_ADD_USER_CERT_FAILED; -void CertDatabase::Init() { - NOTIMPLEMENTED(); + return OK; } } // namespace net diff --git a/net/base/keygen_handler.cc b/net/base/keygen_handler.cc new file mode 100644 index 0000000..e85dc4d --- /dev/null +++ b/net/base/keygen_handler.cc @@ -0,0 +1,36 @@ +// Copyright (c) 2010 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/keygen_handler.h" + +#include "base/logging.h" + +namespace net { + +KeygenHandler::Cache* KeygenHandler::Cache::GetInstance() { + return Singleton<Cache>::get(); +} + +void KeygenHandler::Cache::Insert(const std::string& public_key_info, + const KeyLocation& location) { + AutoLock lock(lock_); + + DCHECK(!public_key_info.empty()) << "Only insert valid public key structures"; + cache_[public_key_info] = location; +} + +bool KeygenHandler::Cache::Find(const std::string& public_key_info, + KeyLocation* location) { + AutoLock lock(lock_); + + KeyLocationMap::iterator iter = cache_.find(public_key_info); + + if (iter == cache_.end()) + return false; + + *location = iter->second; + return true; +} + +} // namespace net diff --git a/net/base/keygen_handler.h b/net/base/keygen_handler.h index 1ed023e..f88ffd5 100644 --- a/net/base/keygen_handler.h +++ b/net/base/keygen_handler.h @@ -1,12 +1,16 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 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_KEYGEN_HANDLER_H_ #define NET_BASE_KEYGEN_HANDLER_H_ +#include <map> #include <string> +#include "base/lock.h" +#include "base/singleton.h" + namespace net { // This class handles keypair generation for generating client @@ -16,6 +20,54 @@ namespace net { class KeygenHandler { public: + // This class stores the relative location for a given private key. It does + // not store the private key, or a handle to the private key, on the basis + // that the key may be located on a smart card or device which may not be + // present at the time of retrieval. + class KeyLocation { + public: +#if defined(OS_WIN) + std::wstring container_name; + std::wstring provider_name; +#elif defined(OS_MACOSX) + std::string keychain_path; +#elif defined(USE_NSS) + std::string slot_name; +#endif + + // Only used by unit tests. + bool Equals(const KeyLocation& location) const; + }; + + // This class stores information about the keys the KeygenHandler has + // generated, so that the private keys can be properly associated with any + // certificates that might be sent to the client based on those keys. + // TODO(wtc): consider adding a Remove() method. + class Cache { + public: + static Cache* GetInstance(); + void Insert(const std::string& public_key_info, + const KeyLocation& location); + + // True if the |public_key_info| was located and the location stored into + // |*location|. + bool Find(const std::string& public_key_info, KeyLocation* location); + + private: + typedef std::map<std::string, KeyLocation> KeyLocationMap; + + // Obtain an instance of the KeyCache by using GetInstance(). + Cache() {} + friend struct DefaultSingletonTraits<Cache>; + + Lock lock_; + + // The key cache. You must obtain |lock_| before using |cache_|. + KeyLocationMap cache_; + + DISALLOW_COPY_AND_ASSIGN(Cache); + }; + // Creates a handler that will generate a key with the given key size // and incorporate the |challenge| into the Netscape SPKAC structure. inline KeygenHandler(int key_size_in_bits, const std::string& challenge); diff --git a/net/base/keygen_handler_mac.cc b/net/base/keygen_handler_mac.cc index e5fd619..35e4714 100644 --- a/net/base/keygen_handler_mac.cc +++ b/net/base/keygen_handler_mac.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 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. @@ -88,6 +88,11 @@ static OSStatus SignData(CSSM_DATA data, CSSM_DATA* signature); +bool KeygenHandler::KeyLocation::Equals( + const KeygenHandler::KeyLocation& location) const { + return keychain_path == location.keychain_path; +} + std::string KeygenHandler::GenKeyAndSignChallenge() { std::string result; OSStatus err; @@ -154,7 +159,7 @@ std::string KeygenHandler::GenKeyAndSignChallenge() { base::Base64Encode(input, &result); } -failure: + failure: if (err) { LOG(ERROR) << "SSL Keygen failed! OSStatus = " << err; } else { @@ -199,7 +204,7 @@ static OSStatus CreateRSAKeyPair(int size_in_bits, CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP, CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT, // private key usage and attributes: - CSSM_KEYUSE_DECRYPT | CSSM_KEYUSE_SIGN | CSSM_KEYUSE_UNWRAP, // private key + CSSM_KEYUSE_DECRYPT | CSSM_KEYUSE_SIGN | CSSM_KEYUSE_UNWRAP, CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_SENSITIVE, NULL, diff --git a/net/base/keygen_handler_nss.cc b/net/base/keygen_handler_nss.cc index d8d9acb..9819289 100644 --- a/net/base/keygen_handler_nss.cc +++ b/net/base/keygen_handler_nss.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 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. @@ -51,6 +51,21 @@ DERTemplate CERTPublicKeyAndChallengeTemplate[] = { { 0, } }; +void StoreKeyLocationInCache(const SECItem& public_key_info, + PK11SlotInfo *slot) { + KeygenHandler::Cache* cache = KeygenHandler::Cache::GetInstance(); + KeygenHandler::KeyLocation key_location; + const char* slot_name = PK11_GetSlotName(slot); + key_location.slot_name.assign(slot_name); + cache->Insert(std::string(reinterpret_cast<char*>(public_key_info.data), + public_key_info.len), key_location); +} + +bool KeygenHandler::KeyLocation::Equals( + const net::KeygenHandler::KeyLocation& location) const { + return slot_name == location.slot_name; +} + // This function is largely copied from the Firefox's // <keygen> implementation in security/manager/ssl/src/nsKeygenHandler.cpp // FIXME(gauravsh): Do we need a copy of the Mozilla license here? @@ -194,21 +209,23 @@ std::string KeygenHandler::GenKeyAndSignChallenge() { goto failure; } + StoreKeyLocationInCache(spkiItem, slot); + failure: if (!isSuccess) { LOG(ERROR) << "SSL Keygen failed!"; } else { - LOG(INFO) << "SSl Keygen succeeded!"; + LOG(INFO) << "SSL Keygen succeeded!"; } // Do cleanups if (privateKey) { - if (!isSuccess || !stores_key_) { - PK11_DestroyTokenObject(privateKey->pkcs11Slot,privateKey->pkcs11ID); - SECKEY_DestroyPrivateKey(privateKey); - } // On successful keygen we need to keep the private key, of course, // or we won't be able to use the client certificate. + if (!isSuccess || !stores_key_) { + PK11_DestroyTokenObject(privateKey->pkcs11Slot, privateKey->pkcs11ID); + } + SECKEY_DestroyPrivateKey(privateKey); } if (publicKey) { diff --git a/net/base/keygen_handler_unittest.cc b/net/base/keygen_handler_unittest.cc index 0022c8e..d73d8e1 100644 --- a/net/base/keygen_handler_unittest.cc +++ b/net/base/keygen_handler_unittest.cc @@ -14,6 +14,20 @@ namespace net { namespace { +KeygenHandler::KeyLocation ValidKeyLocation() { + KeygenHandler::KeyLocation result; +#if defined(OS_WIN) + result.container_name = L"Unit tests"; + result.provider_name = L"Test Provider"; +#elif defined(OS_MACOSX) + result.keychain_path = "/Users/tests/test.chain"; +#elif defined(USE_NSS) + result.slot_name = "Sample slot"; +#endif + + return result; +} + TEST(KeygenHandlerTest, FLAKY_SmokeTest) { KeygenHandler handler(2048, "some challenge"); handler.set_stores_key(false); // Don't leave the key-pair behind @@ -51,6 +65,34 @@ TEST(KeygenHandlerTest, FLAKY_SmokeTest) { // openssl asn1parse -inform DER } +TEST(KeygenHandlerTest, Cache) { + KeygenHandler::Cache* cache = KeygenHandler::Cache::GetInstance(); + KeygenHandler::KeyLocation location1; + KeygenHandler::KeyLocation location2; + + std::string key1("abcd"); + cache->Insert(key1, location1); + + // The cache should have stored location1 at key1 + EXPECT_TRUE(cache->Find(key1, &location2)); + + // The cache should have retrieved it into location2, and their equality + // should be reflexive + EXPECT_TRUE(location1.Equals(location2)); + EXPECT_TRUE(location2.Equals(location1)); + + location2 = ValidKeyLocation(); + KeygenHandler::KeyLocation location3 = ValidKeyLocation(); + EXPECT_FALSE(location1.Equals(location2)); + + // The cache should miss for an unregistered key + std::string key2("def"); + EXPECT_FALSE(cache->Find(key2, &location2)); + + // A cache miss should leave the original location unmolested + EXPECT_TRUE(location2.Equals(location3)); +} + } // namespace } // namespace net diff --git a/net/base/keygen_handler_win.cc b/net/base/keygen_handler_win.cc index cda0307..7a664b9 100644 --- a/net/base/keygen_handler_win.cc +++ b/net/base/keygen_handler_win.cc @@ -1,16 +1,368 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 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/keygen_handler.h" +#include <windows.h> +#include <wincrypt.h> +#pragma comment(lib, "crypt32.lib") +#include <rpc.h> +#pragma comment(lib, "rpcrt4.lib") + +#include <list> +#include <string> +#include <vector> + +#include "base/base64.h" +#include "base/basictypes.h" #include "base/logging.h" +#include "base/string_piece.h" +#include "base/utf_string_conversions.h" namespace net { +// TODO(rsleevi): The following encoding functions are adapted from +// base/crypto/rsa_private_key.h and can/should probably be refactored. +static const uint8 kSequenceTag = 0x30; + +void PrependLength(size_t size, std::list<BYTE>* data) { + // The high bit is used to indicate whether additional octets are needed to + // represent the length. + if (size < 0x80) { + data->push_front(static_cast<BYTE>(size)); + } else { + uint8 num_bytes = 0; + while (size > 0) { + data->push_front(static_cast<BYTE>(size & 0xFF)); + size >>= 8; + num_bytes++; + } + CHECK_LE(num_bytes, 4); + data->push_front(0x80 | num_bytes); + } +} + +void PrependTypeHeaderAndLength(uint8 type, uint32 length, + std::vector<BYTE>* output) { + std::list<BYTE> type_and_length; + + PrependLength(length, &type_and_length); + type_and_length.push_front(type); + + output->insert(output->begin(), type_and_length.begin(), + type_and_length.end()); +} + +bool EncodeAndAppendType(LPCSTR type, const void* to_encode, + std::vector<BYTE>* output) { + BOOL ok; + DWORD size = 0; + ok = CryptEncodeObject(X509_ASN_ENCODING, type, to_encode, NULL, &size); + DCHECK(ok); + if (!ok) + return false; + + std::vector<BYTE>::size_type old_size = output->size(); + output->resize(old_size + size); + + ok = CryptEncodeObject(X509_ASN_ENCODING, type, to_encode, + &(*output)[old_size], &size); + DCHECK(ok); + if (!ok) + return false; + + // Sometimes the initial call to CryptEncodeObject gave a generous estimate + // of the size, so shrink back to what was actually used. + output->resize(old_size + size); + + return true; +} + +// Appends a DER IA5String containing |challenge| to |output|. +// Returns true if encoding was successful. +bool EncodeChallenge(const std::string& challenge, std::vector<BYTE>* output) { + CERT_NAME_VALUE challenge_nv; + challenge_nv.dwValueType = CERT_RDN_IA5_STRING; + challenge_nv.Value.pbData = const_cast<BYTE*>( + reinterpret_cast<const BYTE*>(challenge.c_str())); + challenge_nv.Value.cbData = challenge.size(); + + return EncodeAndAppendType(X509_ANY_STRING, &challenge_nv, output); +} + +// Appends a DER SubjectPublicKeyInfo structure for the signing key in |prov| +// to |output|. +// Returns true if encoding was successful. +bool EncodeSubjectPublicKeyInfo(HCRYPTPROV prov, std::vector<BYTE>* output) { + BOOL ok; + DWORD size = 0; + + // From the private key stored in HCRYPTPROV, obtain the public key, stored + // as a CERT_PUBLIC_KEY_INFO structure. Currently, only RSA public keys are + // supported. + ok = CryptExportPublicKeyInfoEx(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING, + szOID_RSA_RSA, 0, NULL, NULL, &size); + DCHECK(ok); + if (!ok) + return false; + + std::vector<BYTE> public_key_info(size); + PCERT_PUBLIC_KEY_INFO public_key_casted = + reinterpret_cast<PCERT_PUBLIC_KEY_INFO>(&public_key_info[0]); + ok = CryptExportPublicKeyInfoEx(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING, + szOID_RSA_RSA, 0, NULL, public_key_casted, + &size); + DCHECK(ok); + if (!ok) + return false; + + public_key_info.resize(size); + + return EncodeAndAppendType(X509_PUBLIC_KEY_INFO, &public_key_info[0], + output); +} + +// Generates an ASN.1 DER representation of the PublicKeyAndChallenge structure +// from the signing key of |prov| and the specified |challenge| and appends it +// to |output|. +// True if the the encoding was successfully generated. +bool GetPublicKeyAndChallenge(HCRYPTPROV prov, const std::string& challenge, + std::vector<BYTE>* output) { + if (!EncodeSubjectPublicKeyInfo(prov, output) || + !EncodeChallenge(challenge, output)) { + return false; + } + + PrependTypeHeaderAndLength(kSequenceTag, output->size(), output); + return true; +} + +// Generates a DER encoded SignedPublicKeyAndChallenge structure from the +// signing key of |prov| and the specified |challenge| string and appends it +// to |output|. +// True if the encoding was successfully generated. +bool GetSignedPublicKeyAndChallenge(HCRYPTPROV prov, + const std::string& challenge, + std::string* output) { + std::vector<BYTE> pkac; + if (!GetPublicKeyAndChallenge(prov, challenge, &pkac)) + return false; + + std::vector<BYTE> signature; + std::vector<BYTE> signed_pkac; + DWORD size = 0; + BOOL ok; + + // While the MSDN documentation states that CERT_SIGNED_CONTENT_INFO should + // be an X.509 certificate type, for encoding this is not necessary. The + // result of encoding this structure will be a DER-encoded structure with + // the ASN.1 equivalent of + // ::= SEQUENCE { + // ToBeSigned IMPLICIT OCTET STRING, + // SignatureAlgorithm AlgorithmIdentifier, + // Signature BIT STRING + // } + // + // This happens to be the same naive type as an SPKAC, so this works. + CERT_SIGNED_CONTENT_INFO info; + info.ToBeSigned.cbData = pkac.size(); + info.ToBeSigned.pbData = &pkac[0]; + info.SignatureAlgorithm.pszObjId = szOID_RSA_MD5RSA; + info.SignatureAlgorithm.Parameters.cbData = 0; + info.SignatureAlgorithm.Parameters.pbData = NULL; + + ok = CryptSignCertificate(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING, + info.ToBeSigned.pbData, info.ToBeSigned.cbData, + &info.SignatureAlgorithm, NULL, NULL, &size); + DCHECK(ok); + if (!ok) + return false; + + signature.resize(size); + info.Signature.cbData = signature.size(); + info.Signature.pbData = &signature[0]; + info.Signature.cUnusedBits = 0; + + ok = CryptSignCertificate(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING, + info.ToBeSigned.pbData, info.ToBeSigned.cbData, + &info.SignatureAlgorithm, NULL, + info.Signature.pbData, &info.Signature.cbData); + DCHECK(ok); + if (!ok || !EncodeAndAppendType(X509_CERT, &info, &signed_pkac)) + return false; + + output->assign(reinterpret_cast<char*>(&signed_pkac[0]), + signed_pkac.size()); + + return true; +} + +// Generates a unique name for the container which will store the key that is +// generated. The traditional Windows approach is to use a GUID here. +std::wstring GetNewKeyContainerId() { + RPC_STATUS status = RPC_S_OK; + std::wstring result; + + UUID id = { 0 }; + status = UuidCreateSequential(&id); + if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY) + return result; + + RPC_WSTR rpc_string = NULL; + status = UuidToString(&id, &rpc_string); + if (status != RPC_S_OK) + return result; + + // RPC_WSTR is unsigned short*. wchar_t is a built-in type of Visual C++, + // so the type cast is necessary. + result.assign(reinterpret_cast<wchar_t*>(rpc_string)); + RpcStringFree(&rpc_string); + + return result; +} + +void StoreKeyLocationInCache(HCRYPTPROV prov) { + BOOL ok; + DWORD size = 0; + + // Though it is known the container and provider name, as they are supplied + // during GenKeyAndSignChallenge, explicitly resolving them via + // CryptGetProvParam ensures that any defaults (such as provider name being + // NULL) or any CSP modifications to the container name are properly + // reflected. + + // Find the container name. Though the MSDN documentation states it will + // return the exact same value as supplied when the provider was aquired, it + // also notes the return type will be CHAR, /not/ WCHAR. + ok = CryptGetProvParam(prov, PP_CONTAINER, NULL, &size, 0); + if (!ok) + return; + + std::vector<BYTE> buffer(size); + ok = CryptGetProvParam(prov, PP_CONTAINER, &buffer[0], &size, 0); + if (!ok) + return; + + KeygenHandler::KeyLocation key_location; + UTF8ToWide(reinterpret_cast<char*>(&buffer[0]), size, + &key_location.container_name); + + // Get the provider name. This will always resolve, even if NULL (indicating + // the default provider) was supplied to the CryptAcquireContext. + size = 0; + ok = CryptGetProvParam(prov, PP_NAME, NULL, &size, 0); + if (!ok) + return; + + buffer.resize(size); + ok = CryptGetProvParam(prov, PP_NAME, &buffer[0], &size, 0); + if (!ok) + return; + + UTF8ToWide(reinterpret_cast<char*>(&buffer[0]), size, + &key_location.provider_name); + + std::vector<BYTE> public_key_info; + if (!EncodeSubjectPublicKeyInfo(prov, &public_key_info)) + return; + + KeygenHandler::Cache* cache = KeygenHandler::Cache::GetInstance(); + cache->Insert(std::string(public_key_info.begin(), public_key_info.end()), + key_location); +} + +bool KeygenHandler::KeyLocation::Equals( + const KeygenHandler::KeyLocation& location) const { + return container_name == location.container_name && + provider_name == location.provider_name; +} + std::string KeygenHandler::GenKeyAndSignChallenge() { - NOTIMPLEMENTED(); - return std::string(); + std::string result; + + bool is_success = true; + HCRYPTPROV prov = NULL; + HCRYPTKEY key = NULL; + DWORD flags = (key_size_in_bits_ << 16) | CRYPT_EXPORTABLE; + std::string spkac; + + std::wstring new_key_id; + + // TODO(rsleevi): Have the user choose which provider they should use, which + // needs to be filtered by those providers which can provide the key type + // requested or the key size requested. This is especially important for + // generating certificates that will be stored on smart cards. + const int kMaxAttempts = 5; + BOOL ok = FALSE; + for (int attempt = 0; attempt < kMaxAttempts; ++attempt) { + // Per MSDN documentation for CryptAcquireContext, if applications will be + // creating their own keys, they should ensure unique naming schemes to + // prevent overlap with any other applications or consumers of CSPs, and + // *should not* store new keys within the default, NULL key container. + new_key_id = GetNewKeyContainerId(); + if (new_key_id.empty()) + return result; + + // Only create new key containers, so that existing key containers are not + // overwritten. + ok = CryptAcquireContext(&prov, new_key_id.c_str(), NULL, PROV_RSA_FULL, + CRYPT_SILENT | CRYPT_NEWKEYSET); + + if (ok || GetLastError() != NTE_BAD_KEYSET) + break; + } + if (!ok) { + LOG(ERROR) << "Couldn't acquire a CryptoAPI provider context: " + << GetLastError(); + is_success = false; + goto failure; + } + + if (!CryptGenKey(prov, CALG_RSA_KEYX, flags, &key)) { + LOG(ERROR) << "Couldn't generate an RSA key"; + is_success = false; + goto failure; + } + + if (!GetSignedPublicKeyAndChallenge(prov, challenge_, &spkac)) { + LOG(ERROR) << "Couldn't generate the signed public key and challenge"; + is_success = false; + goto failure; + } + + if (!base::Base64Encode(spkac, &result)) { + LOG(ERROR) << "Couldn't convert signed key into base64"; + is_success = false; + goto failure; + } + + StoreKeyLocationInCache(prov); + + failure: + if (!is_success) { + LOG(ERROR) << "SSL Keygen failed"; + } else { + LOG(INFO) << "SSL Key succeeded"; + } + if (key) { + // Securely destroys the handle, but leaves the underlying key alone. The + // key can be obtained again by resolving the key location. If + // |stores_key_| is false, the underlying key will be destroyed below. + CryptDestroyKey(key); + } + + if (prov) { + CryptReleaseContext(prov, 0); + prov = NULL; + if (!stores_key_) { + // Fully destroys any of the keys that were created and releases prov. + CryptAcquireContext(&prov, new_key_id.c_str(), NULL, PROV_RSA_FULL, + CRYPT_SILENT | CRYPT_DELETEKEYSET); + } + } + + return result; } } // namespace net diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h index 50d528a..d6d6a4b 100644 --- a/net/base/net_error_list.h +++ b/net/base/net_error_list.h @@ -360,7 +360,7 @@ NET_ERROR(INSECURE_RESPONSE, -501) NET_ERROR(NO_PRIVATE_KEY_FOR_CERT, -502) // An error adding to the OS certificate database (e.g. OS X Keychain). -NET_ERROR(ERR_ADD_USER_CERT_FAILED, -503) +NET_ERROR(ADD_USER_CERT_FAILED, -503) // // The FTP PASV command failed. diff --git a/net/net.gyp b/net/net.gyp index 3150839..0b3f62c 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -76,6 +76,7 @@ 'base/https_prober.cc', 'base/io_buffer.cc', 'base/io_buffer.h', + 'base/keygen_handler.cc', 'base/keygen_handler.h', 'base/keygen_handler_mac.cc', 'base/keygen_handler_nss.cc', @@ -737,10 +738,6 @@ ], }], [ 'OS == "win"', { - 'sources!': [ - # Remove next line when KeygenHandler is implemented for Windows. - 'base/keygen_handler_unittest.cc', - ], # This is needed to trigger the dll copy step on windows. # TODO(mark): Specifying this here shouldn't be necessary. 'dependencies': [ |