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.
4
5#include "net/base/keygen_handler.h"
6
7#include <windows.h>
8#include <wincrypt.h>
9#pragma comment(lib, "crypt32.lib")
10#include <rpc.h>
11#pragma comment(lib, "rpcrt4.lib")
12
13#include <list>
14#include <string>
15#include <vector>
16
17#include "base/base64.h"
18#include "base/basictypes.h"
19#include "base/logging.h"
20#include "base/string_piece.h"
21#include "base/string_util.h"
22#include "base/utf_string_conversions.h"
23#include "crypto/capi_util.h"
24#include "crypto/scoped_capi_types.h"
25
26
27namespace net {
28
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.
31bool GetSubjectPublicKeyInfo(HCRYPTPROV prov, std::vector<BYTE>* output) {
32  BOOL ok;
33  DWORD size = 0;
34
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;
43
44  output->resize(size);
45
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;
54
55  output->resize(size);
56
57  return true;
58}
59
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.
64bool GetSignedPublicKeyAndChallenge(HCRYPTPROV prov,
65                                    const std::string& challenge,
66                                    std::string* output) {
67  std::wstring wide_challenge = ASCIIToWide(challenge);
68  std::vector<BYTE> spki;
69
70  if (!GetSubjectPublicKeyInfo(prov, &spki))
71    return false;
72
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());
82
83  CRYPT_ALGORITHM_IDENTIFIER sig_alg;
84  memset(&sig_alg, 0, sizeof(sig_alg));
85  sig_alg.pszObjId = szOID_RSA_MD5RSA;
86
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;
97
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;
106
107  output->assign(reinterpret_cast<char*>(&signed_pkac[0]), size);
108  return true;
109}
110
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.
113std::wstring GetNewKeyContainerId() {
114  RPC_STATUS status = RPC_S_OK;
115  std::wstring result;
116
117  UUID id = { 0 };
118  status = UuidCreateSequential(&id);
119  if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY)
120    return result;
121
122  RPC_WSTR rpc_string = NULL;
123  status = UuidToString(&id, &rpc_string);
124  if (status != RPC_S_OK)
125    return result;
126
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);
131
132  return result;
133}
134
135// This is a helper struct designed to optionally delete a key after releasing
136// the associated provider.
137struct KeyContainer {
138 public:
139  explicit KeyContainer(bool delete_keyset)
140      : delete_keyset_(delete_keyset) {}
141
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);
149      }
150    }
151  }
152
153  crypto::ScopedHCRYPTPROV provider_;
154  std::wstring key_id_;
155
156 private:
157  bool delete_keyset_;
158};
159
160std::string KeygenHandler::GenKeyAndSignChallenge() {
161  KeyContainer key_container(!stores_key_);
162
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();
177
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;
184
185    if (GetLastError() != NTE_BAD_KEYSET) {
186      LOG(ERROR) << "Keygen failed: Couldn't acquire a CryptoAPI provider "
187                    "context: " << GetLastError();
188      return std::string();
189    }
190  }
191  if (attempt == kMaxAttempts) {
192    LOG(ERROR) << "Keygen failed: Couldn't acquire a CryptoAPI provider "
193                  "context: Max retries exceeded";
194    return std::string();
195  }
196
197  {
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();
203    }
204
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();
211    }
212
213    std::string result;
214    if (!base::Base64Encode(spkac, &result)) {
215      LOG(ERROR) << "Keygen failed: Couldn't convert signed key into base64";
216      return std::string();
217    }
218
219    VLOG(1) << "Keygen succeeded";
220    return result;
221  }
222}
223
224}  // namespace net
225