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