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. 21extern const SecAsn1Template kSecAsn1AlgorithmIDTemplate[]; 22extern const SecAsn1Template kSecAsn1SubjectPublicKeyInfoTemplate[]; 23 24namespace net { 25 26// Declarations of Netscape keygen cert structures for ASN.1 encoding: 27 28struct 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. 35const SecAsn1Template kIA5StringTemplate[] = { 36 { SEC_ASN1_IA5_STRING, 0, NULL, sizeof(CSSM_DATA) } 37}; 38 39static 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 61struct SignedPublicKeyAndChallenge { 62 PublicKeyAndChallenge pkac; 63 CSSM_X509_ALGORITHM_IDENTIFIER signature_algorithm; 64 CSSM_DATA signature; 65}; 66 67static 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 94static OSStatus CreateRSAKeyPair(int size_in_bits, 95 SecAccessRef initial_access, 96 SecKeyRef* out_pub_key, 97 SecKeyRef* out_priv_key); 98static OSStatus SignData(CSSM_DATA data, 99 SecKeyRef private_key, 100 CSSM_DATA* signature); 101 102std::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. 225static 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 259static 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 306static 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