• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
27 namespace 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.
GetSubjectPublicKeyInfo(HCRYPTPROV prov,std::vector<BYTE> * output)31 bool 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.
GetSignedPublicKeyAndChallenge(HCRYPTPROV prov,const std::string & challenge,std::string * output)64 bool 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.
GetNewKeyContainerId()113 std::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.
137 struct KeyContainer {
138  public:
KeyContainernet::KeyContainer139   explicit KeyContainer(bool delete_keyset)
140       : delete_keyset_(delete_keyset) {}
141 
~KeyContainernet::KeyContainer142   ~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 
GenKeyAndSignChallenge()160 std::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