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

Unified Diff: net/socket/ssl_client_socket_openssl.cc

Issue 1178193002: Sign CertificateVerify messages on a background thread. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: fix net_nacl Created 5 years, 6 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/socket/ssl_client_socket_openssl.cc
diff --git a/net/socket/ssl_client_socket_openssl.cc b/net/socket/ssl_client_socket_openssl.cc
index e2a53fff9aad4c9123c1f71223bb6bc966bfe7fa..0e53ab0ee683b064168e4a52503c84707a2a3b39 100644
--- a/net/socket/ssl_client_socket_openssl.cc
+++ b/net/socket/ssl_client_socket_openssl.cc
@@ -17,11 +17,14 @@
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/environment.h"
+#include "base/lazy_instance.h"
#include "base/memory/singleton.h"
#include "base/metrics/histogram_macros.h"
#include "base/profiler/scoped_tracker.h"
+#include "base/stl_util.h"
#include "base/strings/string_piece.h"
#include "base/synchronization/lock.h"
+#include "base/threading/sequenced_worker_pool.h"
#include "base/threading/thread_local.h"
#include "base/values.h"
#include "crypto/ec_private_key.h"
@@ -42,6 +45,7 @@
#include "net/ssl/ssl_connection_status_flags.h"
#include "net/ssl/ssl_failure_state.h"
#include "net/ssl/ssl_info.h"
+#include "net/ssl/ssl_private_key.h"
#if defined(OS_WIN)
#include "base/win/windows_version.h"
@@ -50,7 +54,7 @@
#if defined(USE_OPENSSL_CERTS)
#include "net/ssl/openssl_client_key_store.h"
#else
-#include "net/ssl/openssl_platform_key.h"
+#include "net/ssl/ssl_platform_key.h"
#endif
namespace net {
@@ -68,7 +72,7 @@ namespace {
// This constant can be any non-negative/non-zero value (eg: it does not
// overlap with any value of the net::Error range, including net::OK).
-const int kNoPendingReadResult = 1;
+const int kNoPendingResult = 1;
// If a client doesn't have a list of protocols that it supports, but
// the server supports NPN, choosing "http/1.1" is the best answer.
@@ -145,6 +149,59 @@ int LogErrorCallback(const char* str, size_t len, void* context) {
return 1;
}
+bool EVP_MDToPrivateKeyHash(const EVP_MD* md, SSLPrivateKey::Hash* hash) {
+ switch (EVP_MD_type(md)) {
+ case NID_md5_sha1:
+ *hash = SSLPrivateKey::Hash::MD5_SHA1;
+ return true;
+ case NID_md5:
+ *hash = SSLPrivateKey::Hash::MD5;
+ return true;
+ case NID_sha1:
+ *hash = SSLPrivateKey::Hash::SHA1;
+ return true;
+ case NID_sha224:
+ *hash = SSLPrivateKey::Hash::SHA224;
Ryan Sleevi 2015/06/23 15:27:28 There are no plans to ever support this, right? Pr
davidben 2015/06/24 21:43:12 Oh hrm. Yes, you're right. In fact ssl_platform_ke
+ return true;
+ case NID_sha256:
+ *hash = SSLPrivateKey::Hash::SHA256;
+ return true;
+ case NID_sha384:
+ *hash = SSLPrivateKey::Hash::SHA384;
+ return true;
+ case NID_sha512:
+ *hash = SSLPrivateKey::Hash::SHA512;
+ return true;
+ default:
+ return false;
+ }
+}
+
+#if !defined(USE_OPENSSL_CERTS)
+class PlatformKeyTaskRunner {
+ public:
+ PlatformKeyTaskRunner() {
+ // Serialize all the private key operations on a single background
+ // thread to avoid problems with buggy smartcards.
+ worker_pool_ = new base::SequencedWorkerPool(1, "Platform Key Thread");
+ task_runner_ = worker_pool_->GetSequencedTaskRunnerWithShutdownBehavior(
+ worker_pool_->GetSequenceToken(),
+ base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
+ }
+
+ base::SequencedTaskRunner* task_runner() { return task_runner_.get(); }
Ryan Sleevi 2015/06/23 15:27:28 DANGER: There's a slight 'danger' in this API shap
davidben 2015/06/24 21:43:12 Pass() doesn't work unless we pass parameters in a
Ryan Sleevi 2015/06/30 10:18:22 Yes :)
+
+ private:
+ scoped_refptr<base::SequencedWorkerPool> worker_pool_;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformKeyTaskRunner);
+};
+
+base::LazyInstance<PlatformKeyTaskRunner>::Leaky g_platform_key_task_runner =
+ LAZY_INSTANCE_INITIALIZER;
+#endif // !USE_OPENSSL_CERTS
+
} // namespace
class SSLClientSocketOpenSSL::SSLContext {
@@ -165,6 +222,8 @@ class SSLClientSocketOpenSSL::SSLContext {
return SSL_set_ex_data(ssl, ssl_socket_data_index_, socket) != 0;
}
+ static const SSL_PRIVATE_KEY_METHOD kPrivateKeyMethod;
+
private:
friend struct DefaultSingletonTraits<SSLContext>;
@@ -236,6 +295,42 @@ class SSLClientSocketOpenSSL::SSLContext {
socket->InfoCallback(type, val);
}
+ static int PrivateKeyTypeCallback(SSL* ssl) {
+ SSLClientSocketOpenSSL* socket = GetInstance()->GetClientSocketFromSSL(ssl);
+ return socket->PrivateKeyTypeCallback();
+ }
+
+ static int PrivateKeySupportsDigestCallback(SSL* ssl, const EVP_MD* md) {
+ SSLClientSocketOpenSSL* socket = GetInstance()->GetClientSocketFromSSL(ssl);
+ return socket->PrivateKeySupportsDigestCallback(md);
+ }
+
+ static size_t PrivateKeyMaxSignatureLenCallback(SSL* ssl) {
+ SSLClientSocketOpenSSL* socket = GetInstance()->GetClientSocketFromSSL(ssl);
+ return socket->PrivateKeyMaxSignatureLenCallback();
+ }
+
+ static ssl_private_key_result_t PrivateKeySignCallback(SSL* ssl,
+ uint8_t* out,
+ size_t* out_len,
+ size_t max_out,
+ const EVP_MD* md,
+ const uint8_t* in,
+ size_t in_len) {
+ SSLClientSocketOpenSSL* socket = GetInstance()->GetClientSocketFromSSL(ssl);
+ return socket->PrivateKeySignCallback(out, out_len, max_out, md, in,
+ in_len);
+ }
+
+ static ssl_private_key_result_t PrivateKeySignCompleteCallback(
+ SSL* ssl,
+ uint8_t* out,
+ size_t* out_len,
+ size_t max_out) {
+ SSLClientSocketOpenSSL* socket = GetInstance()->GetClientSocketFromSSL(ssl);
+ return socket->PrivateKeySignCompleteCallback(out, out_len, max_out);
+ }
+
// This is the index used with SSL_get_ex_data to retrieve the owner
// SSLClientSocketOpenSSL object from an SSL instance.
int ssl_socket_data_index_;
@@ -250,6 +345,15 @@ class SSLClientSocketOpenSSL::SSLContext {
SSLClientSessionCacheOpenSSL session_cache_;
};
+const SSL_PRIVATE_KEY_METHOD
+ SSLClientSocketOpenSSL::SSLContext::kPrivateKeyMethod = {
+ &SSLClientSocketOpenSSL::SSLContext::PrivateKeyTypeCallback,
+ &SSLClientSocketOpenSSL::SSLContext::PrivateKeySupportsDigestCallback,
+ &SSLClientSocketOpenSSL::SSLContext::PrivateKeyMaxSignatureLenCallback,
+ &SSLClientSocketOpenSSL::SSLContext::PrivateKeySignCallback,
+ &SSLClientSocketOpenSSL::SSLContext::PrivateKeySignCompleteCallback,
+};
+
// PeerCertificateChain is a helper object which extracts the certificate
// chain, as given by the server, from an OpenSSL socket and performs the needed
// resource management. The first element of the chain is the leaf certificate
@@ -350,7 +454,7 @@ SSLClientSocketOpenSSL::SSLClientSocketOpenSSL(
const SSLClientSocketContext& context)
: transport_send_busy_(false),
transport_recv_busy_(false),
- pending_read_error_(kNoPendingReadResult),
+ pending_read_error_(kNoPendingResult),
pending_read_ssl_error_(SSL_ERROR_NONE),
transport_read_error_(OK),
transport_write_error_(OK),
@@ -372,6 +476,7 @@ SSLClientSocketOpenSSL::SSLClientSocketOpenSSL(
handshake_completed_(false),
certificate_verified_(false),
ssl_failure_state_(SSL_FAILURE_NONE),
+ signature_result_(kNoPendingResult),
transport_security_state_(context.transport_security_state),
policy_enforcer_(context.cert_policy_enforcer),
net_log_(transport_->socket()->NetLog()),
@@ -493,7 +598,7 @@ void SSLClientSocketOpenSSL::Disconnect() {
user_write_buf_ = NULL;
user_write_buf_len_ = 0;
- pending_read_error_ = kNoPendingReadResult;
+ pending_read_error_ = kNoPendingResult;
pending_read_ssl_error_ = SSL_ERROR_NONE;
pending_read_error_info_ = OpenSSLErrorInfo();
@@ -516,6 +621,10 @@ void SSLClientSocketOpenSSL::Disconnect() {
certificate_verified_ = false;
channel_id_request_.Cancel();
ssl_failure_state_ = SSL_FAILURE_NONE;
+
+ private_key_.reset();
+ signature_result_ = kNoPendingResult;
+ signature_.clear();
}
bool SSLClientSocketOpenSSL::IsConnected() const {
@@ -937,6 +1046,12 @@ int SSLClientSocketOpenSSL::DoHandshake() {
!ssl_config_.send_client_cert) {
return ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
}
+ if (ssl_error == SSL_ERROR_WANT_PRIVATE_KEY_OPERATION) {
+ DCHECK(private_key_);
+ DCHECK_NE(kNoPendingResult, signature_result_);
+ GotoState(STATE_HANDSHAKE);
+ return ERR_IO_PENDING;
+ }
OpenSSLErrorInfo error_info;
net_error = MapOpenSSLErrorWithDetails(ssl_error, err_tracer, &error_info);
@@ -1265,32 +1380,9 @@ void SSLClientSocketOpenSSL::OnSendComplete(int result) {
return;
}
- // OnSendComplete may need to call DoPayloadRead while the renegotiation
- // handshake is in progress.
- int rv_read = ERR_IO_PENDING;
- int rv_write = ERR_IO_PENDING;
- bool network_moved;
- do {
- if (user_read_buf_.get())
- rv_read = DoPayloadRead();
- if (user_write_buf_.get())
- rv_write = DoPayloadWrite();
- network_moved = DoTransportIO();
- } while (rv_read == ERR_IO_PENDING && rv_write == ERR_IO_PENDING &&
- (user_read_buf_.get() || user_write_buf_.get()) && network_moved);
-
- // Performing the Read callback may cause |this| to be deleted. If this
- // happens, the Write callback should not be invoked. Guard against this by
- // holding a WeakPtr to |this| and ensuring it's still valid.
- base::WeakPtr<SSLClientSocketOpenSSL> guard(weak_factory_.GetWeakPtr());
- if (user_read_buf_.get() && rv_read != ERR_IO_PENDING)
- DoReadCallback(rv_read);
-
- if (!guard.get())
- return;
-
- if (user_write_buf_.get() && rv_write != ERR_IO_PENDING)
- DoWriteCallback(rv_write);
+ // OnSendComplete may need to run the Read state machine while a
+ // renegotiation handshake is in progress.
+ RunReadWriteLoops();
}
void SSLClientSocketOpenSSL::OnRecvComplete(int result) {
@@ -1388,9 +1480,9 @@ int SSLClientSocketOpenSSL::DoPayloadRead() {
DCHECK(user_read_buf_.get());
int rv;
- if (pending_read_error_ != kNoPendingReadResult) {
+ if (pending_read_error_ != kNoPendingResult) {
rv = pending_read_error_;
- pending_read_error_ = kNoPendingReadResult;
+ pending_read_error_ = kNoPendingResult;
if (rv == 0) {
net_log_.AddByteTransferEvent(NetLog::TYPE_SSL_SOCKET_BYTES_RECEIVED,
rv, user_read_buf_->data());
@@ -1432,6 +1524,11 @@ int SSLClientSocketOpenSSL::DoPayloadRead() {
} else if (pending_read_ssl_error_ == SSL_ERROR_WANT_X509_LOOKUP &&
!ssl_config_.send_client_cert) {
pending_read_error_ = ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
+ } else if (pending_read_ssl_error_ ==
+ SSL_ERROR_WANT_PRIVATE_KEY_OPERATION) {
+ DCHECK(private_key_);
+ DCHECK_NE(kNoPendingResult, signature_result_);
+ pending_read_error_ = ERR_IO_PENDING;
} else {
pending_read_error_ = MapOpenSSLErrorWithDetails(
pending_read_ssl_error_, err_tracer, &pending_read_error_info_);
@@ -1456,12 +1553,12 @@ int SSLClientSocketOpenSSL::DoPayloadRead() {
// call to DoPayloadRead(), and thus it is important to check SSL_read() on
// subsequent invocations to see if a complete record may now be read.
if (pending_read_error_ == ERR_IO_PENDING)
- pending_read_error_ = kNoPendingReadResult;
+ pending_read_error_ = kNoPendingResult;
} else {
// No bytes were returned. Return the pending read error immediately.
- DCHECK_NE(kNoPendingReadResult, pending_read_error_);
+ DCHECK_NE(kNoPendingResult, pending_read_error_);
rv = pending_read_error_;
- pending_read_error_ = kNoPendingReadResult;
+ pending_read_error_ = kNoPendingResult;
}
if (rv >= 0) {
@@ -1489,6 +1586,8 @@ int SSLClientSocketOpenSSL::DoPayloadWrite() {
}
int ssl_error = SSL_get_error(ssl_, rv);
+ if (ssl_error == SSL_ERROR_WANT_PRIVATE_KEY_OPERATION)
+ return ERR_IO_PENDING;
OpenSSLErrorInfo error_info;
int net_error = MapOpenSSLErrorWithDetails(ssl_error, err_tracer,
&error_info);
@@ -1501,6 +1600,33 @@ int SSLClientSocketOpenSSL::DoPayloadWrite() {
return net_error;
}
+void SSLClientSocketOpenSSL::RunReadWriteLoops() {
+ int rv_read = ERR_IO_PENDING;
+ int rv_write = ERR_IO_PENDING;
+ bool network_moved;
+ do {
+ if (user_read_buf_.get())
+ rv_read = DoPayloadRead();
+ if (user_write_buf_.get())
+ rv_write = DoPayloadWrite();
+ network_moved = DoTransportIO();
+ } while (rv_read == ERR_IO_PENDING && rv_write == ERR_IO_PENDING &&
+ (user_read_buf_.get() || user_write_buf_.get()) && network_moved);
+
+ // Performing the Read callback may cause |this| to be deleted. If this
+ // happens, the Write callback should not be invoked. Guard against this by
+ // holding a WeakPtr to |this| and ensuring it's still valid.
+ base::WeakPtr<SSLClientSocketOpenSSL> guard(weak_factory_.GetWeakPtr());
+ if (user_read_buf_.get() && rv_read != ERR_IO_PENDING)
+ DoReadCallback(rv_read);
+
+ if (!guard.get())
+ return;
+
+ if (user_write_buf_.get() && rv_write != ERR_IO_PENDING)
+ DoWriteCallback(rv_write);
+}
+
int SSLClientSocketOpenSSL::BufferSend(void) {
if (transport_send_busy_)
return ERR_IO_PENDING;
@@ -1684,18 +1810,16 @@ int SSLClientSocketOpenSSL::ClientCertRequestCallback(SSL* ssl) {
return -1;
}
- // TODO(davidben): With Linux client auth support, this should be
- // conditioned on OS_ANDROID and then, with https://crbug.com/394131,
- // removed altogether. OpenSSLClientKeyStore is mostly an artifact of the
- // net/ client auth API lacking a private key handle.
+ if (!SSL_use_certificate(ssl_, leaf_x509.get()) ||
+ !SSL_set1_chain(ssl_, chain.get())) {
+ LOG(WARNING) << "Failed to set client certificate";
+ return -1;
+ }
+
#if defined(USE_OPENSSL_CERTS)
crypto::ScopedEVP_PKEY privkey =
OpenSSLClientKeyStore::GetInstance()->FetchClientCertPrivateKey(
ssl_config_.client_cert.get());
-#else // !defined(USE_OPENSSL_CERTS)
- crypto::ScopedEVP_PKEY privkey =
- FetchClientCertPrivateKey(ssl_config_.client_cert.get());
-#endif // defined(USE_OPENSSL_CERTS)
if (!privkey) {
// Could not find the private key. Fail the handshake and surface an
// appropriate error to the caller.
@@ -1703,13 +1827,29 @@ int SSLClientSocketOpenSSL::ClientCertRequestCallback(SSL* ssl) {
OpenSSLPutNetError(FROM_HERE, ERR_SSL_CLIENT_AUTH_CERT_NO_PRIVATE_KEY);
return -1;
}
-
- if (!SSL_use_certificate(ssl_, leaf_x509.get()) ||
- !SSL_use_PrivateKey(ssl_, privkey.get()) ||
- !SSL_set1_chain(ssl_, chain.get())) {
- LOG(WARNING) << "Failed to set client certificate";
+ if (!SSL_use_PrivateKey(ssl_, privkey.get())) {
+ LOG(WARNING) << "Failed to set private key";
return -1;
}
+#else // !USE_OPENSSL_CERTS
+ // TODO(davidben): Move Android to the SSLPrivateKey codepath and disable
+ // client auth on NaCl altogether.
Ryan Sleevi 2015/06/23 15:27:28 Does this TODO really belong here, or on line 1820
davidben 2015/06/24 21:43:12 Done.
+ //
+ // TODO(davidben): Lift this call up to the embedder so we can actually test
+ // this code. https://crbug.com/394131
+ private_key_ = FetchClientCertPrivateKey(
+ ssl_config_.client_cert.get(),
+ g_platform_key_task_runner.Get().task_runner());
+ if (!private_key_) {
+ // Could not find the private key. Fail the handshake and surface an
+ // appropriate error to the caller.
+ LOG(WARNING) << "Client cert found without private key";
+ OpenSSLPutNetError(FROM_HERE, ERR_SSL_CLIENT_AUTH_CERT_NO_PRIVATE_KEY);
+ return -1;
+ }
+
+ SSL_set_private_key_method(ssl_, &SSLContext::kPrivateKeyMethod);
+#endif // USE_OPENSSL_CERTS
int cert_count = 1 + sk_X509_num(chain.get());
net_log_.AddEvent(NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
@@ -1931,4 +2071,99 @@ bool SSLClientSocketOpenSSL::IsRenegotiationAllowed() const {
return false;
}
+int SSLClientSocketOpenSSL::PrivateKeyTypeCallback() {
+ switch (private_key_->GetType()) {
+ case SSLPrivateKey::Type::RSA:
+ return EVP_PKEY_RSA;
+ case SSLPrivateKey::Type::ECDSA:
+ return EVP_PKEY_EC;
+ }
+ NOTREACHED();
+ return EVP_PKEY_NONE;
+}
+
+int SSLClientSocketOpenSSL::PrivateKeySupportsDigestCallback(const EVP_MD* md) {
+ SSLPrivateKey::Hash hash;
+ return EVP_MDToPrivateKeyHash(md, &hash) && private_key_->SupportsHash(hash);
+}
+
+size_t SSLClientSocketOpenSSL::PrivateKeyMaxSignatureLenCallback() {
+ return private_key_->GetMaxSignatureLength();
+}
+
+ssl_private_key_result_t SSLClientSocketOpenSSL::PrivateKeySignCallback(
+ uint8_t* out,
+ size_t* out_len,
+ size_t max_out,
+ const EVP_MD* md,
+ const uint8_t* in,
+ size_t in_len) {
+ DCHECK_EQ(kNoPendingResult, signature_result_);
+ DCHECK(signature_.empty());
+ DCHECK(private_key_);
+
+ net_log_.BeginEvent(NetLog::TYPE_SSL_PRIVATE_KEY_OPERATION);
+
+ SSLPrivateKey::Hash hash;
+ if (!EVP_MDToPrivateKeyHash(md, &hash)) {
+ OpenSSLPutNetError(FROM_HERE, ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED);
+ return ssl_private_key_failure;
+ }
+
+ signature_result_ = ERR_IO_PENDING;
+ private_key_->SignDigest(
+ hash, base::StringPiece(reinterpret_cast<const char*>(in), in_len),
+ base::Bind(&SSLClientSocketOpenSSL::OnPrivateKeySignComplete,
+ weak_factory_.GetWeakPtr()));
+ return ssl_private_key_retry;
+}
+
+ssl_private_key_result_t SSLClientSocketOpenSSL::PrivateKeySignCompleteCallback(
+ uint8_t* out,
+ size_t* out_len,
+ size_t max_out) {
+ DCHECK_NE(kNoPendingResult, signature_result_);
+ DCHECK(private_key_);
+
+ if (signature_result_ == ERR_IO_PENDING)
+ return ssl_private_key_retry;
+ if (signature_result_ != OK) {
+ OpenSSLPutNetError(FROM_HERE, signature_result_);
+ return ssl_private_key_failure;
+ }
+ if (signature_.size() > max_out) {
+ LOG(ERROR) << "Buffer too small";
Ryan Sleevi 2015/06/23 15:27:28 Is the message necessary? I generally grump about
davidben 2015/06/24 21:43:12 Removed.
+ OpenSSLPutNetError(FROM_HERE, ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED);
+ return ssl_private_key_failure;
+ }
+ memcpy(out, vector_as_array(&signature_), signature_.size());
+ *out_len = signature_.size();
+ signature_.clear();
+ return ssl_private_key_success;
+}
+
+void SSLClientSocketOpenSSL::OnPrivateKeySignComplete(
+ Error error,
+ const std::vector<uint8_t>& signature) {
+ DCHECK_EQ(ERR_IO_PENDING, signature_result_);
+ DCHECK(signature_.empty());
+ DCHECK(private_key_);
+
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_PRIVATE_KEY_OPERATION,
+ error);
+
+ signature_result_ = error;
+ if (signature_result_ == OK)
+ signature_ = signature;
+
+ if (next_handshake_state_ == STATE_HANDSHAKE) {
+ OnHandshakeIOComplete(signature_result_);
+ return;
+ }
+
+ // Either Read or Write may be blocked on an asynchronous private
+ // key operation during a renegotiation.
+ RunReadWriteLoops();
+}
+
} // namespace net

Powered by Google App Engine
This is Rietveld 408576698