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:
1
AUTHORS
1
AUTHORS
@ -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
|
||||
|
36
net/base/keygen_handler.cc
Normal file
36
net/base/keygen_handler.cc
Normal file
@ -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': [
|
||||
|
Reference in New Issue
Block a user