| // 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/string_util.h" |
| #include "base/synchronization/lock.h" |
| #include "base/sys_string_conversions.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::mac::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::mac::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::mac::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 |