0

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
This commit is contained in:
wtc@chromium.org
2010-04-01 18:49:47 +00:00
parent 2779491e15
commit 9aed77b6da
13 changed files with 633 additions and 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>

@ -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);
};

@ -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;
}
}

@ -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

@ -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;
void CertDatabase::Init() {
NOTIMPLEMENTED();
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;
return OK;
}
} // namespace net

@ -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

@ -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);

@ -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,

@ -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) {

@ -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

@ -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

@ -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.

@ -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': [