diff options
Diffstat (limited to 'chromium/net/base/keygen_handler_mac.cc')
-rw-r--r-- | chromium/net/base/keygen_handler_mac.cc | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/chromium/net/base/keygen_handler_mac.cc b/chromium/net/base/keygen_handler_mac.cc new file mode 100644 index 00000000000..63ea84751aa --- /dev/null +++ b/chromium/net/base/keygen_handler_mac.cc @@ -0,0 +1,325 @@ +// 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 |