| // 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/string_piece.h" |
| #include "base/string_util.h" |
| #include "base/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, |
| 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, |
| 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 = 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 = 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; |
| if (!base::Base64Encode(spkac, &result)) { |
| LOG(ERROR) << "Keygen failed: Couldn't convert signed key into base64"; |
| return std::string(); |
| } |
| |
| VLOG(1) << "Keygen succeeded"; |
| return result; |
| } |
| } |
| |
| } // namespace net |