summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-01 18:49:47 +0000
committerwtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-01 18:49:47 +0000
commit9aed77b6dace047bff676a7cdcfef345a858d1a7 (patch)
treefddd5e0fac4ee39b6c79af56871f022472f276d9
parent2779491e15b4a157b6351e20bb0f2dd26f6d84de (diff)
downloadchromium_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--AUTHORS1
-rw-r--r--net/base/cert_database.h7
-rw-r--r--net/base/cert_database_mac.cc10
-rw-r--r--net/base/cert_database_nss.cc11
-rw-r--r--net/base/cert_database_win.cc110
-rw-r--r--net/base/keygen_handler.cc36
-rw-r--r--net/base/keygen_handler.h54
-rw-r--r--net/base/keygen_handler_mac.cc11
-rw-r--r--net/base/keygen_handler_nss.cc29
-rw-r--r--net/base/keygen_handler_unittest.cc42
-rw-r--r--net/base/keygen_handler_win.cc358
-rw-r--r--net/base/net_error_list.h2
-rw-r--r--net/net.gyp5
13 files changed, 633 insertions, 43 deletions
diff --git a/AUTHORS b/AUTHORS
index 8fbe76d..1b39d09a 100644
--- a/AUTHORS
+++ b/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>
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': [