Home | History | Annotate | Download | only in base
      1 // Copyright (c) 2012 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 <Security/SecAsn1Coder.h>
      8 #include <Security/SecAsn1Templates.h>
      9 #include <Security/Security.h>
     10 
     11 #include "base/base64.h"
     12 #include "base/logging.h"
     13 #include "base/mac/mac_logging.h"
     14 #include "base/mac/scoped_cftyperef.h"
     15 #include "base/strings/string_util.h"
     16 #include "base/strings/sys_string_conversions.h"
     17 #include "base/synchronization/lock.h"
     18 #include "crypto/cssm_init.h"
     19 #include "crypto/mac_security_services_lock.h"
     20 
     21 // These are in Security.framework but not declared in a public header.
     22 extern const SecAsn1Template kSecAsn1AlgorithmIDTemplate[];
     23 extern const SecAsn1Template kSecAsn1SubjectPublicKeyInfoTemplate[];
     24 
     25 namespace net {
     26 
     27 // Declarations of Netscape keygen cert structures for ASN.1 encoding:
     28 
     29 struct PublicKeyAndChallenge {
     30   CSSM_X509_SUBJECT_PUBLIC_KEY_INFO spki;
     31   CSSM_DATA challenge_string;
     32 };
     33 
     34 // This is a copy of the built-in kSecAsn1IA5StringTemplate, but without the
     35 // 'streamable' flag, which was causing bogus data to be written.
     36 const SecAsn1Template kIA5StringTemplate[] = {
     37     { SEC_ASN1_IA5_STRING, 0, NULL, sizeof(CSSM_DATA) }
     38 };
     39 
     40 static const SecAsn1Template kPublicKeyAndChallengeTemplate[] = {
     41   {
     42     SEC_ASN1_SEQUENCE,
     43     0,
     44     NULL,
     45     sizeof(PublicKeyAndChallenge)
     46   },
     47   {
     48     SEC_ASN1_INLINE,
     49     offsetof(PublicKeyAndChallenge, spki),
     50     kSecAsn1SubjectPublicKeyInfoTemplate
     51   },
     52   {
     53     SEC_ASN1_INLINE,
     54     offsetof(PublicKeyAndChallenge, challenge_string),
     55     kIA5StringTemplate
     56   },
     57   {
     58     0
     59   }
     60 };
     61 
     62 struct SignedPublicKeyAndChallenge {
     63   PublicKeyAndChallenge pkac;
     64   CSSM_X509_ALGORITHM_IDENTIFIER signature_algorithm;
     65   CSSM_DATA signature;
     66 };
     67 
     68 static const SecAsn1Template kSignedPublicKeyAndChallengeTemplate[] = {
     69   {
     70     SEC_ASN1_SEQUENCE,
     71     0,
     72     NULL,
     73     sizeof(SignedPublicKeyAndChallenge)
     74   },
     75   {
     76     SEC_ASN1_INLINE,
     77     offsetof(SignedPublicKeyAndChallenge, pkac),
     78     kPublicKeyAndChallengeTemplate
     79   },
     80   {
     81     SEC_ASN1_INLINE,
     82     offsetof(SignedPublicKeyAndChallenge, signature_algorithm),
     83     kSecAsn1AlgorithmIDTemplate
     84   },
     85   {
     86     SEC_ASN1_BIT_STRING,
     87     offsetof(SignedPublicKeyAndChallenge, signature)
     88   },
     89   {
     90     0
     91   }
     92 };
     93 
     94 
     95 static OSStatus CreateRSAKeyPair(int size_in_bits,
     96                                  SecAccessRef initial_access,
     97                                  SecKeyRef* out_pub_key,
     98                                  SecKeyRef* out_priv_key);
     99 static OSStatus SignData(CSSM_DATA data,
    100                          SecKeyRef private_key,
    101                          CSSM_DATA* signature);
    102 
    103 std::string KeygenHandler::GenKeyAndSignChallenge() {
    104   std::string result;
    105   OSStatus err;
    106   SecAccessRef initial_access = NULL;
    107   SecKeyRef public_key = NULL;
    108   SecKeyRef private_key = NULL;
    109   SecAsn1CoderRef coder = NULL;
    110   CSSM_DATA signature = {0, NULL};
    111 
    112   {
    113     if (url_.has_host()) {
    114       // TODO(davidben): Use something like "Key generated for
    115       // example.com", but localize it.
    116       base::ScopedCFTypeRef<CFStringRef> label(
    117           base::SysUTF8ToCFStringRef(url_.host()));
    118       // Create an initial access object to set the SecAccessRef. This
    119       // sets a label on the Keychain dialogs. Pass NULL as the second
    120       // argument to use the default trusted list; only allow the
    121       // current application to access without user confirmation.
    122       err = SecAccessCreate(label, NULL, &initial_access);
    123       // If we fail, just continue without a label.
    124       if (err)
    125         crypto::LogCSSMError("SecAccessCreate", err);
    126     }
    127 
    128     // Create the key-pair.
    129     err = CreateRSAKeyPair(key_size_in_bits_, initial_access,
    130                            &public_key, &private_key);
    131     if (err)
    132       goto failure;
    133 
    134     // Get the public key data (DER sequence of modulus, exponent).
    135     CFDataRef key_data = NULL;
    136     err = SecKeychainItemExport(public_key, kSecFormatBSAFE, 0, NULL,
    137                                 &key_data);
    138     if (err) {
    139       crypto::LogCSSMError("SecKeychainItemExpor", err);
    140       goto failure;
    141     }
    142     base::ScopedCFTypeRef<CFDataRef> scoped_key_data(key_data);
    143 
    144     // Create an ASN.1 encoder.
    145     err = SecAsn1CoderCreate(&coder);
    146     if (err) {
    147       crypto::LogCSSMError("SecAsn1CoderCreate", err);
    148       goto failure;
    149     }
    150 
    151     // Fill in and DER-encode the PublicKeyAndChallenge:
    152     SignedPublicKeyAndChallenge spkac;
    153     memset(&spkac, 0, sizeof(spkac));
    154     spkac.pkac.spki.algorithm.algorithm = CSSMOID_RSA;
    155     spkac.pkac.spki.subjectPublicKey.Length =
    156         CFDataGetLength(key_data) * 8;  // interpreted as a _bit_ count
    157     spkac.pkac.spki.subjectPublicKey.Data =
    158         const_cast<uint8_t*>(CFDataGetBytePtr(key_data));
    159     spkac.pkac.challenge_string.Length = challenge_.length();
    160     spkac.pkac.challenge_string.Data =
    161         reinterpret_cast<uint8_t*>(const_cast<char*>(challenge_.data()));
    162 
    163     CSSM_DATA encoded;
    164     err = SecAsn1EncodeItem(coder, &spkac.pkac,
    165                             kPublicKeyAndChallengeTemplate, &encoded);
    166     if (err) {
    167       crypto::LogCSSMError("SecAsn1EncodeItem", err);
    168       goto failure;
    169     }
    170 
    171     // Compute a signature of the result:
    172     err = SignData(encoded, private_key, &signature);
    173     if (err)
    174       goto failure;
    175     spkac.signature.Data = signature.Data;
    176     spkac.signature.Length = signature.Length * 8;  // a _bit_ count
    177     spkac.signature_algorithm.algorithm = CSSMOID_MD5WithRSA;
    178     // TODO(snej): MD5 is weak. Can we use SHA1 instead?
    179     // See <https://bugzilla.mozilla.org/show_bug.cgi?id=549460>
    180 
    181     // DER-encode the entire SignedPublicKeyAndChallenge:
    182     err = SecAsn1EncodeItem(coder, &spkac,
    183                             kSignedPublicKeyAndChallengeTemplate, &encoded);
    184     if (err) {
    185       crypto::LogCSSMError("SecAsn1EncodeItem", err);
    186       goto failure;
    187     }
    188 
    189     // Base64 encode the result.
    190     std::string input(reinterpret_cast<char*>(encoded.Data), encoded.Length);
    191     base::Base64Encode(input, &result);
    192   }
    193 
    194  failure:
    195   if (err)
    196     OSSTATUS_LOG(ERROR, err) << "SSL Keygen failed!";
    197   else
    198     VLOG(1) << "SSL Keygen succeeded! Output is: " << result;
    199 
    200   // Remove keys from keychain if asked to during unit testing:
    201   if (!stores_key_) {
    202     if (public_key)
    203       SecKeychainItemDelete(reinterpret_cast<SecKeychainItemRef>(public_key));
    204     if (private_key)
    205       SecKeychainItemDelete(reinterpret_cast<SecKeychainItemRef>(private_key));
    206   }
    207 
    208   // Clean up:
    209   free(signature.Data);
    210   if (coder)
    211     SecAsn1CoderRelease(coder);
    212   if (initial_access)
    213     CFRelease(initial_access);
    214   if (public_key)
    215     CFRelease(public_key);
    216   if (private_key)
    217     CFRelease(private_key);
    218   return result;
    219 }
    220 
    221 
    222 // Create an RSA key pair with size |size_in_bits|. |initial_access|
    223 // is passed as the initial access control list in Keychain. The
    224 // public and private keys are placed in |out_pub_key| and
    225 // |out_priv_key|, respectively.
    226 static OSStatus CreateRSAKeyPair(int size_in_bits,
    227                                  SecAccessRef initial_access,
    228                                  SecKeyRef* out_pub_key,
    229                                  SecKeyRef* out_priv_key) {
    230   OSStatus err;
    231   SecKeychainRef keychain;
    232   err = SecKeychainCopyDefault(&keychain);
    233   if (err) {
    234     crypto::LogCSSMError("SecKeychainCopyDefault", err);
    235     return err;
    236   }
    237   base::ScopedCFTypeRef<SecKeychainRef> scoped_keychain(keychain);
    238   {
    239     base::AutoLock locked(crypto::GetMacSecurityServicesLock());
    240     err = SecKeyCreatePair(
    241         keychain,
    242         CSSM_ALGID_RSA,
    243         size_in_bits,
    244         0LL,
    245         // public key usage and attributes:
    246         CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP,
    247         CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT,
    248         // private key usage and attributes:
    249         CSSM_KEYUSE_DECRYPT | CSSM_KEYUSE_SIGN | CSSM_KEYUSE_UNWRAP,
    250         CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT |
    251             CSSM_KEYATTR_SENSITIVE,
    252         initial_access,
    253         out_pub_key, out_priv_key);
    254   }
    255   if (err)
    256     crypto::LogCSSMError("SecKeyCreatePair", err);
    257   return err;
    258 }
    259 
    260 static OSStatus CreateSignatureContext(SecKeyRef key,
    261                                        CSSM_ALGORITHMS algorithm,
    262                                        CSSM_CC_HANDLE* out_cc_handle) {
    263   OSStatus err;
    264   const CSSM_ACCESS_CREDENTIALS* credentials = NULL;
    265   {
    266     base::AutoLock locked(crypto::GetMacSecurityServicesLock());
    267     err = SecKeyGetCredentials(key,
    268                                CSSM_ACL_AUTHORIZATION_SIGN,
    269                                kSecCredentialTypeDefault,
    270                                &credentials);
    271   }
    272   if (err) {
    273     crypto::LogCSSMError("SecKeyGetCredentials", err);
    274     return err;
    275   }
    276 
    277   CSSM_CSP_HANDLE csp_handle = 0;
    278   {
    279     base::AutoLock locked(crypto::GetMacSecurityServicesLock());
    280     err = SecKeyGetCSPHandle(key, &csp_handle);
    281   }
    282   if (err) {
    283     crypto::LogCSSMError("SecKeyGetCSPHandle", err);
    284     return err;
    285   }
    286 
    287   const CSSM_KEY* cssm_key = NULL;
    288   {
    289     base::AutoLock locked(crypto::GetMacSecurityServicesLock());
    290     err = SecKeyGetCSSMKey(key, &cssm_key);
    291   }
    292   if (err) {
    293     crypto::LogCSSMError("SecKeyGetCSSMKey", err);
    294     return err;
    295   }
    296 
    297   err = CSSM_CSP_CreateSignatureContext(csp_handle,
    298                                         algorithm,
    299                                         credentials,
    300                                         cssm_key,
    301                                         out_cc_handle);
    302   if (err)
    303     crypto::LogCSSMError("CSSM_CSP_CreateSignatureContext", err);
    304   return err;
    305 }
    306 
    307 static OSStatus SignData(CSSM_DATA data,
    308                          SecKeyRef private_key,
    309                          CSSM_DATA* signature) {
    310   CSSM_CC_HANDLE cc_handle;
    311   OSStatus err = CreateSignatureContext(private_key,
    312                                         CSSM_ALGID_MD5WithRSA,
    313                                         &cc_handle);
    314   if (err) {
    315     crypto::LogCSSMError("CreateSignatureContext", err);
    316     return err;
    317   }
    318   err = CSSM_SignData(cc_handle, &data, 1, CSSM_ALGID_NONE, signature);
    319   if (err)
    320     crypto::LogCSSMError("CSSM_SignData", err);
    321   CSSM_DeleteContext(cc_handle);
    322   return err;
    323 }
    324 
    325 }  // namespace net
    326