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