// 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