Home | History | Annotate | Download | only in base
      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.
     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.
     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.
    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:
    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 
    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