diff options
author | davidben@chromium.org <davidben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-29 07:51:36 +0000 |
---|---|---|
committer | davidben@chromium.org <davidben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-29 07:51:36 +0000 |
commit | 97a854f7376fd361d1c94128c554358b0c3bb13e (patch) | |
tree | 726427a3e8a69bcc3d14e2a8d3280f1e8cdc6c3e /net/ssl | |
parent | 1813c4fe5371be5da5cf7c70b56da6d99efb0210 (diff) | |
download | chromium_src-97a854f7376fd361d1c94128c554358b0c3bb13e.zip chromium_src-97a854f7376fd361d1c94128c554358b0c3bb13e.tar.gz chromium_src-97a854f7376fd361d1c94128c554358b0c3bb13e.tar.bz2 |
Implement TLS client auth in the OS X OpenSSL port.
This introduces a openssl_platform_key.h that looks up and wraps a platform
private key from the platform key store and returns an EVP_PKEY. It is
implemented on Mac and left as a stub on Windows. This will be refactored with
https://crbug.com/394131.
The USE_OPENSSL_CERTS case has been left intact to preserve the existing tests
on Linux but, possibly after the refactor, this will need to change as Linux and
CrOS will likely still use OpenSSL handles for X509Certificate but will not
likely want the OpenSSLClientKeyStore hack.
BUG=394131
Review URL: https://codereview.chromium.org/396803002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@286112 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/ssl')
-rw-r--r-- | net/ssl/openssl_client_key_store.cc | 14 | ||||
-rw-r--r-- | net/ssl/openssl_client_key_store.h | 7 | ||||
-rw-r--r-- | net/ssl/openssl_client_key_store_unittest.cc | 21 | ||||
-rw-r--r-- | net/ssl/openssl_platform_key.h | 26 | ||||
-rw-r--r-- | net/ssl/openssl_platform_key_mac.cc | 463 | ||||
-rw-r--r-- | net/ssl/openssl_platform_key_win.cc | 18 | ||||
-rw-r--r-- | net/ssl/openssl_ssl_util.cc | 204 | ||||
-rw-r--r-- | net/ssl/openssl_ssl_util.h | 40 |
8 files changed, 768 insertions, 25 deletions
diff --git a/net/ssl/openssl_client_key_store.cc b/net/ssl/openssl_client_key_store.cc index d7a32e8..ef38da6 100644 --- a/net/ssl/openssl_client_key_store.cc +++ b/net/ssl/openssl_client_key_store.cc @@ -107,22 +107,20 @@ bool OpenSSLClientKeyStore::RecordClientCertPrivateKey( return true; } -bool OpenSSLClientKeyStore::FetchClientCertPrivateKey( - const X509Certificate* client_cert, - crypto::ScopedEVP_PKEY* private_key) { +crypto::ScopedEVP_PKEY OpenSSLClientKeyStore::FetchClientCertPrivateKey( + const X509Certificate* client_cert) { if (!client_cert) - return false; + return crypto::ScopedEVP_PKEY(); crypto::ScopedEVP_PKEY pub_key(GetOpenSSLPublicKey(client_cert)); if (!pub_key.get()) - return false; + return crypto::ScopedEVP_PKEY(); int index = FindKeyPairIndex(pub_key.get()); if (index < 0) - return false; + return crypto::ScopedEVP_PKEY(); - private_key->reset(CopyEVP_PKEY(pairs_[index].private_key)); - return true; + return crypto::ScopedEVP_PKEY(CopyEVP_PKEY(pairs_[index].private_key)); } void OpenSSLClientKeyStore::Flush() { diff --git a/net/ssl/openssl_client_key_store.h b/net/ssl/openssl_client_key_store.h index d1d09c4..1cf2ced 100644 --- a/net/ssl/openssl_client_key_store.h +++ b/net/ssl/openssl_client_key_store.h @@ -49,11 +49,8 @@ class NET_EXPORT OpenSSLClientKeyStore { // Given a certificate's |public_key|, return the corresponding private // key that has been recorded previously by RecordClientCertPrivateKey(). // |cert| is a client certificate. - // |*private_key| will be reset to its matching private key on success. - // Returns true on success, false otherwise. This increments the reference - // count of the private key on success. - bool FetchClientCertPrivateKey(const X509Certificate* cert, - crypto::ScopedEVP_PKEY* private_key); + // Returns its matching private key on success, NULL otherwise. + crypto::ScopedEVP_PKEY FetchClientCertPrivateKey(const X509Certificate* cert); // Flush all recorded keys. void Flush(); diff --git a/net/ssl/openssl_client_key_store_unittest.cc b/net/ssl/openssl_client_key_store_unittest.cc index b04e109..7909afd 100644 --- a/net/ssl/openssl_client_key_store_unittest.cc +++ b/net/ssl/openssl_client_key_store_unittest.cc @@ -59,8 +59,7 @@ TEST_F(OpenSSLClientKeyStoreTest, Flush) { // Retrieve the private key. This should fail because the store // was flushed. - crypto::ScopedEVP_PKEY pkey; - ASSERT_FALSE(store_->FetchClientCertPrivateKey(cert_1.get(), &pkey)); + crypto::ScopedEVP_PKEY pkey = store_->FetchClientCertPrivateKey(cert_1.get()); ASSERT_FALSE(pkey.get()); } @@ -75,8 +74,7 @@ TEST_F(OpenSSLClientKeyStoreTest, FetchEmptyPrivateKey) { // Retrieve the private key now. This should fail because it was // never recorded in the store. - crypto::ScopedEVP_PKEY pkey; - ASSERT_FALSE(store_->FetchClientCertPrivateKey(cert_1.get(), &pkey)); + crypto::ScopedEVP_PKEY pkey = store_->FetchClientCertPrivateKey(cert_1.get()); ASSERT_FALSE(pkey.get()); } @@ -110,8 +108,8 @@ TEST_F(OpenSSLClientKeyStoreTest, RecordAndFetchPrivateKey) { // Retrieve the private key. This should increment the private key's // reference count. - crypto::ScopedEVP_PKEY pkey2; - ASSERT_TRUE(store_->FetchClientCertPrivateKey(cert_1.get(), &pkey2)); + crypto::ScopedEVP_PKEY pkey2 = + store_->FetchClientCertPrivateKey(cert_1.get()); ASSERT_EQ(pkey2.get(), priv_key.get()); ASSERT_EQ(3, EVP_PKEY_get_refcount(priv_key.get())); @@ -152,12 +150,11 @@ TEST_F(OpenSSLClientKeyStoreTest, RecordAndFetchTwoPrivateKeys) { // Retrieve the private key now. This shall succeed and increment // the private key's reference count. - crypto::ScopedEVP_PKEY fetch_key1; - ASSERT_TRUE(store_->FetchClientCertPrivateKey(cert_1.get(), - &fetch_key1)); - crypto::ScopedEVP_PKEY fetch_key2; - ASSERT_TRUE(store_->FetchClientCertPrivateKey(cert_2.get(), - &fetch_key2)); + crypto::ScopedEVP_PKEY fetch_key1 = + store_->FetchClientCertPrivateKey(cert_1.get()); + crypto::ScopedEVP_PKEY fetch_key2 = + store_->FetchClientCertPrivateKey(cert_2.get()); + EXPECT_TRUE(fetch_key1.get()); EXPECT_TRUE(fetch_key2.get()); diff --git a/net/ssl/openssl_platform_key.h b/net/ssl/openssl_platform_key.h new file mode 100644 index 0000000..9c695f6 --- /dev/null +++ b/net/ssl/openssl_platform_key.h @@ -0,0 +1,26 @@ +// 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. + +#ifndef NET_SSL_OPENSSL_PLATFORM_KEY_H_ +#define NET_SSL_OPENSSL_PLATFORM_KEY_H_ + +#include "crypto/scoped_openssl_types.h" + +namespace net { + +class X509Certificate; + +// Looks up the private key from the platform key store corresponding +// to |certificate|'s public key. Then wraps it in an OpenSSL EVP_PKEY +// structure to be used for SSL client auth. +// +// TODO(davidben): This combines looking up a private key with +// wrapping it in an OpenSSL structure. This will be separated with +// https://crbug.com/394131 +crypto::ScopedEVP_PKEY FetchClientCertPrivateKey( + const X509Certificate* certificate); + +} // namespace net + +#endif // NET_SSL_OPENSSL_PLATFORM_KEY_H_ diff --git a/net/ssl/openssl_platform_key_mac.cc b/net/ssl/openssl_platform_key_mac.cc new file mode 100644 index 0000000..b8bba78 --- /dev/null +++ b/net/ssl/openssl_platform_key_mac.cc @@ -0,0 +1,463 @@ +// 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_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<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(); +} + +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<SecKeyRef> 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(false); + 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<KeyExData*>(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<BoringSSLEngine>::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<uint8*>(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<const KeyExData*>( + 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 /* 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<const KeyExData*>(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<SecKeyRef> private_key( + FetchSecKeyRefForCertificate(certificate)); + if (!private_key) + return crypto::ScopedEVP_PKEY(); + + // Create an EVP_PKEY wrapper. + return CreatePkeyWrapper(private_key.get()); +} + +} // namespace net diff --git a/net/ssl/openssl_platform_key_win.cc b/net/ssl/openssl_platform_key_win.cc new file mode 100644 index 0000000..c7db5cb --- /dev/null +++ b/net/ssl/openssl_platform_key_win.cc @@ -0,0 +1,18 @@ +// 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 "base/logging.h" + +namespace net { + +crypto::ScopedEVP_PKEY FetchClientCertPrivateKey( + const X509Certificate* certificate) { + // TODO(davidben): Implement on Windows. + NOTIMPLEMENTED(); + return crypto::ScopedEVP_PKEY(); +} + +} // namespace net diff --git a/net/ssl/openssl_ssl_util.cc b/net/ssl/openssl_ssl_util.cc new file mode 100644 index 0000000..6b67716 --- /dev/null +++ b/net/ssl/openssl_ssl_util.cc @@ -0,0 +1,204 @@ +// 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_ssl_util.h" + +#include <errno.h> + +#include <openssl/err.h> +#include <openssl/ssl.h> + +#include "base/lazy_instance.h" +#include "base/location.h" +#include "base/logging.h" +#include "crypto/openssl_util.h" +#include "net/base/net_errors.h" + +namespace net { + +SslSetClearMask::SslSetClearMask() + : set_mask(0), + clear_mask(0) { +} + +void SslSetClearMask::ConfigureFlag(long flag, bool state) { + (state ? set_mask : clear_mask) |= flag; + // Make sure we haven't got any intersection in the set & clear options. + DCHECK_EQ(0, set_mask & clear_mask) << flag << ":" << state; +} + +namespace { + +class OpenSSLNetErrorLibSingleton { + public: + OpenSSLNetErrorLibSingleton() { + crypto::EnsureOpenSSLInit(); + + // Allocate a new error library value for inserting net errors into + // OpenSSL. This does not register any ERR_STRING_DATA for the errors, so + // stringifying error codes through OpenSSL will return NULL. + net_error_lib_ = ERR_get_next_error_library(); + } + + unsigned net_error_lib() const { return net_error_lib_; } + + private: + unsigned net_error_lib_; +}; + +base::LazyInstance<OpenSSLNetErrorLibSingleton>::Leaky g_openssl_net_error_lib = + LAZY_INSTANCE_INITIALIZER; + +unsigned OpenSSLNetErrorLib() { + return g_openssl_net_error_lib.Get().net_error_lib(); +} + +int MapOpenSSLErrorSSL(unsigned long error_code) { + DCHECK_EQ(ERR_LIB_SSL, ERR_GET_LIB(error_code)); + + DVLOG(1) << "OpenSSL SSL error, reason: " << ERR_GET_REASON(error_code) + << ", name: " << ERR_error_string(error_code, NULL); + switch (ERR_GET_REASON(error_code)) { + case SSL_R_READ_TIMEOUT_EXPIRED: + return ERR_TIMED_OUT; + case SSL_R_BAD_RESPONSE_ARGUMENT: + return ERR_INVALID_ARGUMENT; + case SSL_R_UNKNOWN_CERTIFICATE_TYPE: + case SSL_R_UNKNOWN_CIPHER_TYPE: + case SSL_R_UNKNOWN_KEY_EXCHANGE_TYPE: + case SSL_R_UNKNOWN_PKEY_TYPE: + case SSL_R_UNKNOWN_REMOTE_ERROR_TYPE: + case SSL_R_UNKNOWN_SSL_VERSION: + return ERR_NOT_IMPLEMENTED; + case SSL_R_UNSUPPORTED_SSL_VERSION: + case SSL_R_NO_CIPHER_MATCH: + case SSL_R_NO_SHARED_CIPHER: + case SSL_R_TLSV1_ALERT_INSUFFICIENT_SECURITY: + case SSL_R_TLSV1_ALERT_PROTOCOL_VERSION: + case SSL_R_UNSUPPORTED_PROTOCOL: + return ERR_SSL_VERSION_OR_CIPHER_MISMATCH; + case SSL_R_SSLV3_ALERT_BAD_CERTIFICATE: + case SSL_R_SSLV3_ALERT_UNSUPPORTED_CERTIFICATE: + case SSL_R_SSLV3_ALERT_CERTIFICATE_REVOKED: + case SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED: + case SSL_R_SSLV3_ALERT_CERTIFICATE_UNKNOWN: + case SSL_R_TLSV1_ALERT_ACCESS_DENIED: + case SSL_R_TLSV1_ALERT_UNKNOWN_CA: + return ERR_BAD_SSL_CLIENT_AUTH_CERT; + case SSL_R_BAD_DECOMPRESSION: + case SSL_R_SSLV3_ALERT_DECOMPRESSION_FAILURE: + return ERR_SSL_DECOMPRESSION_FAILURE_ALERT; + case SSL_R_SSLV3_ALERT_BAD_RECORD_MAC: + return ERR_SSL_BAD_RECORD_MAC_ALERT; + case SSL_R_TLSV1_ALERT_DECRYPT_ERROR: + return ERR_SSL_DECRYPT_ERROR_ALERT; + case SSL_R_TLSV1_UNRECOGNIZED_NAME: + return ERR_SSL_UNRECOGNIZED_NAME_ALERT; + case SSL_R_UNSAFE_LEGACY_RENEGOTIATION_DISABLED: + return ERR_SSL_UNSAFE_NEGOTIATION; + case SSL_R_WRONG_NUMBER_OF_KEY_BITS: + return ERR_SSL_WEAK_SERVER_EPHEMERAL_DH_KEY; + // SSL_R_UNKNOWN_PROTOCOL is reported if premature application data is + // received (see http://crbug.com/42538), and also if all the protocol + // versions supported by the server were disabled in this socket instance. + // Mapped to ERR_SSL_PROTOCOL_ERROR for compatibility with other SSL sockets + // in the former scenario. + case SSL_R_UNKNOWN_PROTOCOL: + case SSL_R_SSL_HANDSHAKE_FAILURE: + case SSL_R_DECRYPTION_FAILED: + case SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC: + case SSL_R_DH_PUBLIC_VALUE_LENGTH_IS_WRONG: + case SSL_R_DIGEST_CHECK_FAILED: + case SSL_R_DUPLICATE_COMPRESSION_ID: + case SSL_R_ECGROUP_TOO_LARGE_FOR_CIPHER: + case SSL_R_ENCRYPTED_LENGTH_TOO_LONG: + case SSL_R_ERROR_IN_RECEIVED_CIPHER_LIST: + case SSL_R_EXCESSIVE_MESSAGE_SIZE: + case SSL_R_EXTRA_DATA_IN_MESSAGE: + case SSL_R_GOT_A_FIN_BEFORE_A_CCS: + case SSL_R_ILLEGAL_PADDING: + case SSL_R_INVALID_CHALLENGE_LENGTH: + case SSL_R_INVALID_COMMAND: + case SSL_R_INVALID_PURPOSE: + case SSL_R_INVALID_STATUS_RESPONSE: + case SSL_R_INVALID_TICKET_KEYS_LENGTH: + case SSL_R_KEY_ARG_TOO_LONG: + case SSL_R_READ_WRONG_PACKET_TYPE: + // SSL_do_handshake reports this error when the server responds to a + // ClientHello with a fatal close_notify alert. + case SSL_AD_REASON_OFFSET + SSL_AD_CLOSE_NOTIFY: + case SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE: + // TODO(joth): SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE may be returned from the + // server after receiving ClientHello if there's no common supported cipher. + // Ideally we'd map that specific case to ERR_SSL_VERSION_OR_CIPHER_MISMATCH + // to match the NSS implementation. See also http://goo.gl/oMtZW + case SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE: + case SSL_R_SSLV3_ALERT_NO_CERTIFICATE: + case SSL_R_SSLV3_ALERT_ILLEGAL_PARAMETER: + case SSL_R_TLSV1_ALERT_DECODE_ERROR: + case SSL_R_TLSV1_ALERT_DECRYPTION_FAILED: + case SSL_R_TLSV1_ALERT_EXPORT_RESTRICTION: + case SSL_R_TLSV1_ALERT_INTERNAL_ERROR: + case SSL_R_TLSV1_ALERT_NO_RENEGOTIATION: + case SSL_R_TLSV1_ALERT_RECORD_OVERFLOW: + case SSL_R_TLSV1_ALERT_USER_CANCELLED: + return ERR_SSL_PROTOCOL_ERROR; + case SSL_R_CERTIFICATE_VERIFY_FAILED: + // The only way that the certificate verify callback can fail is if + // the leaf certificate changed during a renegotiation. + return ERR_SSL_SERVER_CERT_CHANGED; + case SSL_AD_REASON_OFFSET + SSL3_AD_INAPPROPRIATE_FALLBACK: + return ERR_SSL_INAPPROPRIATE_FALLBACK; + default: + LOG(WARNING) << "Unmapped error reason: " << ERR_GET_REASON(error_code); + return ERR_FAILED; + } +} + +} // namespace + +void OpenSSLPutNetError(const tracked_objects::Location& location, int err) { + // Net error codes are negative. Encode them as positive numbers. + err = -err; + if (err < 0 || err > 0xfff) { + // OpenSSL reserves 12 bits for the reason code. + NOTREACHED(); + err = ERR_INVALID_ARGUMENT; + } + ERR_put_error(OpenSSLNetErrorLib(), 0, err, + location.file_name(), location.line_number()); +} + +int MapOpenSSLError(int err, const crypto::OpenSSLErrStackTracer& tracer) { + switch (err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return ERR_IO_PENDING; + case SSL_ERROR_SYSCALL: + LOG(ERROR) << "OpenSSL SYSCALL error, earliest error code in " + "error queue: " << ERR_peek_error() << ", errno: " + << errno; + return ERR_SSL_PROTOCOL_ERROR; + case SSL_ERROR_SSL: + // Walk down the error stack to find an SSL or net error. + unsigned long error_code; + do { + error_code = ERR_get_error(); + if (ERR_GET_LIB(error_code) == ERR_LIB_SSL) { + return MapOpenSSLErrorSSL(error_code); + } else if (ERR_GET_LIB(error_code) == OpenSSLNetErrorLib()) { + // Net error codes are negative but encoded in OpenSSL as positive + // numbers. + return -ERR_GET_REASON(error_code); + } + } while (error_code != 0); + return ERR_SSL_PROTOCOL_ERROR; + default: + // TODO(joth): Implement full mapping. + LOG(WARNING) << "Unknown OpenSSL error " << err; + return ERR_SSL_PROTOCOL_ERROR; + } +} + +} // namespace net diff --git a/net/ssl/openssl_ssl_util.h b/net/ssl/openssl_ssl_util.h new file mode 100644 index 0000000..6721453 --- /dev/null +++ b/net/ssl/openssl_ssl_util.h @@ -0,0 +1,40 @@ +// 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. + +#ifndef NET_SSL_OPENSSL_SSL_UTIL_H_ +#define NET_SSL_OPENSSL_SSL_UTIL_H_ + +namespace crypto { +class OpenSSLErrStackTracer; +} + +namespace tracked_objects { +class Location; +} + +namespace net { + +// Puts a net error, |err|, on the error stack in OpenSSL. The file and line are +// extracted from |posted_from|. The function code of the error is left as 0. +void OpenSSLPutNetError(const tracked_objects::Location& posted_from, int err); + +// Utility to construct the appropriate set & clear masks for use the OpenSSL +// options and mode configuration functions. (SSL_set_options etc) +struct SslSetClearMask { + SslSetClearMask(); + void ConfigureFlag(long flag, bool state); + + long set_mask; + long clear_mask; +}; + +// Converts an OpenSSL error code into a net error code, walking the OpenSSL +// error stack if needed. Note that |tracer| is not currently used in the +// implementation, but is passed in anyway as this ensures the caller will clear +// any residual codes left on the error stack. +int MapOpenSSLError(int err, const crypto::OpenSSLErrStackTracer& tracer); + +} // namespace net + +#endif // NET_SSL_OPENSSL_SSL_UTIL_H_ |