| Index: net/base/keygen_handler_win.cc
|
| ===================================================================
|
| --- net/base/keygen_handler_win.cc (revision 43235)
|
| +++ net/base/keygen_handler_win.cc (working copy)
|
| @@ -1,16 +1,366 @@
|
| -// 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.front();
|
| + 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.front()),
|
| + 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;
|
| +
|
| + // TODO(rsleevi): Is there a less-hackish way to get this through to convert
|
| + // the unsigned short to wchar_t?
|
| + 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 whent he 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.front(), &size, 0);
|
| + if (!ok)
|
| + return;
|
| +
|
| + KeygenHandler::KeyLocation key_location;
|
| + UTF8ToWide(reinterpret_cast<char*>(&buffer.front()), 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.front(), &size, 0);
|
| + if (!ok)
|
| + return;
|
| +
|
| + UTF8ToWide(reinterpret_cast<char*>(&buffer.front()), 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 keytype
|
| + // requested of the keysize 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 != 0 || 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;
|
| + }
|
| +
|
| + failure:
|
| + if (!is_success) {
|
| + LOG(ERROR) << "SSL Keygen failed";
|
| + } else {
|
| + LOG(INFO) << "SSL Key succeeded";
|
| + StoreKeyLocationInCache(prov);
|
| + }
|
| + 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
|
|
|