1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/base/keygen_handler.h"
14 #include "base/base64.h"
15 #include "base/basictypes.h"
16 #include "base/logging.h"
17 #include "base/strings/string_piece.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "crypto/capi_util.h"
21 #include "crypto/scoped_capi_types.h"
22 #include "crypto/wincrypt_shim.h"
24 #pragma comment(lib, "crypt32.lib")
25 #pragma comment(lib, "rpcrt4.lib")
29 // Assigns the contents of a CERT_PUBLIC_KEY_INFO structure for the signing
30 // key in |prov| to |output|. Returns true if encoding was successful.
31 bool GetSubjectPublicKeyInfo(HCRYPTPROV prov
, std::vector
<BYTE
>* output
) {
35 // From the private key stored in HCRYPTPROV, obtain the public key, stored
36 // as a CERT_PUBLIC_KEY_INFO structure. Currently, only RSA public keys are
38 ok
= CryptExportPublicKeyInfoEx(prov
, AT_KEYEXCHANGE
, X509_ASN_ENCODING
,
39 const_cast<char*>(szOID_RSA_RSA
), 0, NULL
,
47 PCERT_PUBLIC_KEY_INFO public_key_casted
=
48 reinterpret_cast<PCERT_PUBLIC_KEY_INFO
>(&(*output
)[0]);
49 ok
= CryptExportPublicKeyInfoEx(prov
, AT_KEYEXCHANGE
, X509_ASN_ENCODING
,
50 const_cast<char*>(szOID_RSA_RSA
), 0, NULL
,
51 public_key_casted
, &size
);
61 // Generates a DER encoded SignedPublicKeyAndChallenge structure from the
62 // signing key of |prov| and the specified ASCII |challenge| string and
63 // appends it to |output|.
64 // True if the encoding was successfully generated.
65 bool GetSignedPublicKeyAndChallenge(HCRYPTPROV prov
,
66 const std::string
& challenge
,
67 std::string
* output
) {
68 base::string16 challenge16
= base::ASCIIToUTF16(challenge
);
69 std::vector
<BYTE
> spki
;
71 if (!GetSubjectPublicKeyInfo(prov
, &spki
))
74 // PublicKeyAndChallenge ::= SEQUENCE {
75 // spki SubjectPublicKeyInfo,
76 // challenge IA5STRING
78 CERT_KEYGEN_REQUEST_INFO pkac
;
79 pkac
.dwVersion
= CERT_KEYGEN_REQUEST_V1
;
80 pkac
.SubjectPublicKeyInfo
=
81 *reinterpret_cast<PCERT_PUBLIC_KEY_INFO
>(&spki
[0]);
82 pkac
.pwszChallengeString
= const_cast<base::char16
*>(challenge16
.c_str());
84 CRYPT_ALGORITHM_IDENTIFIER sig_alg
;
85 memset(&sig_alg
, 0, sizeof(sig_alg
));
86 sig_alg
.pszObjId
= const_cast<char*>(szOID_RSA_MD5RSA
);
90 std::vector
<BYTE
> signed_pkac
;
91 ok
= CryptSignAndEncodeCertificate(prov
, AT_KEYEXCHANGE
, X509_ASN_ENCODING
,
92 X509_KEYGEN_REQUEST_TO_BE_SIGNED
,
93 &pkac
, &sig_alg
, NULL
,
99 signed_pkac
.resize(size
);
100 ok
= CryptSignAndEncodeCertificate(prov
, AT_KEYEXCHANGE
, X509_ASN_ENCODING
,
101 X509_KEYGEN_REQUEST_TO_BE_SIGNED
,
102 &pkac
, &sig_alg
, NULL
,
103 &signed_pkac
[0], &size
);
108 output
->assign(reinterpret_cast<char*>(&signed_pkac
[0]), size
);
112 // Generates a unique name for the container which will store the key that is
113 // generated. The traditional Windows approach is to use a GUID here.
114 std::wstring
GetNewKeyContainerId() {
115 RPC_STATUS status
= RPC_S_OK
;
119 status
= UuidCreateSequential(&id
);
120 if (status
!= RPC_S_OK
&& status
!= RPC_S_UUID_LOCAL_ONLY
)
123 RPC_WSTR rpc_string
= NULL
;
124 status
= UuidToString(&id
, &rpc_string
);
125 if (status
!= RPC_S_OK
)
128 // RPC_WSTR is unsigned short*. wchar_t is a built-in type of Visual C++,
129 // so the type cast is necessary.
130 result
.assign(reinterpret_cast<wchar_t*>(rpc_string
));
131 RpcStringFree(&rpc_string
);
136 // This is a helper struct designed to optionally delete a key after releasing
137 // the associated provider.
138 struct KeyContainer
{
140 explicit KeyContainer(bool delete_keyset
)
141 : delete_keyset_(delete_keyset
) {}
146 if (delete_keyset_
&& !key_id_
.empty()) {
148 crypto::CryptAcquireContextLocked(&provider
, key_id_
.c_str(), NULL
,
149 PROV_RSA_FULL
, CRYPT_SILENT
| CRYPT_DELETEKEYSET
);
154 crypto::ScopedHCRYPTPROV provider_
;
155 std::wstring key_id_
;
161 std::string
KeygenHandler::GenKeyAndSignChallenge() {
162 KeyContainer
key_container(!stores_key_
);
164 // TODO(rsleevi): Have the user choose which provider they should use, which
165 // needs to be filtered by those providers which can provide the key type
166 // requested or the key size requested. This is especially important for
167 // generating certificates that will be stored on smart cards.
168 const int kMaxAttempts
= 5;
170 for (attempt
= 0; attempt
< kMaxAttempts
; ++attempt
) {
171 // Per MSDN documentation for CryptAcquireContext, if applications will be
172 // creating their own keys, they should ensure unique naming schemes to
173 // prevent overlap with any other applications or consumers of CSPs, and
174 // *should not* store new keys within the default, NULL key container.
175 key_container
.key_id_
= GetNewKeyContainerId();
176 if (key_container
.key_id_
.empty())
177 return std::string();
179 // Only create new key containers, so that existing key containers are not
181 if (crypto::CryptAcquireContextLocked(key_container
.provider_
.receive(),
182 key_container
.key_id_
.c_str(), NULL
, PROV_RSA_FULL
,
183 CRYPT_SILENT
| CRYPT_NEWKEYSET
))
186 if (GetLastError() != NTE_BAD_KEYSET
) {
187 LOG(ERROR
) << "Keygen failed: Couldn't acquire a CryptoAPI provider "
188 "context: " << GetLastError();
189 return std::string();
192 if (attempt
== kMaxAttempts
) {
193 LOG(ERROR
) << "Keygen failed: Couldn't acquire a CryptoAPI provider "
194 "context: Max retries exceeded";
195 return std::string();
199 crypto::ScopedHCRYPTKEY key
;
200 if (!CryptGenKey(key_container
.provider_
, CALG_RSA_KEYX
,
201 (key_size_in_bits_
<< 16) | CRYPT_EXPORTABLE
, key
.receive())) {
202 LOG(ERROR
) << "Keygen failed: Couldn't generate an RSA key";
203 return std::string();
207 if (!GetSignedPublicKeyAndChallenge(key_container
.provider_
, challenge_
,
209 LOG(ERROR
) << "Keygen failed: Couldn't generate the signed public key "
211 return std::string();
215 base::Base64Encode(spkac
, &result
);
217 VLOG(1) << "Keygen succeeded";