Chromium Code Reviews| Index: net/ssl/openssl_platform_key_mac.cc |
| diff --git a/net/ssl/openssl_platform_key_mac.cc b/net/ssl/openssl_platform_key_mac.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..53f7e995cff3447be5d6a1272b567adb2f6add22 |
| --- /dev/null |
| +++ b/net/ssl/openssl_platform_key_mac.cc |
| @@ -0,0 +1,341 @@ |
| +// Copyright 2014 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/ssl/openssl_platform_key.h" |
| + |
| +#include <openssl/err.h> |
| +#include <openssl/evp.h> |
| +#include <openssl/rsa.h> |
| + |
| +#include <Security/cssm.h> |
| +#include <Security/SecBase.h> |
| +#include <Security/SecCertificate.h> |
| +#include <Security/SecIdentity.h> |
| +#include <Security/SecKey.h> |
| + |
| +#include "base/lazy_instance.h" |
| +#include "base/location.h" |
| +#include "base/logging.h" |
| +#include "base/mac/mac_logging.h" |
| +#include "base/mac/scoped_cftyperef.h" |
| +#include "base/memory/scoped_ptr.h" |
| +#include "base/synchronization/lock.h" |
| +#include "crypto/mac_security_services_lock.h" |
| +#include "net/base/net_errors.h" |
| +#include "net/cert/x509_certificate.h" |
| +#include "net/ssl/openssl_ssl_util.h" |
| + |
| +namespace net { |
| + |
| +namespace { |
| + |
| +class ScopedCSSM_CC_HANDLE { |
| + public: |
| + ScopedCSSM_CC_HANDLE() : handle_(0) { |
| + } |
| + |
| + ~ScopedCSSM_CC_HANDLE() { |
| + reset(); |
| + } |
| + |
| + CSSM_CC_HANDLE get() const { |
| + return handle_; |
| + } |
| + |
| + void reset() { |
| + if (handle_) |
| + CSSM_DeleteContext(handle_); |
| + handle_ = 0; |
| + } |
| + |
| + CSSM_CC_HANDLE* InitializeInto() { |
| + reset(); |
| + return &handle_; |
| + } |
| + private: |
| + CSSM_CC_HANDLE handle_; |
| +}; |
| + |
| +// Looks up the private key for |certificate| in KeyChain and returns |
| +// a SecKeyRef or NULL on failure. The caller takes ownership of the |
| +// result. |
| +SecKeyRef FetchSecKeyRefForCertificate(const X509Certificate* certificate) { |
| + OSStatus status; |
| + base::ScopedCFTypeRef<SecIdentityRef> identity; |
| + { |
| + base::AutoLock lock(crypto::GetMacSecurityServicesLock()); |
| + status = SecIdentityCreateWithCertificate( |
| + NULL, certificate->os_cert_handle(), identity.InitializeInto()); |
| + } |
| + if (status != noErr) { |
| + OSSTATUS_LOG(WARNING, status); |
| + return NULL; |
| + } |
| + |
| + base::ScopedCFTypeRef<SecKeyRef> private_key; |
| + status = SecIdentityCopyPrivateKey(identity, private_key.InitializeInto()); |
| + if (status != noErr) { |
| + OSSTATUS_LOG(WARNING, status); |
| + return NULL; |
| + } |
| + |
| + return private_key.release(); |
| +} |
| + |
| + |
| +void ExDataFree(void* parent, |
| + void* ptr, |
| + CRYPTO_EX_DATA* ex_data, |
| + int idx, |
| + long argl, void* argp) { |
| + SecKeyRef key = reinterpret_cast<SecKeyRef>(ptr); |
| + if (key == NULL) |
| + return; |
| + |
| + CRYPTO_set_ex_data(ex_data, idx, NULL); |
| + CFRelease(key); |
| +} |
| + |
| +int ExDataDup(CRYPTO_EX_DATA* to, |
| + CRYPTO_EX_DATA* from, |
| + void* from_d, |
| + int idx, |
| + long argl, |
| + void* argp) { |
| + // This should never actually get called. |
| + NOTREACHED(); |
| + SecKeyRef* key = reinterpret_cast<SecKeyRef*>(from_d); |
| + if (*key) |
| + CFRetain(*key); |
| + return 0; |
| +} |
| + |
| +class OpenSSLExDataIndices { |
| + public: |
| + OpenSSLExDataIndices() |
| + : rsa_index_(RSA_get_ex_new_index(0, NULL, NULL, |
| + ExDataDup, ExDataFree)) { |
| + } |
| + |
| + int rsa_index() const { return rsa_index_; } |
| + |
| + private: |
| + int rsa_index_; |
| +}; |
| +base::LazyInstance<OpenSSLExDataIndices>::Leaky g_indices = |
| + LAZY_INSTANCE_INITIALIZER; |
| + |
| +int RsaIndex() { |
| + return g_indices.Get().rsa_index(); |
| +} |
| + |
| +int RsaMethodPubEnc(int flen, |
| + const unsigned char* from, |
| + unsigned char* to, |
| + RSA* rsa, |
| + int padding) { |
| + NOTIMPLEMENTED(); |
| + RSAerr(RSA_F_RSA_PUBLIC_ENCRYPT, RSA_R_RSA_OPERATIONS_NOT_SUPPORTED); |
| + return -1; |
| +} |
| + |
| +int RsaMethodPubDec(int flen, |
| + const unsigned char* from, |
| + unsigned char* to, |
| + RSA* rsa, |
| + int padding) { |
| + NOTIMPLEMENTED(); |
| + RSAerr(RSA_F_RSA_PUBLIC_DECRYPT, RSA_R_RSA_OPERATIONS_NOT_SUPPORTED); |
| + return -1; |
| +} |
| + |
| +int RsaMethodPrivEnc(int flen, |
| + const unsigned char *from, |
| + unsigned char *to, |
| + RSA *rsa, |
| + int padding) { |
| + // Only support PKCS#1 padding. |
| + DCHECK_EQ(RSA_PKCS1_PADDING, padding); |
| + if (padding != RSA_PKCS1_PADDING) { |
| + RSAerr(RSA_F_RSA_PRIVATE_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE); |
| + return -1; |
| + } |
| + |
| + SecKeyRef key = |
| + reinterpret_cast<SecKeyRef>(RSA_get_ex_data(rsa, RsaIndex())); |
| + if (!key) { |
| + LOG(WARNING) << "Null SecKeyRef passed to RsaMethodPrivEnc!"; |
| + RSAerr(RSA_F_RSA_PRIVATE_ENCRYPT, ERR_R_INTERNAL_ERROR); |
| + return -1; |
| + } |
| + |
| + CSSM_CSP_HANDLE csp_handle; |
| + OSStatus status = SecKeyGetCSPHandle(key, &csp_handle); |
| + if (status != noErr) { |
| + RSAerr(RSA_F_RSA_PRIVATE_ENCRYPT, ERR_R_INTERNAL_ERROR); |
| + return -1; |
| + } |
| + |
| + const CSSM_KEY* cssm_key; |
| + status = SecKeyGetCSSMKey(key, &cssm_key); |
| + if (status != noErr) { |
| + RSAerr(RSA_F_RSA_PRIVATE_ENCRYPT, ERR_R_INTERNAL_ERROR); |
| + return -1; |
| + } |
| + DCHECK_EQ(CSSM_ALGID_RSA, cssm_key->KeyHeader.AlgorithmId); |
| + |
| + // TODO(davidben): (Taken from TODO(rsleevi) in sslplatf.c) Should |
| + // it be kSecCredentialTypeNoUI? In Win32, at least, you can prevent |
| + // the UI by setting the provider handle on the certificate to be |
| + // opened with CRYPT_SILENT, but is there an equivalent? |
| + const CSSM_ACCESS_CREDENTIALS * cssm_creds = NULL; |
| + status = SecKeyGetCredentials(key, CSSM_ACL_AUTHORIZATION_SIGN, |
| + kSecCredentialTypeDefault, &cssm_creds); |
| + if (status != noErr) { |
| + OpenSSLPutNetError(FROM_HERE, ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED); |
| + return -1; |
| + } |
| + |
| + ScopedCSSM_CC_HANDLE cssm_signature; |
| + if (CSSM_CSP_CreateSignatureContext( |
| + csp_handle, CSSM_ALGID_RSA, cssm_creds, |
| + cssm_key, cssm_signature.InitializeInto()) != CSSM_OK) { |
| + OpenSSLPutNetError(FROM_HERE, ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED); |
| + return -1; |
| + } |
| + |
| + // Set RSA blinding. |
| + CSSM_CONTEXT_ATTRIBUTE blinding_attr; |
| + blinding_attr.AttributeType = CSSM_ATTRIBUTE_RSA_BLINDING; |
| + blinding_attr.AttributeLength = sizeof(uint32); |
| + blinding_attr.Attribute.Uint32 = 1; |
| + if (CSSM_UpdateContextAttributes( |
| + cssm_signature.get(), 1, &blinding_attr) != CSSM_OK) { |
| + OpenSSLPutNetError(FROM_HERE, ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED); |
| + return -1; |
| + } |
| + |
| + CSSM_DATA hash_data; |
| + hash_data.Length = flen; |
| + hash_data.Data = const_cast<uint8*>(from); |
| + |
| + CSSM_DATA signature_data; |
| + signature_data.Length = RSA_size(rsa); |
| + signature_data.Data = to; |
| + |
| + if (CSSM_SignData(cssm_signature.get(), &hash_data, 1, |
| + CSSM_ALGID_NONE, &signature_data) != CSSM_OK) { |
| + OpenSSLPutNetError(FROM_HERE, ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED); |
| + return -1; |
| + } |
| + |
| + return signature_data.Length; |
| +} |
| + |
| +int RsaMethodPrivDec(int flen, |
| + const unsigned char* from, |
| + unsigned char* to, |
| + RSA* rsa, |
| + int padding) { |
| + NOTIMPLEMENTED(); |
| + RSAerr(RSA_F_RSA_PRIVATE_DECRYPT, RSA_R_RSA_OPERATIONS_NOT_SUPPORTED); |
| + return -1; |
| +} |
| + |
| +const RSA_METHOD mac_rsa_method = { |
| + /* .name = */ "Mac signing-only RSA method", |
| + /* .rsa_pub_enc = */ RsaMethodPubEnc, |
| + /* .rsa_pub_dec = */ RsaMethodPubDec, |
| + /* .rsa_priv_enc = */ RsaMethodPrivEnc, |
| + /* .rsa_priv_dec = */ RsaMethodPrivDec, |
| + /* .rsa_mod_exp = */ NULL, |
| + /* .bn_mod_exp = */ NULL, |
| + /* .init = */ NULL, |
| + /* .finish = */ NULL, |
| + // This flag is necessary to tell OpenSSL to avoid checking the content |
| + // (i.e. internal fields) of the private key. Otherwise, it will complain |
| + // it's not valid for the certificate. |
| + /* .flags = */ RSA_METHOD_FLAG_NO_CHECK, |
| + /* .app_data = */ NULL, |
| + /* .rsa_sign = */ NULL, |
| + /* .rsa_verify = */ NULL, |
| + /* .rsa_keygen = */ NULL, |
| +}; |
| + |
| +crypto::ScopedEVP_PKEY CreateRSAWrapper(SecKeyRef key, |
| + const CSSM_KEY* cssm_key) { |
| + crypto::ScopedRSA rsa(RSA_new()); |
| + if (!rsa) |
| + return crypto::ScopedEVP_PKEY(); |
| + RSA_set_method(rsa.get(), &mac_rsa_method); |
| + CFRetain(key); |
| + RSA_set_ex_data(rsa.get(), RsaIndex(), key); |
| + |
| + // HACK: RSA_size() doesn't work with custom RSA_METHODs. To ensure that |
| + // it will return the right value, set the 'n' field of the RSA object |
| + // to match the private key's modulus. |
| + // TODO(davidben): Avoid this after the BoringSSL transition. |
| + size_t rsa_size = (cssm_key->KeyHeader.LogicalKeySizeInBits + 7) / 8; |
| + std::vector<uint8_t> bogus(rsa_size, 0xFF); |
| + crypto::ScopedBIGNUM bn(BN_bin2bn(&bogus[0], bogus.size(), NULL)); |
| + if (!bn) |
| + return crypto::ScopedEVP_PKEY(); |
| + rsa->n = bn.release(); |
| + |
| + DCHECK_EQ(rsa_size, (size_t)RSA_size(rsa.get())); |
| + |
| + crypto::ScopedEVP_PKEY pkey(EVP_PKEY_new()); |
| + if (!pkey) |
| + return crypto::ScopedEVP_PKEY(); |
| + |
| + if (!EVP_PKEY_set1_RSA(pkey.get(), rsa.get())) |
| + return crypto::ScopedEVP_PKEY(); |
| + |
| + return pkey.Pass(); |
| +} |
| + |
| +crypto::ScopedEVP_PKEY CreateECDSAWrapper(SecKeyRef key) { |
| + // TODO(davidben): Implement ECDSA after BoringSSL transition. Most |
| + // of the signing implementation can be shared with RsaMethodPrivEnc |
| + // and pulled into a common function. |
| + NOTIMPLEMENTED(); |
| + return crypto::ScopedEVP_PKEY(); |
| +} |
| + |
| +crypto::ScopedEVP_PKEY CreatePkeyWrapper(SecKeyRef key) { |
| + const CSSM_KEY* cssm_key; |
| + OSStatus status = SecKeyGetCSSMKey(key, &cssm_key); |
| + if (status != noErr) |
| + return crypto::ScopedEVP_PKEY(); |
| + |
| + switch (cssm_key->KeyHeader.AlgorithmId) { |
| + case CSSM_ALGID_RSA: |
| + return CreateRSAWrapper(key, cssm_key); |
| + case CSSM_ALGID_ECDSA: |
| + return CreateECDSAWrapper(key); |
| + default: |
| + // TODO(davidben): Filter out anything other than ECDSA and RSA |
| + // elsewhere. We don't support other key types. |
| + NOTREACHED(); |
| + LOG(ERROR) << "Unknown key type"; |
| + return crypto::ScopedEVP_PKEY(); |
| + } |
| +} |
| + |
| +} // namespace |
| + |
| +// Default missing implementation. |
|
wtc
2014/07/16 00:09:58
What does "missing implementation" mean?
davidben
2014/07/16 16:25:35
Oops, removed.
|
| +crypto::ScopedEVP_PKEY FetchClientCertPrivateKey( |
| + const X509Certificate* certificate) { |
| + // Look up the private key. |
| + base::ScopedCFTypeRef<SecKeyRef> private_key( |
| + FetchSecKeyRefForCertificate(certificate)); |
| + if (!private_key) |
| + return crypto::ScopedEVP_PKEY(); |
| + |
| + // Create an EVP_PKEY wrapper. |
| + return CreatePkeyWrapper(private_key.get()); |
| +} |
| + |
| +} // namespace net |