// 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 #include #include #include #include #include #include #include #include #include #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_policy.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_; DISALLOW_COPY_AND_ASSIGN(ScopedCSSM_CC_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 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 private_key; status = SecIdentityCopyPrivateKey(identity, private_key.InitializeInto()); if (status != noErr) { OSSTATUS_LOG(WARNING, status); return NULL; } return private_key.release(); } extern const RSA_METHOD mac_rsa_method; extern const ECDSA_METHOD mac_ecdsa_method; // KeyExData contains the data that is contained in the EX_DATA of the // RSA and ECDSA objects that are created to wrap Mac system keys. struct KeyExData { KeyExData(SecKeyRef key, const CSSM_KEY* cssm_key) : key(key, base::scoped_policy::RETAIN), cssm_key(cssm_key) {} base::ScopedCFTypeRef key; const CSSM_KEY* cssm_key; }; // ExDataDup is called when one of the RSA or EC_KEY objects is // duplicated. This is not supported and should never happen. int ExDataDup(CRYPTO_EX_DATA* to, const CRYPTO_EX_DATA* from, void** from_d, int idx, long argl, void* argp) { CHECK_EQ((void*)NULL, *from_d); return 0; } // ExDataFree is called when one of the RSA or EC_KEY objects is freed. void ExDataFree(void* parent, void* ptr, CRYPTO_EX_DATA* ex_data, int idx, long argl, void* argp) { KeyExData* data = reinterpret_cast(ptr); delete data; } // BoringSSLEngine is a BoringSSL ENGINE that implements RSA and ECDSA // by forwarding the requested operations to Apple's CSSM // implementation. class BoringSSLEngine { public: BoringSSLEngine() : rsa_index_(RSA_get_ex_new_index(0 /* argl */, NULL /* argp */, NULL /* new_func */, ExDataDup, ExDataFree)), ec_key_index_(EC_KEY_get_ex_new_index(0 /* argl */, NULL /* argp */, NULL /* new_func */, ExDataDup, ExDataFree)), engine_(ENGINE_new()) { ENGINE_set_RSA_method( engine_, &mac_rsa_method, sizeof(mac_rsa_method)); ENGINE_set_ECDSA_method( engine_, &mac_ecdsa_method, sizeof(mac_ecdsa_method)); } int rsa_ex_index() const { return rsa_index_; } int ec_key_ex_index() const { return ec_key_index_; } const ENGINE* engine() const { return engine_; } private: const int rsa_index_; const int ec_key_index_; ENGINE* const engine_; }; base::LazyInstance::Leaky global_boringssl_engine = LAZY_INSTANCE_INITIALIZER; // Helper function for making a signature. // MakeCSSMSignature uses the key information in |ex_data| to sign the // |in_len| bytes pointed by |in|. It writes up to |max_out| bytes // into the buffer pointed to by |out|, setting |*out_len| to the // number of bytes written. It returns 1 on success and 0 on failure. int MakeCSSMSignature(const KeyExData* ex_data, size_t* out_len, uint8_t* out, size_t max_out, const uint8_t* in, size_t in_len) { CSSM_CSP_HANDLE csp_handle; OSStatus status = SecKeyGetCSPHandle(ex_data->key.get(), &csp_handle); if (status != noErr) { OSSTATUS_LOG(WARNING, status); OPENSSL_PUT_ERROR(RSA, sign_raw, ERR_R_INTERNAL_ERROR); return 0; } const CSSM_ACCESS_CREDENTIALS* cssm_creds = NULL; status = SecKeyGetCredentials(ex_data->key.get(), CSSM_ACL_AUTHORIZATION_SIGN, kSecCredentialTypeDefault, &cssm_creds); if (status != noErr) { OSSTATUS_LOG(WARNING, status); OpenSSLPutNetError(FROM_HERE, ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED); return 0; } ScopedCSSM_CC_HANDLE cssm_signature; if (CSSM_CSP_CreateSignatureContext( csp_handle, ex_data->cssm_key->KeyHeader.AlgorithmId, cssm_creds, ex_data->cssm_key, cssm_signature.InitializeInto()) != CSSM_OK) { OpenSSLPutNetError(FROM_HERE, ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED); return 0; } if (ex_data->cssm_key->KeyHeader.AlgorithmId == CSSM_ALGID_RSA) { // 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 0; } } CSSM_DATA hash_data; hash_data.Length = in_len; hash_data.Data = const_cast(in); CSSM_DATA signature_data; signature_data.Length = max_out; signature_data.Data = out; 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 0; } *out_len = signature_data.Length; return 1; } // Custom RSA_METHOD that uses the platform APIs for signing. const KeyExData* RsaGetExData(const RSA* rsa) { return reinterpret_cast( RSA_get_ex_data(rsa, global_boringssl_engine.Get().rsa_ex_index())); } size_t RsaMethodSize(const RSA *rsa) { const KeyExData *ex_data = RsaGetExData(rsa); return (ex_data->cssm_key->KeyHeader.LogicalKeySizeInBits + 7) / 8; } int RsaMethodEncrypt(RSA* rsa, size_t* out_len, uint8_t* out, size_t max_out, const uint8_t* in, size_t in_len, int padding) { NOTIMPLEMENTED(); OPENSSL_PUT_ERROR(RSA, encrypt, RSA_R_UNKNOWN_ALGORITHM_TYPE); return 0; } int RsaMethodSignRaw(RSA* rsa, size_t* out_len, uint8_t* out, size_t max_out, const uint8_t* in, size_t in_len, int padding) { // Only support PKCS#1 padding. DCHECK_EQ(RSA_PKCS1_PADDING, padding); if (padding != RSA_PKCS1_PADDING) { OPENSSL_PUT_ERROR(RSA, sign_raw, RSA_R_UNKNOWN_PADDING_TYPE); return 0; } const KeyExData *ex_data = RsaGetExData(rsa); if (!ex_data) { OPENSSL_PUT_ERROR(RSA, sign_raw, ERR_R_INTERNAL_ERROR); return 0; } DCHECK_EQ(CSSM_ALGID_RSA, ex_data->cssm_key->KeyHeader.AlgorithmId); return MakeCSSMSignature(ex_data, out_len, out, max_out, in, in_len); } int RsaMethodDecrypt(RSA* rsa, size_t* out_len, uint8_t* out, size_t max_out, const uint8_t* in, size_t in_len, int padding) { NOTIMPLEMENTED(); OPENSSL_PUT_ERROR(RSA, decrypt, RSA_R_UNKNOWN_ALGORITHM_TYPE); return 0; } int RsaMethodVerifyRaw(RSA* rsa, size_t* out_len, uint8_t* out, size_t max_out, const uint8_t* in, size_t in_len, int padding) { NOTIMPLEMENTED(); OPENSSL_PUT_ERROR(RSA, verify_raw, RSA_R_UNKNOWN_ALGORITHM_TYPE); return 0; } const RSA_METHOD mac_rsa_method = { { 0 /* references */, 1 /* is_static */ } /* common */, NULL /* app_data */, NULL /* init */, NULL /* finish */, RsaMethodSize, NULL /* sign */, NULL /* verify */, RsaMethodEncrypt, RsaMethodSignRaw, RsaMethodDecrypt, RsaMethodVerifyRaw, NULL /* private_transform */, NULL /* mod_exp */, NULL /* bn_mod_exp */, RSA_FLAG_OPAQUE, NULL /* keygen */, }; crypto::ScopedEVP_PKEY CreateRSAWrapper(SecKeyRef key, const CSSM_KEY* cssm_key) { crypto::ScopedRSA rsa( RSA_new_method(global_boringssl_engine.Get().engine())); if (!rsa) return crypto::ScopedEVP_PKEY(); RSA_set_ex_data( rsa.get(), global_boringssl_engine.Get().rsa_ex_index(), new KeyExData(key, cssm_key)); 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(); } // Custom ECDSA_METHOD that uses the platform APIs. // Note that for now, only signing through ECDSA_sign() is really supported. // all other method pointers are either stubs returning errors, or no-ops. const KeyExData* EcKeyGetExData(const EC_KEY* ec_key) { return reinterpret_cast(EC_KEY_get_ex_data( ec_key, global_boringssl_engine.Get().ec_key_ex_index())); } size_t EcdsaMethodGroupOrderSize(const EC_KEY* ec_key) { const KeyExData* ex_data = EcKeyGetExData(ec_key); // LogicalKeySizeInBits is the size of an EC public key. But an // ECDSA signature length depends on the size of the base point's // order. For P-256, P-384, and P-521, these two sizes are the same. return (ex_data->cssm_key->KeyHeader.LogicalKeySizeInBits + 7) / 8; } int EcdsaMethodSign(const uint8_t* digest, size_t digest_len, uint8_t* sig, unsigned int* sig_len, EC_KEY* ec_key) { const KeyExData *ex_data = EcKeyGetExData(ec_key); if (!ex_data) { OPENSSL_PUT_ERROR(RSA, sign_raw, ERR_R_INTERNAL_ERROR); return 0; } DCHECK_EQ(CSSM_ALGID_ECDSA, ex_data->cssm_key->KeyHeader.AlgorithmId); // TODO(davidben): Fix BoringSSL to make sig_len a size_t*. size_t out_len; int ret = MakeCSSMSignature( ex_data, &out_len, sig, ECDSA_size(ec_key), digest, digest_len); if (!ret) return 0; *sig_len = out_len; return 1; } int EcdsaMethodVerify(const uint8_t* digest, size_t digest_len, const uint8_t* sig, size_t sig_len, EC_KEY* eckey) { NOTIMPLEMENTED(); OPENSSL_PUT_ERROR(ECDSA, ECDSA_do_verify, ECDSA_R_NOT_IMPLEMENTED); return 0; } const ECDSA_METHOD mac_ecdsa_method = { { 0 /* references */, 1 /* is_static */ } /* common */, NULL /* app_data */, NULL /* init */, NULL /* finish */, EcdsaMethodGroupOrderSize, EcdsaMethodSign, EcdsaMethodVerify, ECDSA_FLAG_OPAQUE, }; crypto::ScopedEVP_PKEY CreateECDSAWrapper(SecKeyRef key, const CSSM_KEY* cssm_key) { crypto::ScopedEC_KEY ec_key( EC_KEY_new_method(global_boringssl_engine.Get().engine())); if (!ec_key) return crypto::ScopedEVP_PKEY(); EC_KEY_set_ex_data( ec_key.get(), global_boringssl_engine.Get().ec_key_ex_index(), new KeyExData(key, cssm_key)); crypto::ScopedEVP_PKEY pkey(EVP_PKEY_new()); if (!pkey) return crypto::ScopedEVP_PKEY(); if (!EVP_PKEY_set1_EC_KEY(pkey.get(), ec_key.get())) return crypto::ScopedEVP_PKEY(); return pkey.Pass(); } 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, cssm_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 crypto::ScopedEVP_PKEY FetchClientCertPrivateKey( const X509Certificate* certificate) { // Look up the private key. base::ScopedCFTypeRef private_key( FetchSecKeyRefForCertificate(certificate)); if (!private_key) return crypto::ScopedEVP_PKEY(); // Create an EVP_PKEY wrapper. return CreatePkeyWrapper(private_key.get()); } } // namespace net