// Copyright (c) 2011 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/strings/string_piece.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "crypto/capi_util.h" #include "crypto/scoped_capi_types.h" namespace net { // Assigns the contents of a CERT_PUBLIC_KEY_INFO structure for the signing // key in |prov| to |output|. Returns true if encoding was successful. bool GetSubjectPublicKeyInfo(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, const_cast<char*>(szOID_RSA_RSA), 0, NULL, NULL, &size); DCHECK(ok); if (!ok) return false; output->resize(size); PCERT_PUBLIC_KEY_INFO public_key_casted = reinterpret_cast<PCERT_PUBLIC_KEY_INFO>(&(*output)[0]); ok = CryptExportPublicKeyInfoEx(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING, const_cast<char*>(szOID_RSA_RSA), 0, NULL, public_key_casted, &size); DCHECK(ok); if (!ok) return false; output->resize(size); return true; } // Generates a DER encoded SignedPublicKeyAndChallenge structure from the // signing key of |prov| and the specified ASCII |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::wstring wide_challenge = base::ASCIIToWide(challenge); std::vector<BYTE> spki; if (!GetSubjectPublicKeyInfo(prov, &spki)) return false; // PublicKeyAndChallenge ::= SEQUENCE { // spki SubjectPublicKeyInfo, // challenge IA5STRING // } CERT_KEYGEN_REQUEST_INFO pkac; pkac.dwVersion = CERT_KEYGEN_REQUEST_V1; pkac.SubjectPublicKeyInfo = *reinterpret_cast<PCERT_PUBLIC_KEY_INFO>(&spki[0]); pkac.pwszChallengeString = const_cast<wchar_t*>(wide_challenge.c_str()); CRYPT_ALGORITHM_IDENTIFIER sig_alg; memset(&sig_alg, 0, sizeof(sig_alg)); sig_alg.pszObjId = const_cast<char*>(szOID_RSA_MD5RSA); BOOL ok; DWORD size = 0; std::vector<BYTE> signed_pkac; ok = CryptSignAndEncodeCertificate(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING, X509_KEYGEN_REQUEST_TO_BE_SIGNED, &pkac, &sig_alg, NULL, NULL, &size); DCHECK(ok); if (!ok) return false; signed_pkac.resize(size); ok = CryptSignAndEncodeCertificate(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING, X509_KEYGEN_REQUEST_TO_BE_SIGNED, &pkac, &sig_alg, NULL, &signed_pkac[0], &size); DCHECK(ok); if (!ok) return false; output->assign(reinterpret_cast<char*>(&signed_pkac[0]), 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; } // This is a helper struct designed to optionally delete a key after releasing // the associated provider. struct KeyContainer { public: explicit KeyContainer(bool delete_keyset) : delete_keyset_(delete_keyset) {} ~KeyContainer() { if (provider_) { provider_.reset(); if (delete_keyset_ && !key_id_.empty()) { HCRYPTPROV provider; crypto::CryptAcquireContextLocked(&provider, key_id_.c_str(), NULL, PROV_RSA_FULL, CRYPT_SILENT | CRYPT_DELETEKEYSET); } } } crypto::ScopedHCRYPTPROV provider_; std::wstring key_id_; private: bool delete_keyset_; }; std::string KeygenHandler::GenKeyAndSignChallenge() { KeyContainer key_container(!stores_key_); // 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; int attempt; for (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. key_container.key_id_ = GetNewKeyContainerId(); if (key_container.key_id_.empty()) return std::string(); // Only create new key containers, so that existing key containers are not // overwritten. if (crypto::CryptAcquireContextLocked(key_container.provider_.receive(), key_container.key_id_.c_str(), NULL, PROV_RSA_FULL, CRYPT_SILENT | CRYPT_NEWKEYSET)) break; if (GetLastError() != NTE_BAD_KEYSET) { LOG(ERROR) << "Keygen failed: Couldn't acquire a CryptoAPI provider " "context: " << GetLastError(); return std::string(); } } if (attempt == kMaxAttempts) { LOG(ERROR) << "Keygen failed: Couldn't acquire a CryptoAPI provider " "context: Max retries exceeded"; return std::string(); } { crypto::ScopedHCRYPTKEY key; if (!CryptGenKey(key_container.provider_, CALG_RSA_KEYX, (key_size_in_bits_ << 16) | CRYPT_EXPORTABLE, key.receive())) { LOG(ERROR) << "Keygen failed: Couldn't generate an RSA key"; return std::string(); } std::string spkac; if (!GetSignedPublicKeyAndChallenge(key_container.provider_, challenge_, &spkac)) { LOG(ERROR) << "Keygen failed: Couldn't generate the signed public key " "and challenge"; return std::string(); } std::string result; base::Base64Encode(spkac, &result); VLOG(1) << "Keygen succeeded"; return result; } } } // namespace net