Web MIDI: send back error information to blink on starting sessions
[chromium-blink-merge.git] / net / base / keygen_handler_win.cc
blob59dc69de54c104a2874106194cb310e6d88479a3
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"
7 #include <windows.h>
8 #include <wincrypt.h>
9 #pragma comment(lib, "crypt32.lib")
10 #include <rpc.h>
11 #pragma comment(lib, "rpcrt4.lib")
13 #include <list>
14 #include <string>
15 #include <vector>
17 #include "base/base64.h"
18 #include "base/basictypes.h"
19 #include "base/logging.h"
20 #include "base/strings/string_piece.h"
21 #include "base/strings/string_util.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "crypto/capi_util.h"
24 #include "crypto/scoped_capi_types.h"
27 namespace net {
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) {
32 BOOL ok;
33 DWORD size = 0;
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
37 // supported.
38 ok = CryptExportPublicKeyInfoEx(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING,
39 szOID_RSA_RSA, 0, NULL, NULL, &size);
40 DCHECK(ok);
41 if (!ok)
42 return false;
44 output->resize(size);
46 PCERT_PUBLIC_KEY_INFO public_key_casted =
47 reinterpret_cast<PCERT_PUBLIC_KEY_INFO>(&(*output)[0]);
48 ok = CryptExportPublicKeyInfoEx(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING,
49 szOID_RSA_RSA, 0, NULL, public_key_casted,
50 &size);
51 DCHECK(ok);
52 if (!ok)
53 return false;
55 output->resize(size);
57 return true;
60 // Generates a DER encoded SignedPublicKeyAndChallenge structure from the
61 // signing key of |prov| and the specified ASCII |challenge| string and
62 // appends it to |output|.
63 // True if the encoding was successfully generated.
64 bool GetSignedPublicKeyAndChallenge(HCRYPTPROV prov,
65 const std::string& challenge,
66 std::string* output) {
67 std::wstring wide_challenge = base::ASCIIToWide(challenge);
68 std::vector<BYTE> spki;
70 if (!GetSubjectPublicKeyInfo(prov, &spki))
71 return false;
73 // PublicKeyAndChallenge ::= SEQUENCE {
74 // spki SubjectPublicKeyInfo,
75 // challenge IA5STRING
76 // }
77 CERT_KEYGEN_REQUEST_INFO pkac;
78 pkac.dwVersion = CERT_KEYGEN_REQUEST_V1;
79 pkac.SubjectPublicKeyInfo =
80 *reinterpret_cast<PCERT_PUBLIC_KEY_INFO>(&spki[0]);
81 pkac.pwszChallengeString = const_cast<wchar_t*>(wide_challenge.c_str());
83 CRYPT_ALGORITHM_IDENTIFIER sig_alg;
84 memset(&sig_alg, 0, sizeof(sig_alg));
85 sig_alg.pszObjId = szOID_RSA_MD5RSA;
87 BOOL ok;
88 DWORD size = 0;
89 std::vector<BYTE> signed_pkac;
90 ok = CryptSignAndEncodeCertificate(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING,
91 X509_KEYGEN_REQUEST_TO_BE_SIGNED,
92 &pkac, &sig_alg, NULL,
93 NULL, &size);
94 DCHECK(ok);
95 if (!ok)
96 return false;
98 signed_pkac.resize(size);
99 ok = CryptSignAndEncodeCertificate(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING,
100 X509_KEYGEN_REQUEST_TO_BE_SIGNED,
101 &pkac, &sig_alg, NULL,
102 &signed_pkac[0], &size);
103 DCHECK(ok);
104 if (!ok)
105 return false;
107 output->assign(reinterpret_cast<char*>(&signed_pkac[0]), size);
108 return true;
111 // Generates a unique name for the container which will store the key that is
112 // generated. The traditional Windows approach is to use a GUID here.
113 std::wstring GetNewKeyContainerId() {
114 RPC_STATUS status = RPC_S_OK;
115 std::wstring result;
117 UUID id = { 0 };
118 status = UuidCreateSequential(&id);
119 if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY)
120 return result;
122 RPC_WSTR rpc_string = NULL;
123 status = UuidToString(&id, &rpc_string);
124 if (status != RPC_S_OK)
125 return result;
127 // RPC_WSTR is unsigned short*. wchar_t is a built-in type of Visual C++,
128 // so the type cast is necessary.
129 result.assign(reinterpret_cast<wchar_t*>(rpc_string));
130 RpcStringFree(&rpc_string);
132 return result;
135 // This is a helper struct designed to optionally delete a key after releasing
136 // the associated provider.
137 struct KeyContainer {
138 public:
139 explicit KeyContainer(bool delete_keyset)
140 : delete_keyset_(delete_keyset) {}
142 ~KeyContainer() {
143 if (provider_) {
144 provider_.reset();
145 if (delete_keyset_ && !key_id_.empty()) {
146 HCRYPTPROV provider;
147 crypto::CryptAcquireContextLocked(&provider, key_id_.c_str(), NULL,
148 PROV_RSA_FULL, CRYPT_SILENT | CRYPT_DELETEKEYSET);
153 crypto::ScopedHCRYPTPROV provider_;
154 std::wstring key_id_;
156 private:
157 bool delete_keyset_;
160 std::string KeygenHandler::GenKeyAndSignChallenge() {
161 KeyContainer key_container(!stores_key_);
163 // TODO(rsleevi): Have the user choose which provider they should use, which
164 // needs to be filtered by those providers which can provide the key type
165 // requested or the key size requested. This is especially important for
166 // generating certificates that will be stored on smart cards.
167 const int kMaxAttempts = 5;
168 int attempt;
169 for (attempt = 0; attempt < kMaxAttempts; ++attempt) {
170 // Per MSDN documentation for CryptAcquireContext, if applications will be
171 // creating their own keys, they should ensure unique naming schemes to
172 // prevent overlap with any other applications or consumers of CSPs, and
173 // *should not* store new keys within the default, NULL key container.
174 key_container.key_id_ = GetNewKeyContainerId();
175 if (key_container.key_id_.empty())
176 return std::string();
178 // Only create new key containers, so that existing key containers are not
179 // overwritten.
180 if (crypto::CryptAcquireContextLocked(key_container.provider_.receive(),
181 key_container.key_id_.c_str(), NULL, PROV_RSA_FULL,
182 CRYPT_SILENT | CRYPT_NEWKEYSET))
183 break;
185 if (GetLastError() != NTE_BAD_KEYSET) {
186 LOG(ERROR) << "Keygen failed: Couldn't acquire a CryptoAPI provider "
187 "context: " << GetLastError();
188 return std::string();
191 if (attempt == kMaxAttempts) {
192 LOG(ERROR) << "Keygen failed: Couldn't acquire a CryptoAPI provider "
193 "context: Max retries exceeded";
194 return std::string();
198 crypto::ScopedHCRYPTKEY key;
199 if (!CryptGenKey(key_container.provider_, CALG_RSA_KEYX,
200 (key_size_in_bits_ << 16) | CRYPT_EXPORTABLE, key.receive())) {
201 LOG(ERROR) << "Keygen failed: Couldn't generate an RSA key";
202 return std::string();
205 std::string spkac;
206 if (!GetSignedPublicKeyAndChallenge(key_container.provider_, challenge_,
207 &spkac)) {
208 LOG(ERROR) << "Keygen failed: Couldn't generate the signed public key "
209 "and challenge";
210 return std::string();
213 std::string result;
214 base::Base64Encode(spkac, &result);
216 VLOG(1) << "Keygen succeeded";
217 return result;
221 } // namespace net