Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(421)

Unified Diff: net/ssl/openssl_platform_key_mac.cc

Issue 396803002: Implement TLS client auth in the OS X OpenSSL port. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: EVP_PKEY_set1_RSA has a saner ownership story. Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698