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/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"
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                                  const_cast<char*>(szOID_RSA_RSA), 0, NULL,
40                                  NULL, &size);
41  DCHECK(ok);
42  if (!ok)
43    return false;
44
45  output->resize(size);
46
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);
52  DCHECK(ok);
53  if (!ok)
54    return false;
55
56  output->resize(size);
57
58  return true;
59}
60
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.
65bool GetSignedPublicKeyAndChallenge(HCRYPTPROV prov,
66                                    const std::string& challenge,
67                                    std::string* output) {
68  std::wstring wide_challenge = base::ASCIIToWide(challenge);
69  std::vector<BYTE> spki;
70
71  if (!GetSubjectPublicKeyInfo(prov, &spki))
72    return false;
73
74  // PublicKeyAndChallenge ::= SEQUENCE {
75  //     spki SubjectPublicKeyInfo,
76  //     challenge IA5STRING
77  // }
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<wchar_t*>(wide_challenge.c_str());
83
84  CRYPT_ALGORITHM_IDENTIFIER sig_alg;
85  memset(&sig_alg, 0, sizeof(sig_alg));
86  sig_alg.pszObjId = const_cast<char*>(szOID_RSA_MD5RSA);
87
88  BOOL ok;
89  DWORD size = 0;
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,
94                                     NULL, &size);
95  DCHECK(ok);
96  if (!ok)
97    return false;
98
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);
104  DCHECK(ok);
105  if (!ok)
106    return false;
107
108  output->assign(reinterpret_cast<char*>(&signed_pkac[0]), size);
109  return true;
110}
111
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.
114std::wstring GetNewKeyContainerId() {
115  RPC_STATUS status = RPC_S_OK;
116  std::wstring result;
117
118  UUID id = { 0 };
119  status = UuidCreateSequential(&id);
120  if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY)
121    return result;
122
123  RPC_WSTR rpc_string = NULL;
124  status = UuidToString(&id, &rpc_string);
125  if (status != RPC_S_OK)
126    return result;
127
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);
132
133  return result;
134}
135
136// This is a helper struct designed to optionally delete a key after releasing
137// the associated provider.
138struct KeyContainer {
139 public:
140  explicit KeyContainer(bool delete_keyset)
141      : delete_keyset_(delete_keyset) {}
142
143  ~KeyContainer() {
144    if (provider_) {
145      provider_.reset();
146      if (delete_keyset_ && !key_id_.empty()) {
147        HCRYPTPROV provider;
148        crypto::CryptAcquireContextLocked(&provider, key_id_.c_str(), NULL,
149            PROV_RSA_FULL, CRYPT_SILENT | CRYPT_DELETEKEYSET);
150      }
151    }
152  }
153
154  crypto::ScopedHCRYPTPROV provider_;
155  std::wstring key_id_;
156
157 private:
158  bool delete_keyset_;
159};
160
161std::string KeygenHandler::GenKeyAndSignChallenge() {
162  KeyContainer key_container(!stores_key_);
163
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;
169  int attempt;
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();
178
179    // Only create new key containers, so that existing key containers are not
180    // overwritten.
181    if (crypto::CryptAcquireContextLocked(key_container.provider_.receive(),
182            key_container.key_id_.c_str(), NULL, PROV_RSA_FULL,
183            CRYPT_SILENT | CRYPT_NEWKEYSET))
184      break;
185
186    if (GetLastError() != NTE_BAD_KEYSET) {
187      LOG(ERROR) << "Keygen failed: Couldn't acquire a CryptoAPI provider "
188                    "context: " << GetLastError();
189      return std::string();
190    }
191  }
192  if (attempt == kMaxAttempts) {
193    LOG(ERROR) << "Keygen failed: Couldn't acquire a CryptoAPI provider "
194                  "context: Max retries exceeded";
195    return std::string();
196  }
197
198  {
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();
204    }
205
206    std::string spkac;
207    if (!GetSignedPublicKeyAndChallenge(key_container.provider_, challenge_,
208                                        &spkac)) {
209      LOG(ERROR) << "Keygen failed: Couldn't generate the signed public key "
210                    "and challenge";
211      return std::string();
212    }
213
214    std::string result;
215    base::Base64Encode(spkac, &result);
216
217    VLOG(1) << "Keygen succeeded";
218    return result;
219  }
220}
221
222}  // namespace net
223