|  | // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "net/base/keygen_handler.h" | 
|  |  | 
|  | #include <Security/SecAsn1Coder.h> | 
|  | #include <Security/SecAsn1Templates.h> | 
|  | #include <Security/Security.h> | 
|  |  | 
|  | #include "base/base64.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/mac/mac_logging.h" | 
|  | #include "base/mac/scoped_cftyperef.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/strings/sys_string_conversions.h" | 
|  | #include "base/synchronization/lock.h" | 
|  | #include "crypto/cssm_init.h" | 
|  | #include "crypto/mac_security_services_lock.h" | 
|  |  | 
|  | // These are in Security.framework but not declared in a public header. | 
|  | extern const SecAsn1Template kSecAsn1AlgorithmIDTemplate[]; | 
|  | extern const SecAsn1Template kSecAsn1SubjectPublicKeyInfoTemplate[]; | 
|  |  | 
|  | namespace net { | 
|  |  | 
|  | // Declarations of Netscape keygen cert structures for ASN.1 encoding: | 
|  |  | 
|  | struct PublicKeyAndChallenge { | 
|  | CSSM_X509_SUBJECT_PUBLIC_KEY_INFO spki; | 
|  | CSSM_DATA challenge_string; | 
|  | }; | 
|  |  | 
|  | // This is a copy of the built-in kSecAsn1IA5StringTemplate, but without the | 
|  | // 'streamable' flag, which was causing bogus data to be written. | 
|  | const SecAsn1Template kIA5StringTemplate[] = { | 
|  | { SEC_ASN1_IA5_STRING, 0, NULL, sizeof(CSSM_DATA) } | 
|  | }; | 
|  |  | 
|  | static const SecAsn1Template kPublicKeyAndChallengeTemplate[] = { | 
|  | { | 
|  | SEC_ASN1_SEQUENCE, | 
|  | 0, | 
|  | NULL, | 
|  | sizeof(PublicKeyAndChallenge) | 
|  | }, | 
|  | { | 
|  | SEC_ASN1_INLINE, | 
|  | offsetof(PublicKeyAndChallenge, spki), | 
|  | kSecAsn1SubjectPublicKeyInfoTemplate | 
|  | }, | 
|  | { | 
|  | SEC_ASN1_INLINE, | 
|  | offsetof(PublicKeyAndChallenge, challenge_string), | 
|  | kIA5StringTemplate | 
|  | }, | 
|  | { | 
|  | 0 | 
|  | } | 
|  | }; | 
|  |  | 
|  | struct SignedPublicKeyAndChallenge { | 
|  | PublicKeyAndChallenge pkac; | 
|  | CSSM_X509_ALGORITHM_IDENTIFIER signature_algorithm; | 
|  | CSSM_DATA signature; | 
|  | }; | 
|  |  | 
|  | static const SecAsn1Template kSignedPublicKeyAndChallengeTemplate[] = { | 
|  | { | 
|  | SEC_ASN1_SEQUENCE, | 
|  | 0, | 
|  | NULL, | 
|  | sizeof(SignedPublicKeyAndChallenge) | 
|  | }, | 
|  | { | 
|  | SEC_ASN1_INLINE, | 
|  | offsetof(SignedPublicKeyAndChallenge, pkac), | 
|  | kPublicKeyAndChallengeTemplate | 
|  | }, | 
|  | { | 
|  | SEC_ASN1_INLINE, | 
|  | offsetof(SignedPublicKeyAndChallenge, signature_algorithm), | 
|  | kSecAsn1AlgorithmIDTemplate | 
|  | }, | 
|  | { | 
|  | SEC_ASN1_BIT_STRING, | 
|  | offsetof(SignedPublicKeyAndChallenge, signature) | 
|  | }, | 
|  | { | 
|  | 0 | 
|  | } | 
|  | }; | 
|  |  | 
|  |  | 
|  | static OSStatus CreateRSAKeyPair(int size_in_bits, | 
|  | SecAccessRef initial_access, | 
|  | SecKeyRef* out_pub_key, | 
|  | SecKeyRef* out_priv_key); | 
|  | static OSStatus SignData(CSSM_DATA data, | 
|  | SecKeyRef private_key, | 
|  | CSSM_DATA* signature); | 
|  |  | 
|  | std::string KeygenHandler::GenKeyAndSignChallenge() { | 
|  | std::string result; | 
|  | OSStatus err; | 
|  | SecAccessRef initial_access = NULL; | 
|  | SecKeyRef public_key = NULL; | 
|  | SecKeyRef private_key = NULL; | 
|  | SecAsn1CoderRef coder = NULL; | 
|  | CSSM_DATA signature = {0, NULL}; | 
|  |  | 
|  | { | 
|  | if (url_.has_host()) { | 
|  | // TODO(davidben): Use something like "Key generated for | 
|  | // example.com", but localize it. | 
|  | base::ScopedCFTypeRef<CFStringRef> label( | 
|  | base::SysUTF8ToCFStringRef(url_.host())); | 
|  | // Create an initial access object to set the SecAccessRef. This | 
|  | // sets a label on the Keychain dialogs. Pass NULL as the second | 
|  | // argument to use the default trusted list; only allow the | 
|  | // current application to access without user confirmation. | 
|  | err = SecAccessCreate(label, NULL, &initial_access); | 
|  | // If we fail, just continue without a label. | 
|  | if (err) | 
|  | crypto::LogCSSMError("SecAccessCreate", err); | 
|  | } | 
|  |  | 
|  | // Create the key-pair. | 
|  | err = CreateRSAKeyPair(key_size_in_bits_, initial_access, | 
|  | &public_key, &private_key); | 
|  | if (err) | 
|  | goto failure; | 
|  |  | 
|  | // Get the public key data (DER sequence of modulus, exponent). | 
|  | CFDataRef key_data = NULL; | 
|  | err = SecKeychainItemExport(public_key, kSecFormatBSAFE, 0, NULL, | 
|  | &key_data); | 
|  | if (err) { | 
|  | crypto::LogCSSMError("SecKeychainItemExpor", err); | 
|  | goto failure; | 
|  | } | 
|  | base::ScopedCFTypeRef<CFDataRef> scoped_key_data(key_data); | 
|  |  | 
|  | // Create an ASN.1 encoder. | 
|  | err = SecAsn1CoderCreate(&coder); | 
|  | if (err) { | 
|  | crypto::LogCSSMError("SecAsn1CoderCreate", err); | 
|  | goto failure; | 
|  | } | 
|  |  | 
|  | // Fill in and DER-encode the PublicKeyAndChallenge: | 
|  | SignedPublicKeyAndChallenge spkac; | 
|  | memset(&spkac, 0, sizeof(spkac)); | 
|  | spkac.pkac.spki.algorithm.algorithm = CSSMOID_RSA; | 
|  | spkac.pkac.spki.subjectPublicKey.Length = | 
|  | CFDataGetLength(key_data) * 8;  // interpreted as a _bit_ count | 
|  | spkac.pkac.spki.subjectPublicKey.Data = | 
|  | const_cast<uint8_t*>(CFDataGetBytePtr(key_data)); | 
|  | spkac.pkac.challenge_string.Length = challenge_.length(); | 
|  | spkac.pkac.challenge_string.Data = | 
|  | reinterpret_cast<uint8_t*>(const_cast<char*>(challenge_.data())); | 
|  |  | 
|  | CSSM_DATA encoded; | 
|  | err = SecAsn1EncodeItem(coder, &spkac.pkac, | 
|  | kPublicKeyAndChallengeTemplate, &encoded); | 
|  | if (err) { | 
|  | crypto::LogCSSMError("SecAsn1EncodeItem", err); | 
|  | goto failure; | 
|  | } | 
|  |  | 
|  | // Compute a signature of the result: | 
|  | err = SignData(encoded, private_key, &signature); | 
|  | if (err) | 
|  | goto failure; | 
|  | spkac.signature.Data = signature.Data; | 
|  | spkac.signature.Length = signature.Length * 8;  // a _bit_ count | 
|  | spkac.signature_algorithm.algorithm = CSSMOID_MD5WithRSA; | 
|  | // TODO(snej): MD5 is weak. Can we use SHA1 instead? | 
|  | // See <https://bugzilla.mozilla.org/show_bug.cgi?id=549460> | 
|  |  | 
|  | // DER-encode the entire SignedPublicKeyAndChallenge: | 
|  | err = SecAsn1EncodeItem(coder, &spkac, | 
|  | kSignedPublicKeyAndChallengeTemplate, &encoded); | 
|  | if (err) { | 
|  | crypto::LogCSSMError("SecAsn1EncodeItem", err); | 
|  | goto failure; | 
|  | } | 
|  |  | 
|  | // Base64 encode the result. | 
|  | std::string input(reinterpret_cast<char*>(encoded.Data), encoded.Length); | 
|  | base::Base64Encode(input, &result); | 
|  | } | 
|  |  | 
|  | failure: | 
|  | if (err) | 
|  | OSSTATUS_LOG(ERROR, err) << "SSL Keygen failed!"; | 
|  | else | 
|  | VLOG(1) << "SSL Keygen succeeded! Output is: " << result; | 
|  |  | 
|  | // Remove keys from keychain if asked to during unit testing: | 
|  | if (!stores_key_) { | 
|  | if (public_key) | 
|  | SecKeychainItemDelete(reinterpret_cast<SecKeychainItemRef>(public_key)); | 
|  | if (private_key) | 
|  | SecKeychainItemDelete(reinterpret_cast<SecKeychainItemRef>(private_key)); | 
|  | } | 
|  |  | 
|  | // Clean up: | 
|  | free(signature.Data); | 
|  | if (coder) | 
|  | SecAsn1CoderRelease(coder); | 
|  | if (initial_access) | 
|  | CFRelease(initial_access); | 
|  | if (public_key) | 
|  | CFRelease(public_key); | 
|  | if (private_key) | 
|  | CFRelease(private_key); | 
|  | return result; | 
|  | } | 
|  |  | 
|  |  | 
|  | // Create an RSA key pair with size |size_in_bits|. |initial_access| | 
|  | // is passed as the initial access control list in Keychain. The | 
|  | // public and private keys are placed in |out_pub_key| and | 
|  | // |out_priv_key|, respectively. | 
|  | static OSStatus CreateRSAKeyPair(int size_in_bits, | 
|  | SecAccessRef initial_access, | 
|  | SecKeyRef* out_pub_key, | 
|  | SecKeyRef* out_priv_key) { | 
|  | OSStatus err; | 
|  | SecKeychainRef keychain; | 
|  | err = SecKeychainCopyDefault(&keychain); | 
|  | if (err) { | 
|  | crypto::LogCSSMError("SecKeychainCopyDefault", err); | 
|  | return err; | 
|  | } | 
|  | base::ScopedCFTypeRef<SecKeychainRef> scoped_keychain(keychain); | 
|  | { | 
|  | base::AutoLock locked(crypto::GetMacSecurityServicesLock()); | 
|  | err = SecKeyCreatePair( | 
|  | keychain, | 
|  | CSSM_ALGID_RSA, | 
|  | size_in_bits, | 
|  | 0LL, | 
|  | // public key usage and attributes: | 
|  | CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP, | 
|  | CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT, | 
|  | // private key usage and attributes: | 
|  | CSSM_KEYUSE_DECRYPT | CSSM_KEYUSE_SIGN | CSSM_KEYUSE_UNWRAP, | 
|  | CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT | | 
|  | CSSM_KEYATTR_SENSITIVE, | 
|  | initial_access, | 
|  | out_pub_key, out_priv_key); | 
|  | } | 
|  | if (err) | 
|  | crypto::LogCSSMError("SecKeyCreatePair", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static OSStatus CreateSignatureContext(SecKeyRef key, | 
|  | CSSM_ALGORITHMS algorithm, | 
|  | CSSM_CC_HANDLE* out_cc_handle) { | 
|  | OSStatus err; | 
|  | const CSSM_ACCESS_CREDENTIALS* credentials = NULL; | 
|  | { | 
|  | base::AutoLock locked(crypto::GetMacSecurityServicesLock()); | 
|  | err = SecKeyGetCredentials(key, | 
|  | CSSM_ACL_AUTHORIZATION_SIGN, | 
|  | kSecCredentialTypeDefault, | 
|  | &credentials); | 
|  | } | 
|  | if (err) { | 
|  | crypto::LogCSSMError("SecKeyGetCredentials", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | CSSM_CSP_HANDLE csp_handle = 0; | 
|  | { | 
|  | base::AutoLock locked(crypto::GetMacSecurityServicesLock()); | 
|  | err = SecKeyGetCSPHandle(key, &csp_handle); | 
|  | } | 
|  | if (err) { | 
|  | crypto::LogCSSMError("SecKeyGetCSPHandle", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | const CSSM_KEY* cssm_key = NULL; | 
|  | { | 
|  | base::AutoLock locked(crypto::GetMacSecurityServicesLock()); | 
|  | err = SecKeyGetCSSMKey(key, &cssm_key); | 
|  | } | 
|  | if (err) { | 
|  | crypto::LogCSSMError("SecKeyGetCSSMKey", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | err = CSSM_CSP_CreateSignatureContext(csp_handle, | 
|  | algorithm, | 
|  | credentials, | 
|  | cssm_key, | 
|  | out_cc_handle); | 
|  | if (err) | 
|  | crypto::LogCSSMError("CSSM_CSP_CreateSignatureContext", err); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static OSStatus SignData(CSSM_DATA data, | 
|  | SecKeyRef private_key, | 
|  | CSSM_DATA* signature) { | 
|  | CSSM_CC_HANDLE cc_handle; | 
|  | OSStatus err = CreateSignatureContext(private_key, | 
|  | CSSM_ALGID_MD5WithRSA, | 
|  | &cc_handle); | 
|  | if (err) { | 
|  | crypto::LogCSSMError("CreateSignatureContext", err); | 
|  | return err; | 
|  | } | 
|  | err = CSSM_SignData(cc_handle, &data, 1, CSSM_ALGID_NONE, signature); | 
|  | if (err) | 
|  | crypto::LogCSSMError("CSSM_SignData", err); | 
|  | CSSM_DeleteContext(cc_handle); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | }  // namespace net |