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