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

Unified Diff: net/socket/ssl_client_socket_nss.cc

Issue 10454066: Move the core state machine of SSLClientSocketNSS into a thread-safe Core (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix win_rel by not caching the current threads task runner. See added comment Created 8 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
« no previous file with comments | « net/socket/ssl_client_socket_nss.h ('k') | net/socket/ssl_client_socket_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: net/socket/ssl_client_socket_nss.cc
diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc
index d45b4290024b9f06953cec466c666b522624a00b..49bd0b7564a178c704067f83a8d33c01fa601a8f 100644
--- a/net/socket/ssl_client_socket_nss.cc
+++ b/net/socket/ssl_client_socket_nss.cc
@@ -67,13 +67,17 @@
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/build_time.h"
+#include "base/callback_helpers.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/metrics/histogram.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
#include "base/string_number_conversions.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
+#include "base/thread_task_runner_handle.h"
#include "base/threading/thread_restrictions.h"
#include "base/values.h"
#include "crypto/ec_private_key.h"
@@ -246,60 +250,6 @@ BOOL WINAPI ClientCertFindCallback(PCCERT_CONTEXT cert_context,
#endif
-// PeerCertificateChain is a helper object which extracts the certificate
-// chain, as given by the server, from an NSS socket and performs the needed
-// resource management. The first element of the chain is the leaf certificate
-// and the other elements are in the order given by the server.
-class PeerCertificateChain {
- public:
- explicit PeerCertificateChain(PRFileDesc* nss_fd)
- : num_certs_(0),
- certs_(NULL) {
- SECStatus rv = SSL_PeerCertificateChain(nss_fd, NULL, &num_certs_, 0);
- DCHECK_EQ(rv, SECSuccess);
-
- certs_ = new CERTCertificate*[num_certs_];
- const unsigned expected_num_certs = num_certs_;
- rv = SSL_PeerCertificateChain(nss_fd, certs_, &num_certs_,
- expected_num_certs);
- DCHECK_EQ(rv, SECSuccess);
- DCHECK_EQ(num_certs_, expected_num_certs);
- }
-
- ~PeerCertificateChain() {
- for (unsigned i = 0; i < num_certs_; i++)
- CERT_DestroyCertificate(certs_[i]);
- delete[] certs_;
- }
-
- unsigned size() const { return num_certs_; }
-
- CERTCertificate* operator[](unsigned i) {
- DCHECK_LT(i, num_certs_);
- return certs_[i];
- }
-
- std::vector<base::StringPiece> AsStringPieceVector() const {
- std::vector<base::StringPiece> v(size());
- for (unsigned i = 0; i < size(); i++) {
- v[i] = base::StringPiece(
- reinterpret_cast<const char*>(certs_[i]->derCert.data),
- certs_[i]->derCert.len);
- }
-
- return v;
- }
-
- private:
- unsigned num_certs_;
- CERTCertificate** certs_;
-};
-
-void DestroyCertificates(CERTCertificate** certs, unsigned len) {
- for (unsigned i = 0; i < len; i++)
- CERT_DestroyCertificate(certs[i]);
-}
-
// DNSValidationResult enumerates the possible outcomes from processing a
// set of DNS records.
enum DNSValidationResult {
@@ -359,7 +309,7 @@ DNSValidationResult VerifyCAARecords(
}
// CheckDNSSECChain tries to validate a DNSSEC chain embedded in
-// |server_cert_nss_|. It returns true iff a chain is found that proves the
+// |server_cert_nss|. It returns true iff a chain is found that proves the
// value of a CAA record that contains a valid public key fingerprint.
// |port| contains the TCP port number that we connected to as CAA records can
// be specific to a given port.
@@ -421,335 +371,779 @@ DNSValidationResult CheckDNSSECChain(
return r;
}
-} // namespace
-
-SSLClientSocketNSS::SSLClientSocketNSS(ClientSocketHandle* transport_socket,
- const HostPortPair& host_and_port,
- const SSLConfig& ssl_config,
- SSLHostInfo* ssl_host_info,
- const SSLClientSocketContext& context)
- : transport_send_busy_(false),
- transport_recv_busy_(false),
- transport_recv_eof_(false),
- transport_(transport_socket),
- host_and_port_(host_and_port),
- ssl_config_(ssl_config),
- user_read_buf_len_(0),
- user_write_buf_len_(0),
- server_cert_nss_(NULL),
- server_cert_verify_result_(NULL),
- ssl_connection_status_(0),
- client_auth_cert_needed_(false),
- cert_verifier_(context.cert_verifier),
- domain_bound_cert_xtn_negotiated_(false),
- server_bound_cert_service_(context.server_bound_cert_service),
- domain_bound_cert_type_(CLIENT_CERT_INVALID_TYPE),
- domain_bound_cert_request_handle_(NULL),
- handshake_callback_called_(false),
- completed_handshake_(false),
- ssl_session_cache_shard_(context.ssl_session_cache_shard),
- predicted_cert_chain_correct_(false),
- next_handshake_state_(STATE_NONE),
- nss_fd_(NULL),
- nss_bufs_(NULL),
- net_log_(transport_socket->socket()->NetLog()),
- ssl_host_info_(ssl_host_info),
- transport_security_state_(context.transport_security_state),
- next_proto_status_(kNextProtoUnsupported),
- valid_thread_id_(base::kInvalidThreadId) {
- EnterFunction("");
+bool DomainBoundCertNegotiated(PRFileDesc* socket) {
+ // TODO(wtc,mattm): this is temporary while DBC support is changed into
+ // Channel ID.
+ return false;
}
-SSLClientSocketNSS::~SSLClientSocketNSS() {
- EnterFunction("");
- Disconnect();
- LeaveFunction("");
+void DestroyCertificates(CERTCertificate** certs, size_t len) {
+ for (size_t i = 0; i < len; i++)
+ CERT_DestroyCertificate(certs[i]);
}
-// static
-void SSLClientSocket::ClearSessionCache() {
- // SSL_ClearSessionCache can't be called before NSS is initialized. Don't
- // bother initializing NSS just to clear an empty SSL session cache.
- if (!NSS_IsInitialized())
+// Helper function to make it easier to call BoundNetLog::AddByteTransferEvent
+// from within the SSLClientSocketNSS::Core.
+// AddByteTransferEvent expects to receive a const char*, which within the
+// Core is backed by an IOBuffer. If the "const char*" is bound via
+// base::Bind and posted to another thread, and the IOBuffer that backs that
+// pointer then goes out of scope on the origin thread, this would result in
+// an invalid read of a stale pointer.
+// Instead, provide a signature that accepts an IOBuffer*, so that a reference
+// to the owning IOBuffer can be bound to the Callback. This ensures that the
+// IOBuffer will stay alive long enough to cross threads if needed.
+void LogByteTransferEvent(BoundNetLog* net_log, NetLog::EventType event_type,
+ int len, IOBuffer* buffer) {
+ if (!net_log)
return;
-
- SSL_ClearSessionCache();
+ net_log->AddByteTransferEvent(event_type, len, buffer->data());
}
-void SSLClientSocketNSS::GetSSLInfo(SSLInfo* ssl_info) {
- EnterFunction("");
- ssl_info->Reset();
- if (!server_cert_nss_)
- return;
+// PeerCertificateChain is a helper object which extracts the certificate
+// chain, as given by the server, from an NSS socket and performs the needed
+// resource management. The first element of the chain is the leaf certificate
+// and the other elements are in the order given by the server.
+class PeerCertificateChain {
+ public:
+ PeerCertificateChain() {}
+ PeerCertificateChain(const PeerCertificateChain& other);
+ ~PeerCertificateChain();
+ PeerCertificateChain& operator=(const PeerCertificateChain& other);
- ssl_info->cert_status = server_cert_verify_result_->cert_status;
- ssl_info->cert = server_cert_verify_result_->verified_cert;
- ssl_info->connection_status = ssl_connection_status_;
- ssl_info->public_key_hashes = server_cert_verify_result_->public_key_hashes;
- for (std::vector<SHA1Fingerprint>::const_iterator
- i = side_pinned_public_keys_.begin();
- i != side_pinned_public_keys_.end(); i++) {
- ssl_info->public_key_hashes.push_back(*i);
- }
- ssl_info->is_issued_by_known_root =
- server_cert_verify_result_->is_issued_by_known_root;
- ssl_info->client_cert_sent = WasDomainBoundCertSent() ||
- (ssl_config_.send_client_cert && ssl_config_.client_cert);
+ // Resets the current chain, freeing any resources, and updates the current
+ // chain to be a copy of the chain stored in |nss_fd|.
+ // If |nss_fd| is NULL, then the current certificate chain will be freed.
+ void Reset(PRFileDesc* nss_fd);
- PRUint16 cipher_suite =
- SSLConnectionStatusToCipherSuite(ssl_connection_status_);
- SSLCipherSuiteInfo cipher_info;
- SECStatus ok = SSL_GetCipherSuiteInfo(cipher_suite,
- &cipher_info, sizeof(cipher_info));
- if (ok == SECSuccess) {
- ssl_info->security_bits = cipher_info.effectiveKeyBits;
- } else {
- ssl_info->security_bits = -1;
- LOG(DFATAL) << "SSL_GetCipherSuiteInfo returned " << PR_GetError()
- << " for cipherSuite " << cipher_suite;
- }
+ // Returns the current certificate chain as a vector of DER-encoded
+ // base::StringPieces. The returned vector remains valid until Reset is
+ // called.
+ std::vector<base::StringPiece> AsStringPieceVector() const;
- PRBool last_handshake_resumed;
- ok = SSL_HandshakeResumedSession(nss_fd_, &last_handshake_resumed);
- if (ok == SECSuccess) {
- if (last_handshake_resumed) {
- ssl_info->handshake_type = SSLInfo::HANDSHAKE_RESUME;
- } else {
- ssl_info->handshake_type = SSLInfo::HANDSHAKE_FULL;
- }
+ bool empty() const { return certs_.empty(); }
+ size_t size() const { return certs_.size(); }
+
+ CERTCertificate* operator[](size_t index) const {
+ DCHECK_LT(index, certs_.size());
+ return certs_[index];
}
- LeaveFunction("");
-}
+ private:
+ std::vector<CERTCertificate*> certs_;
+};
-void SSLClientSocketNSS::GetSSLCertRequestInfo(
- SSLCertRequestInfo* cert_request_info) {
- EnterFunction("");
- // TODO(rch): switch SSLCertRequestInfo.host_and_port to a HostPortPair
- cert_request_info->host_and_port = host_and_port_.ToString();
- cert_request_info->client_certs = client_certs_;
- LeaveFunction(cert_request_info->client_certs.size());
+PeerCertificateChain::PeerCertificateChain(
+ const PeerCertificateChain& other) {
+ *this = other;
}
-int SSLClientSocketNSS::ExportKeyingMaterial(const base::StringPiece& label,
- bool has_context,
- const base::StringPiece& context,
- unsigned char* out,
- unsigned int outlen) {
- if (!IsConnected())
- return ERR_SOCKET_NOT_CONNECTED;
- SECStatus result = SSL_ExportKeyingMaterial(
- nss_fd_, label.data(), label.size(), has_context,
- reinterpret_cast<const unsigned char*>(context.data()),
- context.length(), out, outlen);
- if (result != SECSuccess) {
- LogFailedNSSFunction(net_log_, "SSL_ExportKeyingMaterial", "");
- return MapNSSError(PORT_GetError());
- }
- return OK;
+PeerCertificateChain::~PeerCertificateChain() {
+ Reset(NULL);
}
-SSLClientSocket::NextProtoStatus
-SSLClientSocketNSS::GetNextProto(std::string* proto,
- std::string* server_protos) {
- *proto = next_proto_;
- *server_protos = server_protos_;
- return next_proto_status_;
-}
+PeerCertificateChain& PeerCertificateChain::operator=(
+ const PeerCertificateChain& other) {
+ if (this == &other)
+ return *this;
-int SSLClientSocketNSS::Connect(const CompletionCallback& callback) {
- EnterFunction("");
- DCHECK(transport_.get());
- DCHECK(next_handshake_state_ == STATE_NONE);
- DCHECK(user_read_callback_.is_null());
- DCHECK(user_write_callback_.is_null());
- DCHECK(user_connect_callback_.is_null());
- DCHECK(!user_read_buf_);
- DCHECK(!user_write_buf_);
+ Reset(NULL);
+ certs_.reserve(other.certs_.size());
+ for (size_t i = 0; i < other.certs_.size(); ++i)
+ certs_.push_back(CERT_DupCertificate(other.certs_[i]));
- EnsureThreadIdAssigned();
+ return *this;
+}
- net_log_.BeginEvent(NetLog::TYPE_SSL_CONNECT, NULL);
+void PeerCertificateChain::Reset(PRFileDesc* nss_fd) {
+ for (size_t i = 0; i < certs_.size(); ++i)
+ CERT_DestroyCertificate(certs_[i]);
+ certs_.clear();
- int rv = Init();
- if (rv != OK) {
- net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_CONNECT, rv);
- return rv;
- }
+ if (nss_fd == NULL)
+ return;
- rv = InitializeSSLOptions();
- if (rv != OK) {
- net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_CONNECT, rv);
- return rv;
- }
+ unsigned int num_certs = 0;
+ SECStatus rv = SSL_PeerCertificateChain(nss_fd, NULL, &num_certs, 0);
+ DCHECK_EQ(SECSuccess, rv);
- rv = InitializeSSLPeerName();
- if (rv != OK) {
- net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_CONNECT, rv);
- return rv;
- }
+ // The handshake on |nss_fd| may not have completed.
+ if (num_certs == 0)
+ return;
- if (ssl_config_.cached_info_enabled && ssl_host_info_.get()) {
- GotoState(STATE_LOAD_SSL_HOST_INFO);
- } else {
- GotoState(STATE_HANDSHAKE);
- }
+ certs_.resize(num_certs);
+ const unsigned int expected_num_certs = num_certs;
+ rv = SSL_PeerCertificateChain(nss_fd, vector_as_array(&certs_),
+ &num_certs, expected_num_certs);
+ DCHECK_EQ(SECSuccess, rv);
+ DCHECK_EQ(expected_num_certs, num_certs);
+}
- rv = DoHandshakeLoop(OK);
- if (rv == ERR_IO_PENDING) {
- user_connect_callback_ = callback;
- } else {
- net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_CONNECT, rv);
+std::vector<base::StringPiece>
+PeerCertificateChain::AsStringPieceVector() const {
+ std::vector<base::StringPiece> v(certs_.size());
+ for (unsigned i = 0; i < certs_.size(); i++) {
+ v[i] = base::StringPiece(
+ reinterpret_cast<const char*>(certs_[i]->derCert.data),
+ certs_[i]->derCert.len);
}
- LeaveFunction("");
- return rv > OK ? OK : rv;
+ return v;
}
-void SSLClientSocketNSS::Disconnect() {
- EnterFunction("");
+// HandshakeState is a helper struct used to pass handshake state between
+// the NSS task runner and the network task runner.
+//
+// It contains members that may be read or written on the NSS task runner,
+// but which also need to be read from the network task runner. The NSS task
+// runner will notify the network task runner whenever this state changes, so
+// that the network task runner can safely make a copy, which avoids the need
+// for locking.
+struct HandshakeState {
+ HandshakeState() { Reset(); }
+
+ void Reset() {
+ next_proto_status = SSLClientSocket::kNextProtoUnsupported;
+ next_proto.clear();
+ server_protos.clear();
+ domain_bound_cert_type = CLIENT_CERT_INVALID_TYPE;
+ client_certs.clear();
+ server_cert_chain.Reset(NULL);
+ server_cert = NULL;
+ predicted_cert_chain_correct = false;
+ resumed_handshake = false;
+ ssl_connection_status = 0;
+ }
+
+ // Set to kNextProtoNegotiated if NPN was successfully negotiated, with the
+ // negotiated protocol stored in |next_proto|.
+ SSLClientSocket::NextProtoStatus next_proto_status;
+ std::string next_proto;
+ // If the server supports NPN, the protocols supported by the server.
+ std::string server_protos;
+
+ // The type of domain bound cert that was exchanged, or
+ // CLIENT_CERT_INVALID_TYPE if no domain bound cert was negotiated or sent.
+ SSLClientCertType domain_bound_cert_type;
+
+ // If the peer requests client certificate authentication, the set of
+ // certificates that matched the peer's criteria.
+ CertificateList client_certs;
+
+ // Set when the handshake fully completes.
+ //
+ // The server certificate is first received from NSS as an NSS certificate
+ // chain (|server_cert_chain|) and then converted into a platform-specific
+ // X509Certificate object (|server_cert|). It's possible for some
+ // certificates to be successfully parsed by NSS, and not by the platform
+ // libraries (i.e.: when running within a sandbox, different parsing
+ // algorithms, etc), so it's not safe to assume that |server_cert| will
+ // always be non-NULL.
+ PeerCertificateChain server_cert_chain;
+ scoped_refptr<X509Certificate> server_cert;
+
+ // True if we predicted a certificate chain (via
+ // Core::SetPredictedCertificates) and that prediction matched what the
+ // server sent.
+ bool predicted_cert_chain_correct;
+
+ // True if the current handshake was the result of TLS session resumption.
+ bool resumed_handshake;
+
+ // The negotiated security parameters (TLS version, cipher, extensions) of
+ // the SSL connection.
+ int ssl_connection_status;
+};
- CHECK(CalledOnValidThread());
+} // namespace
- // Shut down anything that may call us back.
- verifier_.reset();
- transport_->socket()->Disconnect();
+// SSLClientSocketNSS::Core provides a thread-safe, ref-counted core that is
+// able to marshal data between NSS functions and an underlying transport
+// socket.
+//
+// All public functions are meant to be called from the network task runner,
+// and any callbacks supplied will be invoked there as well, provided that
+// Detach() has not been called yet.
+//
+/////////////////////////////////////////////////////////////////////////////
+//
+// Threading within SSLClientSocketNSS and SSLClientSocketNSS::Core:
+//
+// Because NSS may block on either hardware or user input during operations
+// such as signing, creating certificates, or locating private keys, the Core
+// handles all of the interactions with the underlying NSS SSL socket, so
+// that these blocking calls can be executed on a dedicated task runner.
+//
+// Note that the network task runner and the NSS task runner may be executing
+// on the same thread. If that happens, then it's more performant to try to
+// complete as much work as possible synchronously, even if it might block,
+// rather than continually PostTask-ing to the same thread.
+//
+// Because NSS functions should only be called on the NSS task runner, while
+// I/O resources should only be accessed on the network task runner, most
+// public functions are implemented via three methods, each with different
+// task runner affinities.
+//
+// In the single-threaded mode (where the network and NSS task runners run on
+// the same thread), these are all attempted synchronously, while in the
+// multi-threaded mode, message passing is used.
+//
+// 1) NSS Task Runner: Execute NSS function (DoPayloadRead, DoPayloadWrite,
+// DoHandshake)
+// 2) NSS Task Runner: Prepare data to go from NSS to an IO function:
+// (BufferRecv, BufferSend)
+// 3) Network Task Runner: Perform IO on that data (DoBufferRecv,
+// DoBufferSend, DoGetDomainBoundCert, OnGetDomainBoundCertComplete)
+// 4) Both Task Runners: Callback for asynchronous completion or to marshal
+// data from the network task runner back to NSS (BufferRecvComplete,
+// BufferSendComplete, OnHandshakeIOComplete)
+//
+/////////////////////////////////////////////////////////////////////////////
+// Single-threaded example
+//
+// |--------------------------Network Task Runner--------------------------|
+// SSLClientSocketNSS Core (Transport Socket)
+// Read()
+// |-------------------------V
+// Read()
+// |
+// DoPayloadRead()
+// |
+// BufferRecv()
+// |
+// DoBufferRecv()
+// |-------------------------V
+// Read()
+// V-------------------------|
+// BufferRecvComplete()
+// |
+// PostOrRunCallback()
+// V-------------------------|
+// (Read Callback)
+//
+/////////////////////////////////////////////////////////////////////////////
+// Multi-threaded example:
+//
+// |--------------------Network Task Runner-------------|--NSS Task Runner--|
+// SSLClientSocketNSS Core Socket Core
+// Read()
+// |---------------------V
+// Read()
+// |-------------------------------V
+// Read()
+// |
+// DoPayloadRead()
+// |
+// BufferRecv
+// V-------------------------------|
+// DoBufferRecv
+// |----------------V
+// Read()
+// V----------------|
+// BufferRecvComplete()
+// |-------------------------------V
+// BufferRecvComplete()
+// |
+// PostOrRunCallback()
+// V-------------------------------|
+// PostOrRunCallback()
+// V---------------------|
+// (Read Callback)
+//
+/////////////////////////////////////////////////////////////////////////////
+class SSLClientSocketNSS::Core : public base::RefCountedThreadSafe<Core> {
+ public:
+ // Creates a new Core.
+ //
+ // Any calls to NSS are executed on the |nss_task_runner|, while any calls
+ // that need to operate on the underlying transport, net log, or server
+ // bound certificate fetching will happen on the |network_task_runner|, so
+ // that their lifetimes match that of the owning SSLClientSocketNSS.
+ //
+ // The caller retains ownership of |transport|, |net_log|, and
+ // |server_bound_cert_service|, and they will not be accessed once Detach()
+ // has been called.
+ Core(base::SequencedTaskRunner* network_task_runner,
+ base::SingleThreadTaskRunner* nss_task_runner,
+ ClientSocketHandle* transport,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ BoundNetLog* net_log,
+ ServerBoundCertService* server_bound_cert_service);
+
+ // Called on the network task runner.
+ // Transfers ownership of |socket|, an NSS SSL socket, and |buffers|, the
+ // underlying memio implementation, to the Core. Returns true if the Core
+ // was successfully registered with the socket.
+ bool Init(PRFileDesc* socket, memio_Private* buffers);
+
+ // Called on the network task runner.
+ // Sets the predicted certificate chain that the peer will send, for use
+ // with the TLS CachedInfo extension. If called, it must not be called
+ // before Init() or after Connect().
+ void SetPredictedCertificates(
+ const std::vector<std::string>& predicted_certificates);
+
+ // Called on the network task runner.
+ //
+ // Attempts to perform an SSL handshake. If the handshake cannot be
+ // completed synchronously, returns ERR_IO_PENDING, invoking |callback| on
+ // the network task runner once the handshake has completed. Otherwise,
+ // returns OK on success or a network error code on failure.
+ int Connect(const CompletionCallback& callback);
+
+ // Called on the network task runner.
+ // Signals that the resources owned by the network task runner are going
+ // away. No further callbacks will be invoked on the network task runner.
+ // May be called at any time.
+ void Detach();
+
+ // Called on the network task runner.
+ // Returns the current state of the underlying SSL socket. May be called at
+ // any time.
+ const HandshakeState& state() const { return network_handshake_state_; }
+
+ // Called on the network task runner.
+ // Read() and Write() mirror the net::Socket functions of the same name.
+ // If ERR_IO_PENDING is returned, |callback| will be invoked on the network
+ // task runner at a later point, unless the caller calls Detach().
+ int Read(IOBuffer* buf, int buf_len, const CompletionCallback& callback);
+ int Write(IOBuffer* buf, int buf_len, const CompletionCallback& callback);
- if (domain_bound_cert_request_handle_ != NULL) {
- server_bound_cert_service_->CancelRequest(
- domain_bound_cert_request_handle_);
- domain_bound_cert_request_handle_ = NULL;
- }
+ private:
+ friend class base::RefCountedThreadSafe<Core>;
+ ~Core();
+
+ enum State {
+ STATE_NONE,
+ STATE_HANDSHAKE,
+ STATE_GET_DOMAIN_BOUND_CERT_COMPLETE,
+ };
+
+ bool OnNSSTaskRunner() const;
+ bool OnNetworkTaskRunner() const;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Methods that are ONLY called on the NSS task runner:
+ ////////////////////////////////////////////////////////////////////////////
+
+ // Called by NSS during full handshakes to allow the application to
+ // verify the certificate. Instead of verifying the certificate in the midst
+ // of the handshake, SECSuccess is always returned and the peer's certificate
+ // is verified afterwards.
+ // This behaviour is an artifact of the original SSLClientSocketWin
+ // implementation, which could not verify the peer's certificate until after
+ // the handshake had completed, as well as bugs in NSS that prevent
+ // SSL_RestartHandshakeAfterCertReq from working.
+ static SECStatus OwnAuthCertHandler(void* arg,
+ PRFileDesc* socket,
+ PRBool checksig,
+ PRBool is_server);
+
+ // Callbacks called by NSS when the peer requests client certificate
+ // authentication.
+ // See the documentation in third_party/nss/ssl/ssl.h for the meanings of
+ // the arguments.
+#if defined(NSS_PLATFORM_CLIENT_AUTH)
+ // When NSS has been integrated with awareness of the underlying system
+ // cryptographic libraries, this callback allows the caller to supply a
+ // native platform certificate and key for use by NSS. At most, one of
+ // either (result_certs, result_private_key) or (result_nss_certificate,
+ // result_nss_private_key) should be set.
+ // |arg| contains a pointer to the current SSLClientSocketNSS::Core.
+ static SECStatus PlatformClientAuthHandler(
+ void* arg,
+ PRFileDesc* socket,
+ CERTDistNames* ca_names,
+ CERTCertList** result_certs,
+ void** result_private_key,
+ CERTCertificate** result_nss_certificate,
+ SECKEYPrivateKey** result_nss_private_key);
+#else
+ static SECStatus ClientAuthHandler(void* arg,
+ PRFileDesc* socket,
+ CERTDistNames* ca_names,
+ CERTCertificate** result_certificate,
+ SECKEYPrivateKey** result_private_key);
+#endif
+
+ // Called by NSS once the handshake has completed.
+ // |arg| contains a pointer to the current SSLClientSocketNSS::Core.
+ static void HandshakeCallback(PRFileDesc* socket, void* arg);
+
+ // Called by NSS if the peer supports the NPN handshake extension, to allow
+ // the application to select the protocol to use.
+ // See the documentation for SSLNextProtocolCallback in
+ // third_party/nss/ssl/ssl.h for the meanings of the arguments.
+ // |arg| contains a pointer to the current SSLClientSocketNSS::Core.
+ static SECStatus NextProtoCallback(void* arg,
+ PRFileDesc* fd,
+ const unsigned char* protos,
+ unsigned int protos_len,
+ unsigned char* proto_out,
+ unsigned int* proto_out_len,
+ unsigned int proto_max_len);
+
+ // Handles an NSS error generated while handshaking or performing IO.
+ // Returns a network error code mapped from the original NSS error.
+ int HandleNSSError(PRErrorCode error, bool handshake_error);
+
+ int DoHandshakeLoop(int last_io_result);
+ int DoReadLoop(int result);
+ int DoWriteLoop(int result);
+
+ int DoHandshake();
+ int DoGetDBCertComplete(int result);
+
+ int DoPayloadRead();
+ int DoPayloadWrite();
+
+ bool DoTransportIO();
+ int BufferRecv();
+ int BufferSend();
+
+ void OnRecvComplete(int result);
+ void OnSendComplete(int result);
+
+ void DoConnectCallback(int result);
+ void DoReadCallback(int result);
+ void DoWriteCallback(int result);
+
+ // Domain bound cert client auth handler.
+ // Returns the value the ClientAuthHandler function should return.
+ SECStatus DomainBoundClientAuthHandler(
+ const SECItem* cert_types,
+ CERTCertificate** result_certificate,
+ SECKEYPrivateKey** result_private_key);
+
+ // ImportDBCertAndKey is a helper function for turning a DER-encoded cert and
+ // key into a CERTCertificate and SECKEYPrivateKey. Returns OK upon success
+ // and an error code otherwise.
+ // Requires |domain_bound_private_key_| and |domain_bound_cert_| to have been
+ // set by a call to ServerBoundCertService->GetDomainBoundCert. The caller
+ // takes ownership of the |*cert| and |*key|.
+ int ImportDBCertAndKey(CERTCertificate** cert, SECKEYPrivateKey** key);
+
+ // Updates the NSS and platform specific certificates.
+ void UpdateServerCert();
+ // Updates the nss_handshake_state_ with the negotiated security parameters.
+ void UpdateConnectionStatus();
+ // Record histograms for DBC support during full handshakes - resumed
+ // handshakes are ignored.
+ void RecordDomainBoundCertSupport() const;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Methods that are ONLY called on the network task runner:
+ ////////////////////////////////////////////////////////////////////////////
+ int DoBufferRecv(IOBuffer* buffer, int len);
+ int DoBufferSend(IOBuffer* buffer, int len);
+ int DoGetDomainBoundCert(const std::string& origin,
+ const std::vector<uint8>& requested_cert_types);
+
+ void OnGetDomainBoundCertComplete(int result);
+ void OnHandshakeStateUpdated(const HandshakeState& state);
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Methods that are called on both the network task runner and the NSS
+ // task runner.
+ ////////////////////////////////////////////////////////////////////////////
+ void OnHandshakeIOComplete(int result);
+ void BufferRecvComplete(IOBuffer* buffer, int result);
+ void BufferSendComplete(int result);
+
+ // PostOrRunCallback is a helper function to ensure that |callback| is
+ // invoked on the network task runner, but only if Detach() has not yet
+ // been called.
+ void PostOrRunCallback(const tracked_objects::Location& location,
+ const base::Closure& callback);
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Members that are ONLY accessed on the network task runner:
+ ////////////////////////////////////////////////////////////////////////////
+
+ // True if the owning SSLClientSocketNSS has called Detach(). No further
+ // callbacks will be invoked nor access to members owned by the network
+ // task runner.
+ bool detached_;
+
+ // The underlying transport to use for network IO.
+ ClientSocketHandle* transport_;
+ base::WeakPtrFactory<BoundNetLog> weak_net_log_factory_;
+
+ // The current handshake state. Mirrors |nss_handshake_state_|.
+ HandshakeState network_handshake_state_;
+
+ ServerBoundCertService* server_bound_cert_service_;
+ ServerBoundCertService::RequestHandle domain_bound_cert_request_handle_;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Members that are ONLY accessed on the NSS task runner:
+ ////////////////////////////////////////////////////////////////////////////
+ HostPortPair host_and_port_;
+ SSLConfig ssl_config_;
+
+ // NSS SSL socket.
+ PRFileDesc* nss_fd_;
+
+ // Buffers for the network end of the SSL state machine
+ memio_Private* nss_bufs_;
+
+ // The certificate chain, in DER form, that is expected to be received from
+ // the server.
+ std::vector<std::string> predicted_certs_;
+
+ State next_handshake_state_;
+
+ // True if domain bound certs were negotiated.
+ bool domain_bound_cert_xtn_negotiated_;
+ // True if the handshake state machine was interrupted for client auth.
+ bool client_auth_cert_needed_;
+ // True if NSS has called HandshakeCallback.
+ bool handshake_callback_called_;
+
+ HandshakeState nss_handshake_state_;
+
+ bool transport_recv_busy_;
+ bool transport_recv_eof_;
+ bool transport_send_busy_;
+
+ // Used by Read function.
+ scoped_refptr<IOBuffer> user_read_buf_;
+ int user_read_buf_len_;
+
+ // Used by Write function.
+ scoped_refptr<IOBuffer> user_write_buf_;
+ int user_write_buf_len_;
+
+ CompletionCallback user_connect_callback_;
+ CompletionCallback user_read_callback_;
+ CompletionCallback user_write_callback_;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Members that are accessed on both the network task runner and the NSS
+ // task runner.
+ ////////////////////////////////////////////////////////////////////////////
+ scoped_refptr<base::SequencedTaskRunner> network_task_runner_;
+ scoped_refptr<base::SingleThreadTaskRunner> nss_task_runner_;
+
+ // Dereferenced only on the network task runner, but bound to tasks destined
+ // for the network task runner from the NSS task runner.
+ base::WeakPtr<BoundNetLog> weak_net_log_;
+
+ // Written on the network task runner by the |server_bound_cert_service_|,
+ // prior to invoking OnHandshakeIOComplete.
+ // Read on the NSS task runner when once OnHandshakeIOComplete is invoked
+ // on the NSS task runner.
+ SSLClientCertType domain_bound_cert_type_;
+ std::string domain_bound_private_key_;
+ std::string domain_bound_cert_;
+
+ DISALLOW_COPY_AND_ASSIGN(Core);
+};
+
+SSLClientSocketNSS::Core::Core(
+ base::SequencedTaskRunner* network_task_runner,
+ base::SingleThreadTaskRunner* nss_task_runner,
+ ClientSocketHandle* transport,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ BoundNetLog* net_log,
+ ServerBoundCertService* server_bound_cert_service)
+ : detached_(false),
+ transport_(transport),
+ weak_net_log_factory_(net_log),
+ server_bound_cert_service_(server_bound_cert_service),
+ domain_bound_cert_request_handle_(NULL),
+ host_and_port_(host_and_port),
+ ssl_config_(ssl_config),
+ nss_fd_(NULL),
+ nss_bufs_(NULL),
+ next_handshake_state_(STATE_NONE),
+ domain_bound_cert_xtn_negotiated_(false),
+ client_auth_cert_needed_(false),
+ handshake_callback_called_(false),
+ transport_recv_busy_(false),
+ transport_recv_eof_(false),
+ transport_send_busy_(false),
+ user_read_buf_len_(0),
+ user_write_buf_len_(0),
+ network_task_runner_(network_task_runner),
+ nss_task_runner_(nss_task_runner),
+ weak_net_log_(weak_net_log_factory_.GetWeakPtr()),
+ domain_bound_cert_type_(CLIENT_CERT_INVALID_TYPE) {
+}
+SSLClientSocketNSS::Core::~Core() {
// TODO(wtc): Send SSL close_notify alert.
if (nss_fd_ != NULL) {
PR_Close(nss_fd_);
nss_fd_ = NULL;
}
+}
- // Reset object state.
- user_connect_callback_.Reset();
- user_read_callback_.Reset();
- user_write_callback_.Reset();
- transport_send_busy_ = false;
- transport_recv_busy_ = false;
- transport_recv_eof_ = false;
- user_read_buf_ = NULL;
- user_read_buf_len_ = 0;
- user_write_buf_ = NULL;
- user_write_buf_len_ = 0;
- server_cert_ = NULL;
- if (server_cert_nss_) {
- CERT_DestroyCertificate(server_cert_nss_);
- server_cert_nss_ = NULL;
- }
- local_server_cert_verify_result_.Reset();
- server_cert_verify_result_ = NULL;
- ssl_connection_status_ = 0;
- completed_handshake_ = false;
- start_cert_verification_time_ = base::TimeTicks();
- predicted_cert_chain_correct_ = false;
- nss_bufs_ = NULL;
- client_certs_.clear();
- client_auth_cert_needed_ = false;
- domain_bound_cert_xtn_negotiated_ = false;
+bool SSLClientSocketNSS::Core::Init(PRFileDesc* socket,
+ memio_Private* buffers) {
+ DCHECK(OnNetworkTaskRunner());
+ DCHECK(!nss_fd_);
+ DCHECK(!nss_bufs_);
- LeaveFunction("");
-}
+ nss_fd_ = socket;
+ nss_bufs_ = buffers;
-bool SSLClientSocketNSS::IsConnected() const {
- // Ideally, we should also check if we have received the close_notify alert
- // message from the server, and return false in that case. We're not doing
- // that, so this function may return a false positive. Since the upper
- // layer (HttpNetworkTransaction) needs to handle a persistent connection
- // closed by the server when we send a request anyway, a false positive in
- // exchange for simpler code is a good trade-off.
- EnterFunction("");
- bool ret = completed_handshake_ && transport_->socket()->IsConnected();
- LeaveFunction("");
- return ret;
-}
+ SECStatus rv = SECSuccess;
-bool SSLClientSocketNSS::IsConnectedAndIdle() const {
- // Unlike IsConnected, this method doesn't return a false positive.
- //
- // Strictly speaking, we should check if we have received the close_notify
- // alert message from the server, and return false in that case. Although
- // the close_notify alert message means EOF in the SSL layer, it is just
- // bytes to the transport layer below, so
- // transport_->socket()->IsConnectedAndIdle() returns the desired false
- // when we receive close_notify.
- EnterFunction("");
- bool ret = completed_handshake_ && transport_->socket()->IsConnectedAndIdle();
- LeaveFunction("");
- return ret;
-}
+ if (!ssl_config_.next_protos.empty()) {
+ rv = SSL_SetNextProtoCallback(
+ nss_fd_, SSLClientSocketNSS::Core::NextProtoCallback, this);
+ if (rv != SECSuccess)
+ LogFailedNSSFunction(*weak_net_log_, "SSL_SetNextProtoCallback", "");
+ }
-int SSLClientSocketNSS::GetPeerAddress(AddressList* address) const {
- return transport_->socket()->GetPeerAddress(address);
-}
+ rv = SSL_AuthCertificateHook(
+ nss_fd_, SSLClientSocketNSS::Core::OwnAuthCertHandler, this);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(*weak_net_log_, "SSL_AuthCertificateHook", "");
+ return false;
+ }
-int SSLClientSocketNSS::GetLocalAddress(IPEndPoint* address) const {
- return transport_->socket()->GetLocalAddress(address);
-}
+#if defined(NSS_PLATFORM_CLIENT_AUTH)
+ rv = SSL_GetPlatformClientAuthDataHook(
+ nss_fd_, SSLClientSocketNSS::Core::PlatformClientAuthHandler,
+ this);
+#else
+ rv = SSL_GetClientAuthDataHook(
+ nss_fd_, SSLClientSocketNSS::Core::ClientAuthHandler, this);
+#endif
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(*weak_net_log_, "SSL_GetClientAuthDataHook", "");
+ return false;
+ }
-const BoundNetLog& SSLClientSocketNSS::NetLog() const {
- return net_log_;
+ rv = SSL_HandshakeCallback(
+ nss_fd_, SSLClientSocketNSS::Core::HandshakeCallback, this);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(*weak_net_log_, "SSL_HandshakeCallback", "");
+ return false;
+ }
+
+ return true;
}
-void SSLClientSocketNSS::SetSubresourceSpeculation() {
- if (transport_.get() && transport_->socket()) {
- transport_->socket()->SetSubresourceSpeculation();
- } else {
- NOTREACHED();
+void SSLClientSocketNSS::Core::SetPredictedCertificates(
+ const std::vector<std::string>& predicted_certs) {
+ if (predicted_certs.empty())
+ return;
+
+ if (!OnNSSTaskRunner()) {
+ DCHECK(!detached_);
+ nss_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&Core::SetPredictedCertificates, this, predicted_certs));
+ return;
}
-}
-void SSLClientSocketNSS::SetOmniboxSpeculation() {
- if (transport_.get() && transport_->socket()) {
- transport_->socket()->SetOmniboxSpeculation();
- } else {
- NOTREACHED();
+ DCHECK(nss_fd_);
+
+ predicted_certs_ = predicted_certs;
+
+ scoped_array<CERTCertificate*> certs(
+ new CERTCertificate*[predicted_certs.size()]);
+
+ for (size_t i = 0; i < predicted_certs.size(); i++) {
+ SECItem derCert;
+ derCert.data = const_cast<uint8*>(reinterpret_cast<const uint8*>(
+ predicted_certs[i].data()));
+ derCert.len = predicted_certs[i].size();
+ certs[i] = CERT_NewTempCertificate(
+ CERT_GetDefaultCertDB(), &derCert, NULL /* no nickname given */,
+ PR_FALSE /* not permanent */, PR_TRUE /* copy DER data */);
+ if (!certs[i]) {
+ DestroyCertificates(&certs[0], i);
+ NOTREACHED();
+ return;
+ }
}
-}
-bool SSLClientSocketNSS::WasEverUsed() const {
- if (transport_.get() && transport_->socket()) {
- return transport_->socket()->WasEverUsed();
+ SECStatus rv;
+#ifdef SSL_ENABLE_CACHED_INFO
+ rv = SSL_SetPredictedPeerCertificates(nss_fd_, certs.get(),
+ predicted_certs.size());
+ DCHECK_EQ(SECSuccess, rv);
+#else
+ rv = SECFailure; // Not implemented.
+#endif
+ DestroyCertificates(&certs[0], predicted_certs.size());
+
+ if (rv != SECSuccess) {
+ LOG(WARNING) << "SetPredictedCertificates failed: "
+ << host_and_port_.ToString();
}
- NOTREACHED();
- return false;
}
-bool SSLClientSocketNSS::UsingTCPFastOpen() const {
- if (transport_.get() && transport_->socket()) {
- return transport_->socket()->UsingTCPFastOpen();
+int SSLClientSocketNSS::Core::Connect(const CompletionCallback& callback) {
+ if (!OnNSSTaskRunner()) {
+ DCHECK(!detached_);
+ bool posted = nss_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(IgnoreResult(&Core::Connect), this, callback));
+ return posted ? ERR_IO_PENDING : ERR_ABORTED;
}
- NOTREACHED();
- return false;
-}
-int64 SSLClientSocketNSS::NumBytesRead() const {
- if (transport_.get() && transport_->socket()) {
- return transport_->socket()->NumBytesRead();
+ DCHECK(OnNSSTaskRunner());
+ DCHECK_EQ(STATE_NONE, next_handshake_state_);
+ DCHECK(user_read_callback_.is_null());
+ DCHECK(user_write_callback_.is_null());
+ DCHECK(user_connect_callback_.is_null());
+ DCHECK(!user_read_buf_);
+ DCHECK(!user_write_buf_);
+
+ next_handshake_state_ = STATE_HANDSHAKE;
+ int rv = DoHandshakeLoop(OK);
+ if (rv == ERR_IO_PENDING) {
+ user_connect_callback_ = callback;
+ } else if (rv > OK) {
+ rv = OK;
}
- NOTREACHED();
- return -1;
+ if (rv != ERR_IO_PENDING && !OnNetworkTaskRunner()) {
+ PostOrRunCallback(FROM_HERE, base::Bind(callback, rv));
+ return ERR_IO_PENDING;
+ }
+
+ return rv;
}
-base::TimeDelta SSLClientSocketNSS::GetConnectTimeMicros() const {
- if (transport_.get() && transport_->socket()) {
- return transport_->socket()->GetConnectTimeMicros();
+void SSLClientSocketNSS::Core::Detach() {
+ DCHECK(OnNetworkTaskRunner());
+
+ detached_ = true;
+ transport_ = NULL;
+ weak_net_log_factory_.InvalidateWeakPtrs();
+
+ network_handshake_state_.Reset();
+
+ if (domain_bound_cert_request_handle_ != NULL) {
+ server_bound_cert_service_->CancelRequest(
+ domain_bound_cert_request_handle_);
+ domain_bound_cert_request_handle_ = NULL;
}
- NOTREACHED();
- return base::TimeDelta::FromMicroseconds(-1);
}
-int SSLClientSocketNSS::Read(IOBuffer* buf, int buf_len,
- const CompletionCallback& callback) {
- EnterFunction(buf_len);
- DCHECK(completed_handshake_);
- DCHECK(next_handshake_state_ == STATE_NONE);
+int SSLClientSocketNSS::Core::Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ if (!OnNSSTaskRunner()) {
+ DCHECK(OnNetworkTaskRunner());
+ DCHECK(!detached_);
+ DCHECK(transport_);
+
+ bool posted = nss_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(IgnoreResult(&Core::Read), this, make_scoped_refptr(buf),
+ buf_len, callback));
+ return posted ? ERR_IO_PENDING : ERR_ABORTED;
+ }
+
+ DCHECK(OnNSSTaskRunner());
+ DCHECK(handshake_callback_called_);
+ DCHECK_EQ(STATE_NONE, next_handshake_state_);
DCHECK(user_read_callback_.is_null());
DCHECK(user_connect_callback_.is_null());
DCHECK(!user_read_buf_);
@@ -759,22 +1153,39 @@ int SSLClientSocketNSS::Read(IOBuffer* buf, int buf_len,
user_read_buf_len_ = buf_len;
int rv = DoReadLoop(OK);
-
if (rv == ERR_IO_PENDING) {
user_read_callback_ = callback;
} else {
user_read_buf_ = NULL;
user_read_buf_len_ = 0;
+
+ if (!OnNetworkTaskRunner()) {
+ PostOrRunCallback(FROM_HERE, base::Bind(callback, rv));
+ return ERR_IO_PENDING;
+ }
}
- LeaveFunction(rv);
+
return rv;
}
-int SSLClientSocketNSS::Write(IOBuffer* buf, int buf_len,
- const CompletionCallback& callback) {
- EnterFunction(buf_len);
- DCHECK(completed_handshake_);
- DCHECK(next_handshake_state_ == STATE_NONE);
+int SSLClientSocketNSS::Core::Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ if (!OnNSSTaskRunner()) {
+ DCHECK(OnNetworkTaskRunner());
+ DCHECK(!detached_);
+ DCHECK(transport_);
+
+ bool posted = nss_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(IgnoreResult(&Core::Write), this, make_scoped_refptr(buf),
+ buf_len, callback));
+ int rv = posted ? ERR_IO_PENDING : ERR_ABORTED;
+ return rv;
+ }
+
+ DCHECK(OnNSSTaskRunner());
+ DCHECK(handshake_callback_called_);
+ DCHECK_EQ(STATE_NONE, next_handshake_state_);
DCHECK(user_write_callback_.is_null());
DCHECK(user_connect_callback_.is_null());
DCHECK(!user_write_buf_);
@@ -784,423 +1195,1109 @@ int SSLClientSocketNSS::Write(IOBuffer* buf, int buf_len,
user_write_buf_len_ = buf_len;
int rv = DoWriteLoop(OK);
-
if (rv == ERR_IO_PENDING) {
user_write_callback_ = callback;
} else {
user_write_buf_ = NULL;
user_write_buf_len_ = 0;
+
+ if (!OnNetworkTaskRunner()) {
+ PostOrRunCallback(FROM_HERE, base::Bind(callback, rv));
+ return ERR_IO_PENDING;
+ }
}
- LeaveFunction(rv);
+
return rv;
}
-bool SSLClientSocketNSS::SetReceiveBufferSize(int32 size) {
- return transport_->socket()->SetReceiveBufferSize(size);
+bool SSLClientSocketNSS::Core::OnNSSTaskRunner() const {
+ return nss_task_runner_->RunsTasksOnCurrentThread();
}
-bool SSLClientSocketNSS::SetSendBufferSize(int32 size) {
- return transport_->socket()->SetSendBufferSize(size);
+bool SSLClientSocketNSS::Core::OnNetworkTaskRunner() const {
+ return network_task_runner_->RunsTasksOnCurrentThread();
}
-int SSLClientSocketNSS::Init() {
- EnterFunction("");
- // Initialize the NSS SSL library in a threadsafe way. This also
- // initializes the NSS base library.
- EnsureNSSSSLInit();
- if (!NSS_IsInitialized())
- return ERR_UNEXPECTED;
-#if !defined(OS_MACOSX) && !defined(OS_WIN)
- if (ssl_config_.cert_io_enabled) {
- // We must call EnsureNSSHttpIOInit() here, on the IO thread, to get the IO
- // loop by MessageLoopForIO::current().
- // X509Certificate::Verify() runs on a worker thread of CertVerifier.
- EnsureNSSHttpIOInit();
+// static
+SECStatus SSLClientSocketNSS::Core::OwnAuthCertHandler(
+ void* arg,
+ PRFileDesc* socket,
+ PRBool checksig,
+ PRBool is_server) {
+#ifdef SSL_ENABLE_FALSE_START
+ Core* core = reinterpret_cast<Core*>(arg);
+ if (!core->handshake_callback_called_) {
+ // Only need to turn off False Start in the initial handshake. Also, it is
+ // unsafe to call SSL_OptionSet in a renegotiation because the "first
+ // handshake" lock isn't already held, which will result in an assertion
+ // failure in the ssl_Get1stHandshakeLock call in SSL_OptionSet.
+ PRBool npn;
+ SECStatus rv = SSL_HandshakeNegotiatedExtension(socket,
+ ssl_next_proto_nego_xtn,
+ &npn);
+ if (rv != SECSuccess || !npn) {
+ // If the server doesn't support NPN, then we don't do False Start with
+ // it.
+ SSL_OptionSet(socket, SSL_ENABLE_FALSE_START, PR_FALSE);
+ }
}
#endif
- LeaveFunction("");
- return OK;
+ // Tell NSS to not verify the certificate.
+ return SECSuccess;
}
-int SSLClientSocketNSS::InitializeSSLOptions() {
- // Transport connected, now hook it up to nss
- // TODO(port): specify rx and tx buffer sizes separately
- nss_fd_ = memio_CreateIOLayer(kRecvBufferSize);
- if (nss_fd_ == NULL) {
- return ERR_OUT_OF_MEMORY; // TODO(port): map NSPR error code.
- }
-
- // Grab pointer to buffers
- nss_bufs_ = memio_GetSecret(nss_fd_);
+#if defined(NSS_PLATFORM_CLIENT_AUTH)
+// static
+SECStatus SSLClientSocketNSS::Core::PlatformClientAuthHandler(
+ void* arg,
+ PRFileDesc* socket,
+ CERTDistNames* ca_names,
+ CERTCertList** result_certs,
+ void** result_private_key,
+ CERTCertificate** result_nss_certificate,
+ SECKEYPrivateKey** result_nss_private_key) {
+ Core* core = reinterpret_cast<Core*>(arg);
+ DCHECK(core->OnNSSTaskRunner());
- /* Create SSL state machine */
- /* Push SSL onto our fake I/O socket */
- nss_fd_ = SSL_ImportFD(NULL, nss_fd_);
- if (nss_fd_ == NULL) {
- LogFailedNSSFunction(net_log_, "SSL_ImportFD", "");
- return ERR_OUT_OF_MEMORY; // TODO(port): map NSPR/NSS error code.
- }
- // TODO(port): set more ssl options! Check errors!
+ core->PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, core->weak_net_log_,
+ NetLog::TYPE_SSL_CLIENT_CERT_REQUESTED,
+ scoped_refptr<NetLog::EventParameters>()));
- int rv;
+ const SECItem* cert_types = SSL_GetRequestedClientCertificateTypes(socket);
- rv = SSL_OptionSet(nss_fd_, SSL_SECURITY, PR_TRUE);
- if (rv != SECSuccess) {
- LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_SECURITY");
- return ERR_UNEXPECTED;
+ // Check if a domain-bound certificate is requested.
+ if (DomainBoundCertNegotiated(socket)) {
+ return core->DomainBoundClientAuthHandler(cert_types,
+ result_nss_certificate,
+ result_nss_private_key);
}
- rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SSL2, PR_FALSE);
- if (rv != SECSuccess) {
- LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_SSL2");
- return ERR_UNEXPECTED;
- }
+ core->client_auth_cert_needed_ = !core->ssl_config_.send_client_cert;
+#if defined(OS_WIN)
+ if (core->ssl_config_.send_client_cert) {
+ if (core->ssl_config_.client_cert) {
+ PCCERT_CONTEXT cert_context =
+ core->ssl_config_.client_cert->os_cert_handle();
- // Don't do V2 compatible hellos because they don't support TLS extensions.
- rv = SSL_OptionSet(nss_fd_, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
- if (rv != SECSuccess) {
- LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_V2_COMPATIBLE_HELLO");
- return ERR_UNEXPECTED;
- }
+ HCRYPTPROV_OR_NCRYPT_KEY_HANDLE crypt_prov = 0;
+ DWORD key_spec = 0;
+ BOOL must_free = FALSE;
+ BOOL acquired_key = CryptAcquireCertificatePrivateKey(
+ cert_context, CRYPT_ACQUIRE_CACHE_FLAG, NULL,
+ &crypt_prov, &key_spec, &must_free);
- SSLVersionRange version_range;
- version_range.min = ssl_config_.version_min;
- version_range.max = ssl_config_.version_max;
- rv = SSL_VersionRangeSet(nss_fd_, &version_range);
- if (rv != SECSuccess) {
- LogFailedNSSFunction(net_log_, "SSL_VersionRangeSet", "");
- return ERR_NO_SSL_VERSIONS_ENABLED;
- }
+ if (acquired_key) {
+ // Since we passed CRYPT_ACQUIRE_CACHE_FLAG, |must_free| must be false
+ // according to the MSDN documentation.
+ CHECK_EQ(must_free, FALSE);
+ DCHECK_NE(key_spec, CERT_NCRYPT_KEY_SPEC);
- for (std::vector<uint16>::const_iterator it =
- ssl_config_.disabled_cipher_suites.begin();
- it != ssl_config_.disabled_cipher_suites.end(); ++it) {
- // This will fail if the specified cipher is not implemented by NSS, but
- // the failure is harmless.
- SSL_CipherPrefSet(nss_fd_, *it, PR_FALSE);
- }
+ SECItem der_cert;
+ der_cert.type = siDERCertBuffer;
+ der_cert.data = cert_context->pbCertEncoded;
+ der_cert.len = cert_context->cbCertEncoded;
-#ifdef SSL_ENABLE_SESSION_TICKETS
- // Support RFC 5077
- rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SESSION_TICKETS, PR_TRUE);
- if (rv != SECSuccess) {
- LogFailedNSSFunction(
- net_log_, "SSL_OptionSet", "SSL_ENABLE_SESSION_TICKETS");
- }
+ // TODO(rsleevi): Error checking for NSS allocation errors.
+ CERTCertDBHandle* db_handle = CERT_GetDefaultCertDB();
+ CERTCertificate* user_cert = CERT_NewTempCertificate(
+ db_handle, &der_cert, NULL, PR_FALSE, PR_TRUE);
+ if (!user_cert) {
+ // Importing the certificate can fail for reasons including a serial
+ // number collision. See crbug.com/97355.
+ core->PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, core->weak_net_log_,
+ NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
+ make_scoped_refptr(
+ new NetLogIntegerParameter("cert_count", 0))));
+ return SECFailure;
+ }
+ CERTCertList* cert_chain = CERT_NewCertList();
+ CERT_AddCertToListTail(cert_chain, user_cert);
+
+ // Add the intermediates.
+ X509Certificate::OSCertHandles intermediates =
+ core->ssl_config_.client_cert->GetIntermediateCertificates();
+ for (X509Certificate::OSCertHandles::const_iterator it =
+ intermediates.begin(); it != intermediates.end(); ++it) {
+ der_cert.data = (*it)->pbCertEncoded;
+ der_cert.len = (*it)->cbCertEncoded;
+
+ CERTCertificate* intermediate = CERT_NewTempCertificate(
+ db_handle, &der_cert, NULL, PR_FALSE, PR_TRUE);
+ if (!intermediate) {
+ CERT_DestroyCertList(cert_chain);
+ core->PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, core->weak_net_log_,
+ NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
+ make_scoped_refptr(
+ new NetLogIntegerParameter("cert_count", 0))));
+ return SECFailure;
+ }
+ CERT_AddCertToListTail(cert_chain, intermediate);
+ }
+ PCERT_KEY_CONTEXT key_context = reinterpret_cast<PCERT_KEY_CONTEXT>(
+ PORT_ZAlloc(sizeof(CERT_KEY_CONTEXT)));
+ key_context->cbSize = sizeof(*key_context);
+ // NSS will free this context when no longer in use, but the
+ // |must_free| result from CryptAcquireCertificatePrivateKey was false
+ // so we increment the refcount to negate NSS's future decrement.
+ CryptContextAddRef(crypt_prov, NULL, 0);
+ key_context->hCryptProv = crypt_prov;
+ key_context->dwKeySpec = key_spec;
+ *result_private_key = key_context;
+ *result_certs = cert_chain;
+
+ int cert_count = 1 + intermediates.size();
+ core->PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, core->weak_net_log_,
+ NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
+ make_scoped_refptr(
+ new NetLogIntegerParameter("cert_count",
+ cert_count))));
+ return SECSuccess;
+ }
+ LOG(WARNING) << "Client cert found without private key";
+ }
+
+ // Send no client certificate.
+ core->PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, core->weak_net_log_,
+ NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
+ make_scoped_refptr(
+ new NetLogIntegerParameter("cert_count", 0))));
+ return SECFailure;
+ }
+
+ core->nss_handshake_state_.client_certs.clear();
+
+ std::vector<CERT_NAME_BLOB> issuer_list(ca_names->nnames);
+ for (int i = 0; i < ca_names->nnames; ++i) {
+ issuer_list[i].cbData = ca_names->names[i].len;
+ issuer_list[i].pbData = ca_names->names[i].data;
+ }
+
+ // Client certificates of the user are in the "MY" system certificate store.
+ HCERTSTORE my_cert_store = CertOpenSystemStore(NULL, L"MY");
+ if (!my_cert_store) {
+ PLOG(ERROR) << "Could not open the \"MY\" system certificate store";
+
+ core->PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, core->weak_net_log_,
+ NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
+ make_scoped_refptr(
+ new NetLogIntegerParameter("cert_count", 0))));
+ return SECFailure;
+ }
+
+ // Enumerate the client certificates.
+ CERT_CHAIN_FIND_BY_ISSUER_PARA find_by_issuer_para;
+ memset(&find_by_issuer_para, 0, sizeof(find_by_issuer_para));
+ find_by_issuer_para.cbSize = sizeof(find_by_issuer_para);
+ find_by_issuer_para.pszUsageIdentifier = szOID_PKIX_KP_CLIENT_AUTH;
+ find_by_issuer_para.cIssuer = ca_names->nnames;
+ find_by_issuer_para.rgIssuer = ca_names->nnames ? &issuer_list[0] : NULL;
+ find_by_issuer_para.pfnFindCallback = ClientCertFindCallback;
+
+ PCCERT_CHAIN_CONTEXT chain_context = NULL;
+ DWORD find_flags = CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_FLAG |
+ CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_URL_FLAG;
+
+ for (;;) {
+ // Find a certificate chain.
+ chain_context = CertFindChainInStore(my_cert_store,
+ X509_ASN_ENCODING,
+ find_flags,
+ CERT_CHAIN_FIND_BY_ISSUER,
+ &find_by_issuer_para,
+ chain_context);
+ if (!chain_context) {
+ DWORD err = GetLastError();
+ if (err != CRYPT_E_NOT_FOUND)
+ DLOG(ERROR) << "CertFindChainInStore failed: " << err;
+ break;
+ }
+
+ // Get the leaf certificate.
+ PCCERT_CONTEXT cert_context =
+ chain_context->rgpChain[0]->rgpElement[0]->pCertContext;
+ // Create a copy the handle, so that we can close the "MY" certificate store
+ // before returning from this function.
+ PCCERT_CONTEXT cert_context2;
+ BOOL ok = CertAddCertificateContextToStore(NULL, cert_context,
+ CERT_STORE_ADD_USE_EXISTING,
+ &cert_context2);
+ if (!ok) {
+ NOTREACHED();
+ continue;
+ }
+
+ // Copy the rest of the chain. Copying the chain stops gracefully if an
+ // error is encountered, with the partial chain being used as the
+ // intermediates, as opposed to failing to consider the client certificate
+ // at all.
+ net::X509Certificate::OSCertHandles intermediates;
+ for (DWORD i = 1; i < chain_context->rgpChain[0]->cElement; i++) {
+ PCCERT_CONTEXT intermediate_copy;
+ ok = CertAddCertificateContextToStore(
+ NULL, chain_context->rgpChain[0]->rgpElement[i]->pCertContext,
+ CERT_STORE_ADD_USE_EXISTING, &intermediate_copy);
+ if (!ok) {
+ NOTREACHED();
+ break;
+ }
+ intermediates.push_back(intermediate_copy);
+ }
+
+ scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromHandle(
+ cert_context2, intermediates);
+ core->nss_handshake_state_.client_certs.push_back(cert);
+
+ X509Certificate::FreeOSCertHandle(cert_context2);
+ for (net::X509Certificate::OSCertHandles::iterator it =
+ intermediates.begin(); it != intermediates.end(); ++it) {
+ net::X509Certificate::FreeOSCertHandle(*it);
+ }
+ }
+
+ BOOL ok = CertCloseStore(my_cert_store, CERT_CLOSE_STORE_CHECK_FLAG);
+ DCHECK(ok);
+
+ // Update the network task runner's view of the handshake state now that
+ // client certs have been detected.
+ core->PostOrRunCallback(
+ FROM_HERE, base::Bind(&Core::OnHandshakeStateUpdated, core,
+ core->nss_handshake_state_));
+
+ // Tell NSS to suspend the client authentication. We will then abort the
+ // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED.
+ return SECWouldBlock;
+#elif defined(OS_MACOSX)
+ if (core->ssl_config_.send_client_cert) {
+ if (core->ssl_config_.client_cert) {
+ OSStatus os_error = noErr;
+ SecIdentityRef identity = NULL;
+ SecKeyRef private_key = NULL;
+ CFArrayRef chain =
+ core->ssl_config_.client_cert->CreateClientCertificateChain();
+ if (chain) {
+ identity = reinterpret_cast<SecIdentityRef>(
+ const_cast<void*>(CFArrayGetValueAtIndex(chain, 0)));
+ }
+ if (identity)
+ os_error = SecIdentityCopyPrivateKey(identity, &private_key);
+
+ if (chain && identity && os_error == noErr) {
+ // TODO(rsleevi): Error checking for NSS allocation errors.
+ *result_certs = CERT_NewCertList();
+ *result_private_key = private_key;
+
+ for (CFIndex i = 0; i < CFArrayGetCount(chain); ++i) {
+ CSSM_DATA cert_data;
+ SecCertificateRef cert_ref;
+ if (i == 0) {
+ cert_ref = core->ssl_config_.client_cert->os_cert_handle();
+ } else {
+ cert_ref = reinterpret_cast<SecCertificateRef>(
+ const_cast<void*>(CFArrayGetValueAtIndex(chain, i)));
+ }
+ os_error = SecCertificateGetData(cert_ref, &cert_data);
+ if (os_error != noErr)
+ break;
+
+ SECItem der_cert;
+ der_cert.type = siDERCertBuffer;
+ der_cert.data = cert_data.Data;
+ der_cert.len = cert_data.Length;
+ CERTCertificate* nss_cert = CERT_NewTempCertificate(
+ CERT_GetDefaultCertDB(), &der_cert, NULL, PR_FALSE, PR_TRUE);
+ if (!nss_cert) {
+ // In the event of an NSS error we make up an OS error and reuse
+ // the error handling, below.
+ os_error = errSecCreateChainFailed;
+ break;
+ }
+ CERT_AddCertToListTail(*result_certs, nss_cert);
+ }
+ }
+ if (os_error == noErr) {
+ int cert_count = 0;
+ if (chain) {
+ cert_count = CFArrayGetCount(chain);
+ CFRelease(chain);
+ }
+ core->PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, core->weak_net_log_,
+ NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
+ make_scoped_refptr(
+ new NetLogIntegerParameter("cert_count",
+ cert_count))));
+ return SECSuccess;
+ }
+ OSSTATUS_LOG(WARNING, os_error)
+ << "Client cert found, but could not be used";
+ if (*result_certs) {
+ CERT_DestroyCertList(*result_certs);
+ *result_certs = NULL;
+ }
+ if (*result_private_key)
+ *result_private_key = NULL;
+ if (private_key)
+ CFRelease(private_key);
+ if (chain)
+ CFRelease(chain);
+ }
+
+ // Send no client certificate.
+ core->PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, core->weak_net_log_,
+ NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
+ make_scoped_refptr(
+ new NetLogIntegerParameter("cert_count", 0))));
+ return SECFailure;
+ }
+
+ core->nss_handshake_state_.client_certs.clear();
+
+ // First, get the cert issuer names allowed by the server.
+ std::vector<CertPrincipal> valid_issuers;
+ int n = ca_names->nnames;
+ for (int i = 0; i < n; i++) {
+ // Parse each name into a CertPrincipal object.
+ CertPrincipal p;
+ if (p.ParseDistinguishedName(ca_names->names[i].data,
+ ca_names->names[i].len)) {
+ valid_issuers.push_back(p);
+ }
+ }
+
+ // Now get the available client certs whose issuers are allowed by the server.
+ X509Certificate::GetSSLClientCertificates(
+ core->host_and_port_.host(), valid_issuers,
+ &core->nss_handshake_state_.client_certs);
+
+ // Update the network task runner's view of the handshake state now that
+ // client certs have been detected.
+ core->PostOrRunCallback(
+ FROM_HERE, base::Bind(&Core::OnHandshakeStateUpdated, core,
+ core->nss_handshake_state_));
+
+ // Tell NSS to suspend the client authentication. We will then abort the
+ // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED.
+ return SECWouldBlock;
#else
- #error "You need to install NSS-3.12 or later to build chromium"
+ return SECFailure;
#endif
+}
+
+#else // NSS_PLATFORM_CLIENT_AUTH
+
+// static
+// Based on Mozilla's NSS_GetClientAuthData.
+SECStatus SSLClientSocketNSS::Core::ClientAuthHandler(
+ void* arg,
+ PRFileDesc* socket,
+ CERTDistNames* ca_names,
+ CERTCertificate** result_certificate,
+ SECKEYPrivateKey** result_private_key) {
+ Core* core = reinterpret_cast<Core*>(arg);
+ DCHECK(core->OnNSSTaskRunner());
+
+ core->PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, core->weak_net_log_,
+ NetLog::TYPE_SSL_CLIENT_CERT_REQUESTED,
+ scoped_refptr<NetLog::EventParameters>()));
+
+ const SECItem* cert_types = SSL_GetRequestedClientCertificateTypes(socket);
+
+ // Check if a domain-bound certificate is requested.
+ if (DomainBoundCertNegotiated(socket)) {
+ return core->DomainBoundClientAuthHandler(
+ cert_types, result_certificate, result_private_key);
+ }
+
+ // Regular client certificate requested.
+ core->client_auth_cert_needed_ = !core->ssl_config_.send_client_cert;
+ void* wincx = SSL_RevealPinArg(socket);
+
+ // Second pass: a client certificate should have been selected.
+ if (core->ssl_config_.send_client_cert) {
+ if (core->ssl_config_.client_cert) {
+ CERTCertificate* cert = CERT_DupCertificate(
+ core->ssl_config_.client_cert->os_cert_handle());
+ SECKEYPrivateKey* privkey = PK11_FindKeyByAnyCert(cert, wincx);
+ if (privkey) {
+ // TODO(jsorianopastor): We should wait for server certificate
+ // verification before sending our credentials. See
+ // http://crbug.com/13934.
+ *result_certificate = cert;
+ *result_private_key = privkey;
+ // A cert_count of -1 means the number of certificates is unknown.
+ // NSS will construct the certificate chain.
+ core->PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, core->weak_net_log_,
+ NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
+ make_scoped_refptr(
+ new NetLogIntegerParameter("cert_count", -1))));
+
+ return SECSuccess;
+ }
+ LOG(WARNING) << "Client cert found without private key";
+ }
+ // Send no client certificate.
+ core->PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, core->weak_net_log_,
+ NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
+ make_scoped_refptr(
+ new NetLogIntegerParameter("cert_count", 0))));
+ return SECFailure;
+ }
+
+ core->nss_handshake_state_.client_certs.clear();
+
+ // Iterate over all client certificates.
+ CERTCertList* client_certs = CERT_FindUserCertsByUsage(
+ CERT_GetDefaultCertDB(), certUsageSSLClient,
+ PR_FALSE, PR_FALSE, wincx);
+ if (client_certs) {
+ for (CERTCertListNode* node = CERT_LIST_HEAD(client_certs);
+ !CERT_LIST_END(node, client_certs);
+ node = CERT_LIST_NEXT(node)) {
+ // Only offer unexpired certificates.
+ if (CERT_CheckCertValidTimes(node->cert, PR_Now(), PR_TRUE) !=
+ secCertTimeValid) {
+ continue;
+ }
+ // Filter by issuer.
+ //
+ // TODO(davidben): This does a binary comparison of the DER-encoded
+ // issuers. We should match according to RFC 5280 sec. 7.1. We should find
+ // an appropriate NSS function or add one if needbe.
+ if (ca_names->nnames &&
+ NSS_CmpCertChainWCANames(node->cert, ca_names) != SECSuccess) {
+ continue;
+ }
+
+ X509Certificate* x509_cert = X509Certificate::CreateFromHandle(
+ node->cert, net::X509Certificate::OSCertHandles());
+ core->nss_handshake_state_.client_certs.push_back(x509_cert);
+ }
+ CERT_DestroyCertList(client_certs);
+ }
+
+ // Update the network task runner's view of the handshake state now that
+ // client certs have been detected.
+ core->PostOrRunCallback(
+ FROM_HERE, base::Bind(&Core::OnHandshakeStateUpdated, core,
+ core->nss_handshake_state_));
+
+ // Tell NSS to suspend the client authentication. We will then abort the
+ // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED.
+ return SECWouldBlock;
+}
+#endif // NSS_PLATFORM_CLIENT_AUTH
+
+// static
+void SSLClientSocketNSS::Core::HandshakeCallback(
+ PRFileDesc* socket,
+ void* arg) {
+ Core* core = reinterpret_cast<Core*>(arg);
+ DCHECK(core->OnNSSTaskRunner());
-#ifdef SSL_ENABLE_DEFLATE
- // Some web servers have been found to break if TLS is used *or* if DEFLATE
- // is advertised. Thus, if TLS is disabled (probably because we are doing
- // SSLv3 fallback), we disable DEFLATE also.
- // See http://crbug.com/31628
- rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_DEFLATE,
- ssl_config_.version_max >= SSL_PROTOCOL_VERSION_TLS1);
- if (rv != SECSuccess)
- LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_DEFLATE");
-#endif
+ core->handshake_callback_called_ = true;
-#ifdef SSL_ENABLE_FALSE_START
- rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_FALSE_START,
- ssl_config_.false_start_enabled);
- if (rv != SECSuccess)
- LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_FALSE_START");
-#endif
+ HandshakeState* nss_state = &core->nss_handshake_state_;
-#ifdef SSL_ENABLE_RENEGOTIATION
- // We allow servers to request renegotiation. Since we're a client,
- // prohibiting this is rather a waste of time. Only servers are in a
- // position to prevent renegotiation attacks.
- // http://extendedsubset.com/?p=8
+ PRBool last_handshake_resumed;
+ SECStatus rv = SSL_HandshakeResumedSession(socket, &last_handshake_resumed);
+ if (rv == SECSuccess && last_handshake_resumed) {
+ nss_state->resumed_handshake = true;
+ } else {
+ nss_state->resumed_handshake = false;
+ }
+
+ core->RecordDomainBoundCertSupport();
+ core->UpdateServerCert();
+ core->UpdateConnectionStatus();
+
+ // We need to see if the predicted certificate chain (from
+ // SetPredictedCertificates) matches the actual certificate chain.
+ nss_state->predicted_cert_chain_correct = false;
+ if (!core->predicted_certs_.empty()) {
+ PeerCertificateChain& certs = nss_state->server_cert_chain;
+ nss_state->predicted_cert_chain_correct =
+ certs.size() == core->predicted_certs_.size();
+
+ if (nss_state->predicted_cert_chain_correct) {
+ for (unsigned i = 0; i < certs.size(); i++) {
+ if (certs[i]->derCert.len != core->predicted_certs_[i].size() ||
+ memcmp(certs[i]->derCert.data, core->predicted_certs_[i].data(),
+ certs[i]->derCert.len) != 0) {
+ nss_state->predicted_cert_chain_correct = false;
+ break;
+ }
+ }
+ }
+ }
- rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_RENEGOTIATION,
- SSL_RENEGOTIATE_TRANSITIONAL);
- if (rv != SECSuccess) {
- LogFailedNSSFunction(
- net_log_, "SSL_OptionSet", "SSL_ENABLE_RENEGOTIATION");
+ // Update the network task runners view of the handshake state whenever
+ // a handshake has completed.
+ core->PostOrRunCallback(
+ FROM_HERE, base::Bind(&Core::OnHandshakeStateUpdated, core,
+ *nss_state));
+}
+
+// static
+SECStatus SSLClientSocketNSS::Core::NextProtoCallback(
+ void* arg,
+ PRFileDesc* nss_fd,
+ const unsigned char* protos,
+ unsigned int protos_len,
+ unsigned char* proto_out,
+ unsigned int* proto_out_len,
+ unsigned int proto_max_len) {
+ Core* core = reinterpret_cast<Core*>(arg);
+ DCHECK(core->OnNSSTaskRunner());
+
+ HandshakeState* nss_state = &core->nss_handshake_state_;
+
+ // For each protocol in server preference, see if we support it.
+ for (unsigned int i = 0; i < protos_len; ) {
+ const size_t len = protos[i];
+ for (std::vector<std::string>::const_iterator
+ j = core->ssl_config_.next_protos.begin();
+ j != core->ssl_config_.next_protos.end(); j++) {
+ // Having very long elements in the |next_protos| vector isn't a disaster
+ // because they'll never be selected, but it does indicate an error
+ // somewhere.
+ DCHECK_LT(j->size(), 256u);
+
+ if (j->size() == len &&
+ memcmp(&protos[i + 1], j->data(), len) == 0) {
+ nss_state->next_proto_status = kNextProtoNegotiated;
+ nss_state->next_proto = *j;
+ break;
+ }
+ }
+
+ if (nss_state->next_proto_status == kNextProtoNegotiated)
+ break;
+
+ // NSS ensures that the data in |protos| is well formed, so this will not
+ // cause a jump past the end of the buffer.
+ i += len + 1;
}
-#endif // SSL_ENABLE_RENEGOTIATION
- if (!ssl_config_.next_protos.empty()) {
- rv = SSL_SetNextProtoCallback(
- nss_fd_, SSLClientSocketNSS::NextProtoCallback, this);
- if (rv != SECSuccess)
- LogFailedNSSFunction(net_log_, "SSL_SetNextProtoCallback", "");
+ nss_state->server_protos.assign(
+ reinterpret_cast<const char*>(protos), protos_len);
+
+ // If we didn't find a protocol, we select the first one from our list.
+ if (nss_state->next_proto_status != kNextProtoNegotiated) {
+ nss_state->next_proto_status = kNextProtoNoOverlap;
+ nss_state->next_proto = core->ssl_config_.next_protos[0];
}
-#ifdef SSL_CBC_RANDOM_IV
- rv = SSL_OptionSet(nss_fd_, SSL_CBC_RANDOM_IV,
- ssl_config_.false_start_enabled);
- if (rv != SECSuccess)
- LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_CBC_RANDOM_IV");
+ if (nss_state->next_proto.size() > proto_max_len) {
+ PORT_SetError(SEC_ERROR_OUTPUT_LEN);
+ return SECFailure;
+ }
+ memcpy(proto_out, nss_state->next_proto.data(),
+ nss_state->next_proto.size());
+ *proto_out_len = nss_state->next_proto.size();
+
+ // Update the network task runner's view of the handshake state now that
+ // NPN negotiation has occurred.
+ core->PostOrRunCallback(
+ FROM_HERE, base::Bind(&Core::OnHandshakeStateUpdated, core,
+ *nss_state));
+
+ return SECSuccess;
+}
+
+int SSLClientSocketNSS::Core::HandleNSSError(PRErrorCode nss_error,
+ bool handshake_error) {
+ DCHECK(OnNSSTaskRunner());
+
+ int net_error = handshake_error ? MapNSSHandshakeError(nss_error) :
+ MapNSSError(nss_error);
+
+#if defined(OS_WIN)
+ // On Windows, a handle to the HCRYPTPROV is cached in the X509Certificate
+ // os_cert_handle() as an optimization. However, if the certificate
+ // private key is stored on a smart card, and the smart card is removed,
+ // the cached HCRYPTPROV will not be able to obtain the HCRYPTKEY again,
+ // preventing client certificate authentication. Because the
+ // X509Certificate may outlive the individual SSLClientSocketNSS, due to
+ // caching in X509Certificate, this failure ends up preventing client
+ // certificate authentication with the same certificate for all future
+ // attempts, even after the smart card has been re-inserted. By setting
+ // the CERT_KEY_PROV_HANDLE_PROP_ID to NULL, the cached HCRYPTPROV will
+ // typically be freed. This allows a new HCRYPTPROV to be obtained from
+ // the certificate on the next attempt, which should succeed if the smart
+ // card has been re-inserted, or will typically prompt the user to
+ // re-insert the smart card if not.
+ if ((net_error == ERR_SSL_CLIENT_AUTH_CERT_NO_PRIVATE_KEY ||
+ net_error == ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED) &&
+ ssl_config_.send_client_cert && ssl_config_.client_cert) {
+ CertSetCertificateContextProperty(
+ ssl_config_.client_cert->os_cert_handle(),
+ CERT_KEY_PROV_HANDLE_PROP_ID, 0, NULL);
+ }
#endif
-#ifdef SSL_ENABLE_OCSP_STAPLING
- if (IsOCSPStaplingSupported()) {
- rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_OCSP_STAPLING, PR_TRUE);
- if (rv != SECSuccess) {
- LogFailedNSSFunction(net_log_, "SSL_OptionSet",
- "SSL_ENABLE_OCSP_STAPLING");
+ return net_error;
+}
+
+int SSLClientSocketNSS::Core::DoHandshakeLoop(int last_io_result) {
+ DCHECK(OnNSSTaskRunner());
+
+ int rv = last_io_result;
+ do {
+ // Default to STATE_NONE for next state.
+ State state = next_handshake_state_;
+ GotoState(STATE_NONE);
+
+ switch (state) {
+ case STATE_HANDSHAKE:
+ rv = DoHandshake();
+ break;
+ case STATE_GET_DOMAIN_BOUND_CERT_COMPLETE:
+ rv = DoGetDBCertComplete(rv);
+ break;
+ case STATE_NONE:
+ default:
+ rv = ERR_UNEXPECTED;
+ LOG(DFATAL) << "unexpected state " << state;
+ break;
+ }
+
+ // Do the actual network I/O
+ bool network_moved = DoTransportIO();
+ if (network_moved && next_handshake_state_ == STATE_HANDSHAKE) {
+ // In general we exit the loop if rv is ERR_IO_PENDING. In this
+ // special case we keep looping even if rv is ERR_IO_PENDING because
+ // the transport IO may allow DoHandshake to make progress.
+ DCHECK(rv == OK || rv == ERR_IO_PENDING);
+ rv = OK; // This causes us to stay in the loop.
}
+ } while (rv != ERR_IO_PENDING && next_handshake_state_ != STATE_NONE);
+ return rv;
+}
+
+int SSLClientSocketNSS::Core::DoReadLoop(int result) {
+ DCHECK(OnNSSTaskRunner());
+ DCHECK(handshake_callback_called_);
+ DCHECK_EQ(STATE_NONE, next_handshake_state_);
+
+ if (result < 0)
+ return result;
+
+ if (!nss_bufs_) {
+ LOG(DFATAL) << "!nss_bufs_";
+ int rv = ERR_UNEXPECTED;
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, weak_net_log_,
+ NetLog::TYPE_SSL_READ_ERROR,
+ make_scoped_refptr(new SSLErrorParams(rv, 0))));
+ return rv;
}
-#endif
-#ifdef SSL_ENABLE_CACHED_INFO
- rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_CACHED_INFO,
- ssl_config_.cached_info_enabled);
- if (rv != SECSuccess)
- LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_CACHED_INFO");
-#endif
+ bool network_moved;
+ int rv;
+ do {
+ rv = DoPayloadRead();
+ network_moved = DoTransportIO();
+ } while (rv == ERR_IO_PENDING && network_moved);
+
+ return rv;
+}
+
+int SSLClientSocketNSS::Core::DoWriteLoop(int result) {
+ DCHECK(OnNSSTaskRunner());
+ DCHECK(handshake_callback_called_);
+ DCHECK_EQ(STATE_NONE, next_handshake_state_);
+
+ if (result < 0)
+ return result;
+
+ if (!nss_bufs_) {
+ LOG(DFATAL) << "!nss_bufs_";
+ int rv = ERR_UNEXPECTED;
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, weak_net_log_,
+ NetLog::TYPE_SSL_READ_ERROR,
+ make_scoped_refptr(new SSLErrorParams(rv, 0))));
+ return rv;
+ }
+
+ bool network_moved;
+ int rv;
+ do {
+ rv = DoPayloadWrite();
+ network_moved = DoTransportIO();
+ } while (rv == ERR_IO_PENDING && network_moved);
+
+ LeaveFunction(rv);
+ return rv;
+}
+
+int SSLClientSocketNSS::Core::DoHandshake() {
+ DCHECK(OnNSSTaskRunner());
+
+ int net_error = net::OK;
+ SECStatus rv = SSL_ForceHandshake(nss_fd_);
+
+ // TODO(rkn): Handle the case in which server-bound cert generation takes
+ // too long and the server has closed the connection. Report some new error
+ // code so that the higher level code will attempt to delete the socket and
+ // redo the handshake.
+ if (client_auth_cert_needed_) {
+ if (domain_bound_cert_xtn_negotiated_) {
+ GotoState(STATE_GET_DOMAIN_BOUND_CERT_COMPLETE);
+ net_error = ERR_IO_PENDING;
+ } else {
+ net_error = ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, weak_net_log_,
+ NetLog::TYPE_SSL_HANDSHAKE_ERROR,
+ make_scoped_refptr(new SSLErrorParams(net_error, 0))));
+
+ // If the handshake already succeeded (because the server requests but
+ // doesn't require a client cert), we need to invalidate the SSL session
+ // so that we won't try to resume the non-client-authenticated session in
+ // the next handshake. This will cause the server to ask for a client
+ // cert again.
+ if (rv == SECSuccess && SSL_InvalidateSession(nss_fd_) != SECSuccess)
+ LOG(WARNING) << "Couldn't invalidate SSL session: " << PR_GetError();
+ }
+ } else if (rv == SECSuccess) {
+ if (!handshake_callback_called_) {
+ // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=562434 -
+ // SSL_ForceHandshake returned SECSuccess prematurely.
+ rv = SECFailure;
+ net_error = ERR_SSL_PROTOCOL_ERROR;
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, weak_net_log_,
+ NetLog::TYPE_SSL_HANDSHAKE_ERROR,
+ make_scoped_refptr(
+ new SSLErrorParams(net_error, 0))));
+ } else {
+ #if defined(SSL_ENABLE_OCSP_STAPLING)
+ // TODO(agl): figure out how to plumb an OCSP response into the Mac
+ // system library and update IsOCSPStaplingSupported for Mac.
+ if (!nss_handshake_state_.predicted_cert_chain_correct &&
+ IsOCSPStaplingSupported()) {
+ unsigned int len = 0;
+ SSL_GetStapledOCSPResponse(nss_fd_, NULL, &len);
+ if (len) {
+ const unsigned int orig_len = len;
+ scoped_array<uint8> ocsp_response(new uint8[orig_len]);
+ SSL_GetStapledOCSPResponse(nss_fd_, ocsp_response.get(), &len);
+ DCHECK_EQ(orig_len, len);
+
+ #if defined(OS_WIN)
+ if (nss_handshake_state_.server_cert) {
+ CRYPT_DATA_BLOB ocsp_response_blob;
+ ocsp_response_blob.cbData = len;
+ ocsp_response_blob.pbData = ocsp_response.get();
+ BOOL ok = CertSetCertificateContextProperty(
+ nss_handshake_state_.server_cert->os_cert_handle(),
+ CERT_OCSP_RESPONSE_PROP_ID,
+ CERT_SET_PROPERTY_IGNORE_PERSIST_ERROR_FLAG,
+ &ocsp_response_blob);
+ if (!ok) {
+ VLOG(1) << "Failed to set OCSP response property: "
+ << GetLastError();
+ }
+ }
+ #elif defined(USE_NSS)
+ CacheOCSPResponseFromSideChannelFunction cache_ocsp_response =
+ GetCacheOCSPResponseFromSideChannelFunction();
+ SECItem ocsp_response_item;
+ ocsp_response_item.type = siBuffer;
+ ocsp_response_item.data = ocsp_response.get();
+ ocsp_response_item.len = len;
+
+ cache_ocsp_response(
+ CERT_GetDefaultCertDB(),
+ nss_handshake_state_.server_cert_chain[0], PR_Now(),
+ &ocsp_response_item, NULL);
+ #endif
+ }
+ }
+ #endif
+ }
+ // Done!
+ } else {
+ PRErrorCode prerr = PR_GetError();
+ net_error = HandleNSSError(prerr, true);
+
+ // If not done, stay in this state
+ if (net_error == ERR_IO_PENDING) {
+ GotoState(STATE_HANDSHAKE);
+ } else {
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, weak_net_log_,
+ NetLog::TYPE_SSL_HANDSHAKE_ERROR,
+ make_scoped_refptr(
+ new SSLErrorParams(net_error, prerr))));
+ }
+ }
+
+ return net_error;
+}
-#ifdef SSL_ENABLE_OB_CERTS
- rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_OB_CERTS,
- ssl_config_.domain_bound_certs_enabled);
- if (rv != SECSuccess)
- LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_OB_CERTS");
-#endif
+int SSLClientSocketNSS::Core::DoGetDBCertComplete(int result) {
+ SECStatus rv;
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::EndEventWithNetErrorCode, weak_net_log_,
+ NetLog::TYPE_SSL_GET_DOMAIN_BOUND_CERT, result));
-#ifdef SSL_ENCRYPT_CLIENT_CERTS
- // For now, enable the encrypted client certificates extension only if
- // server-bound certificates are enabled.
- rv = SSL_OptionSet(nss_fd_, SSL_ENCRYPT_CLIENT_CERTS,
- ssl_config_.domain_bound_certs_enabled);
- if (rv != SECSuccess)
- LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENCRYPT_CLIENT_CERTS");
-#endif
+ client_auth_cert_needed_ = false;
+ domain_bound_cert_type_ = CLIENT_CERT_INVALID_TYPE;
- rv = SSL_OptionSet(nss_fd_, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
- if (rv != SECSuccess) {
- LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_HANDSHAKE_AS_CLIENT");
- return ERR_UNEXPECTED;
- }
+ if (result != OK) {
+ // Failed to get a DBC. Proceed without.
+ rv = SSL_RestartHandshakeAfterCertReq(nss_fd_, NULL, NULL, NULL);
+ if (rv != SECSuccess)
+ return MapNSSError(PORT_GetError());
- rv = SSL_AuthCertificateHook(nss_fd_, OwnAuthCertHandler, this);
- if (rv != SECSuccess) {
- LogFailedNSSFunction(net_log_, "SSL_AuthCertificateHook", "");
- return ERR_UNEXPECTED;
+ GotoState(STATE_HANDSHAKE);
+ return OK;
}
-#if defined(NSS_PLATFORM_CLIENT_AUTH)
- rv = SSL_GetPlatformClientAuthDataHook(nss_fd_, PlatformClientAuthHandler,
- this);
-#else
- rv = SSL_GetClientAuthDataHook(nss_fd_, ClientAuthHandler, this);
-#endif
- if (rv != SECSuccess) {
- LogFailedNSSFunction(net_log_, "SSL_GetClientAuthDataHook", "");
- return ERR_UNEXPECTED;
- }
+ CERTCertificate* cert;
+ SECKEYPrivateKey* key;
+ int error = ImportDBCertAndKey(&cert, &key);
+ if (error != OK)
+ return error;
- rv = SSL_HandshakeCallback(nss_fd_, HandshakeCallback, this);
- if (rv != SECSuccess) {
- LogFailedNSSFunction(net_log_, "SSL_HandshakeCallback", "");
- return ERR_UNEXPECTED;
- }
+ CERTCertificateList* cert_chain =
+ CERT_CertChainFromCert(cert, certUsageSSLClient, PR_FALSE);
- // Tell SSL the hostname we're trying to connect to.
- SSL_SetURL(nss_fd_, host_and_port_.host().c_str());
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, weak_net_log_,
+ NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
+ make_scoped_refptr(
+ new NetLogIntegerParameter("cert_count",
+ cert_chain->len))));
- // Tell SSL we're a client; needed if not letting NSPR do socket I/O
- SSL_ResetHandshake(nss_fd_, PR_FALSE);
+ rv = SSL_RestartHandshakeAfterCertReq(nss_fd_, cert, key, cert_chain);
+ if (rv != SECSuccess)
+ return MapNSSError(PORT_GetError());
+ GotoState(STATE_HANDSHAKE);
return OK;
}
-int SSLClientSocketNSS::InitializeSSLPeerName() {
- // Tell NSS who we're connected to
- AddressList peer_address;
- int err = transport_->socket()->GetPeerAddress(&peer_address);
- if (err != OK)
- return err;
+int SSLClientSocketNSS::Core::DoPayloadRead() {
+ DCHECK(OnNSSTaskRunner());
+ DCHECK(user_read_buf_);
+ DCHECK_GT(user_read_buf_len_, 0);
- SockaddrStorage storage;
- if (!peer_address.front().ToSockAddr(storage.addr, &storage.addr_len))
- return ERR_UNEXPECTED;
+ int rv = PR_Read(nss_fd_, user_read_buf_->data(), user_read_buf_len_);
+ if (client_auth_cert_needed_) {
+ // We don't need to invalidate the non-client-authenticated SSL session
+ // because the server will renegotiate anyway.
+ rv = ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, weak_net_log_,
+ NetLog::TYPE_SSL_READ_ERROR,
+ make_scoped_refptr(new SSLErrorParams(rv, 0))));
+ return rv;
+ }
+ if (rv >= 0) {
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&LogByteTransferEvent, weak_net_log_,
+ NetLog::TYPE_SSL_SOCKET_BYTES_RECEIVED, rv,
+ scoped_refptr<IOBuffer>(user_read_buf_)));
+ return rv;
+ }
+ PRErrorCode prerr = PR_GetError();
+ if (prerr == PR_WOULD_BLOCK_ERROR)
+ return ERR_IO_PENDING;
- PRNetAddr peername;
- memset(&peername, 0, sizeof(peername));
- DCHECK_LE(static_cast<size_t>(storage.addr_len), sizeof(peername));
- size_t len = std::min(static_cast<size_t>(storage.addr_len),
- sizeof(peername));
- memcpy(&peername, storage.addr, len);
+ rv = HandleNSSError(prerr, false);
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, weak_net_log_,
+ NetLog::TYPE_SSL_READ_ERROR,
+ make_scoped_refptr(new SSLErrorParams(rv, prerr))));
+ return rv;
+}
- // Adjust the address family field for BSD, whose sockaddr
- // structure has a one-byte length and one-byte address family
- // field at the beginning. PRNetAddr has a two-byte address
- // family field at the beginning.
- peername.raw.family = storage.addr->sa_family;
+int SSLClientSocketNSS::Core::DoPayloadWrite() {
+ DCHECK(OnNSSTaskRunner());
- memio_SetPeerName(nss_fd_, &peername);
+ DCHECK(user_write_buf_);
- // Set the peer ID for session reuse. This is necessary when we create an
- // SSL tunnel through a proxy -- GetPeerName returns the proxy's address
- // rather than the destination server's address in that case.
- std::string peer_id = host_and_port_.ToString();
- // If the ssl_session_cache_shard_ is non-empty, we append it to the peer id.
- // This will cause session cache misses between sockets with different values
- // of ssl_session_cache_shard_ and this is used to partition the session cache
- // for incognito mode.
- if (!ssl_session_cache_shard_.empty()) {
- peer_id += "/" + ssl_session_cache_shard_;
+ int rv = PR_Write(nss_fd_, user_write_buf_->data(), user_write_buf_len_);
+ if (rv >= 0) {
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&LogByteTransferEvent, weak_net_log_,
+ NetLog::TYPE_SSL_SOCKET_BYTES_SENT, rv,
+ scoped_refptr<IOBuffer>(user_write_buf_)));
+ return rv;
}
- SECStatus rv = SSL_SetSockPeerID(nss_fd_, const_cast<char*>(peer_id.c_str()));
- if (rv != SECSuccess)
- LogFailedNSSFunction(net_log_, "SSL_SetSockPeerID", peer_id.c_str());
+ PRErrorCode prerr = PR_GetError();
+ if (prerr == PR_WOULD_BLOCK_ERROR)
+ return ERR_IO_PENDING;
- return OK;
+ rv = HandleNSSError(prerr, false);
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, weak_net_log_,
+ NetLog::TYPE_SSL_WRITE_ERROR,
+ make_scoped_refptr(new SSLErrorParams(rv, prerr))));
+ return rv;
}
+// Do as much network I/O as possible between the buffer and the
+// transport socket. Return true if some I/O performed, false
+// otherwise (error or ERR_IO_PENDING).
+bool SSLClientSocketNSS::Core::DoTransportIO() {
+ DCHECK(OnNSSTaskRunner());
-// Sets server_cert_ and server_cert_nss_ if not yet set.
-void SSLClientSocketNSS::UpdateServerCert() {
- // We set the server_cert_ from HandshakeCallback().
- if (server_cert_ == NULL) {
- server_cert_nss_ = SSL_PeerCertificate(nss_fd_);
- if (server_cert_nss_) {
- PeerCertificateChain certs(nss_fd_);
- // This call may fail when SSL is used inside sandbox. In that
- // case CreateFromDERCertChain() returns NULL.
- server_cert_ = X509Certificate::CreateFromDERCertChain(
- certs.AsStringPieceVector());
- if (server_cert_) {
- net_log_.AddEvent(
- NetLog::TYPE_SSL_CERTIFICATES_RECEIVED,
- make_scoped_refptr(new X509CertificateNetLogParam(server_cert_)));
- }
- }
+ bool network_moved = false;
+ if (nss_bufs_ != NULL) {
+ int rv;
+ // Read and write as much data as we can. The loop is neccessary
+ // because Write() may return synchronously.
+ do {
+ rv = BufferSend();
+ if (rv > 0)
+ network_moved = true;
+ } while (rv > 0);
+ if (!transport_recv_eof_ && BufferRecv() >= 0)
+ network_moved = true;
}
+ return network_moved;
}
-// Sets ssl_connection_status_.
-void SSLClientSocketNSS::UpdateConnectionStatus() {
- SSLChannelInfo channel_info;
- SECStatus ok = SSL_GetChannelInfo(nss_fd_,
- &channel_info, sizeof(channel_info));
- if (ok == SECSuccess &&
- channel_info.length == sizeof(channel_info) &&
- channel_info.cipherSuite) {
- ssl_connection_status_ |=
- (static_cast<int>(channel_info.cipherSuite) &
- SSL_CONNECTION_CIPHERSUITE_MASK) <<
- SSL_CONNECTION_CIPHERSUITE_SHIFT;
+int SSLClientSocketNSS::Core::BufferRecv() {
+ DCHECK(OnNSSTaskRunner());
- ssl_connection_status_ |=
- (static_cast<int>(channel_info.compressionMethod) &
- SSL_CONNECTION_COMPRESSION_MASK) <<
- SSL_CONNECTION_COMPRESSION_SHIFT;
+ if (transport_recv_busy_)
+ return ERR_IO_PENDING;
- // NSS 3.12.x doesn't have version macros for TLS 1.1 and 1.2 (because NSS
- // doesn't support them yet), so we use 0x0302 and 0x0303 directly.
- int version = SSL_CONNECTION_VERSION_UNKNOWN;
- if (channel_info.protocolVersion < SSL_LIBRARY_VERSION_3_0) {
- // All versions less than SSL_LIBRARY_VERSION_3_0 are treated as SSL
- // version 2.
- version = SSL_CONNECTION_VERSION_SSL2;
- } else if (channel_info.protocolVersion == SSL_LIBRARY_VERSION_3_0) {
- version = SSL_CONNECTION_VERSION_SSL3;
- } else if (channel_info.protocolVersion == SSL_LIBRARY_VERSION_3_1_TLS) {
- version = SSL_CONNECTION_VERSION_TLS1;
- } else if (channel_info.protocolVersion == 0x0302) {
- version = SSL_CONNECTION_VERSION_TLS1_1;
- } else if (channel_info.protocolVersion == 0x0303) {
- version = SSL_CONNECTION_VERSION_TLS1_2;
+ char* buf;
+ int nb = memio_GetReadParams(nss_bufs_, &buf);
+ int rv;
+ if (!nb) {
+ // buffer too full to read into, so no I/O possible at moment
+ rv = ERR_IO_PENDING;
+ } else {
+ scoped_refptr<IOBuffer> read_buffer(new IOBuffer(nb));
+ if (OnNetworkTaskRunner()) {
+ rv = DoBufferRecv(read_buffer, nb);
+ } else {
+ bool posted = network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(IgnoreResult(&Core::DoBufferRecv), this, read_buffer,
+ nb));
+ rv = posted ? ERR_IO_PENDING : ERR_ABORTED;
}
- ssl_connection_status_ |=
- (version & SSL_CONNECTION_VERSION_MASK) <<
- SSL_CONNECTION_VERSION_SHIFT;
- }
- // SSL_HandshakeNegotiatedExtension was added in NSS 3.12.6.
- // Since SSL_MAX_EXTENSIONS was added at the same time, we can test
- // SSL_MAX_EXTENSIONS for the presence of SSL_HandshakeNegotiatedExtension.
-#if defined(SSL_MAX_EXTENSIONS)
- PRBool peer_supports_renego_ext;
- ok = SSL_HandshakeNegotiatedExtension(nss_fd_, ssl_renegotiation_info_xtn,
- &peer_supports_renego_ext);
- if (ok == SECSuccess) {
- if (!peer_supports_renego_ext) {
- ssl_connection_status_ |= SSL_CONNECTION_NO_RENEGOTIATION_EXTENSION;
- // Log an informational message if the server does not support secure
- // renegotiation (RFC 5746).
- VLOG(1) << "The server " << host_and_port_.ToString()
- << " does not support the TLS renegotiation_info extension.";
+ if (rv == ERR_IO_PENDING) {
+ transport_recv_busy_ = true;
+ } else {
+ if (rv > 0) {
+ memcpy(buf, read_buffer->data(), rv);
+ } else if (rv == 0) {
+ transport_recv_eof_ = true;
+ }
+ memio_PutReadResult(nss_bufs_, MapErrorToNSS(rv));
}
- UMA_HISTOGRAM_ENUMERATION("Net.RenegotiationExtensionSupported",
- peer_supports_renego_ext, 2);
}
-#endif
-
- if (ssl_config_.version_fallback)
- ssl_connection_status_ |= SSL_CONNECTION_VERSION_FALLBACK;
+ return rv;
}
-void SSLClientSocketNSS::DoReadCallback(int rv) {
- EnterFunction(rv);
- DCHECK(rv != ERR_IO_PENDING);
- DCHECK(!user_read_callback_.is_null());
+// Return 0 for EOF,
+// > 0 for bytes transferred immediately,
+// < 0 for error (or the non-error ERR_IO_PENDING).
+int SSLClientSocketNSS::Core::BufferSend() {
+ DCHECK(OnNSSTaskRunner());
- // Since Run may result in Read being called, clear |user_read_callback_|
- // up front.
- CompletionCallback c = user_read_callback_;
- user_read_callback_.Reset();
- user_read_buf_ = NULL;
- user_read_buf_len_ = 0;
- c.Run(rv);
- LeaveFunction("");
-}
+ if (transport_send_busy_)
+ return ERR_IO_PENDING;
-void SSLClientSocketNSS::DoWriteCallback(int rv) {
- EnterFunction(rv);
- DCHECK(rv != ERR_IO_PENDING);
- DCHECK(!user_write_callback_.is_null());
+ const char* buf1;
+ const char* buf2;
+ unsigned int len1, len2;
+ memio_GetWriteParams(nss_bufs_, &buf1, &len1, &buf2, &len2);
+ const unsigned int len = len1 + len2;
- // Since Run may result in Write being called, clear |user_write_callback_|
- // up front.
- CompletionCallback c = user_write_callback_;
- user_write_callback_.Reset();
- user_write_buf_ = NULL;
- user_write_buf_len_ = 0;
- c.Run(rv);
- LeaveFunction("");
-}
+ int rv = 0;
+ if (len) {
+ scoped_refptr<IOBuffer> send_buffer(new IOBuffer(len));
+ memcpy(send_buffer->data(), buf1, len1);
+ memcpy(send_buffer->data() + len1, buf2, len2);
-// As part of Connect(), the SSLClientSocketNSS object performs an SSL
-// handshake. This requires network IO, which in turn calls
-// BufferRecvComplete() with a non-zero byte count. This byte count eventually
-// winds its way through the state machine and ends up being passed to the
-// callback. For Read() and Write(), that's what we want. But for Connect(),
-// the caller expects OK (i.e. 0) for success.
-//
-void SSLClientSocketNSS::DoConnectCallback(int rv) {
- EnterFunction(rv);
- DCHECK_NE(rv, ERR_IO_PENDING);
- DCHECK(!user_connect_callback_.is_null());
+ if (OnNetworkTaskRunner()) {
+ rv = DoBufferSend(send_buffer, len);
+ } else {
+ bool posted = network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(IgnoreResult(&Core::DoBufferSend), this, send_buffer,
+ len));
+ rv = posted ? ERR_IO_PENDING : ERR_ABORTED;
+ }
- CompletionCallback c = user_connect_callback_;
- user_connect_callback_.Reset();
- c.Run(rv > OK ? OK : rv);
- LeaveFunction("");
+ if (rv == ERR_IO_PENDING) {
+ transport_send_busy_ = true;
+ } else {
+ memio_PutWriteResult(nss_bufs_, MapErrorToNSS(rv));
+ }
+ }
+
+ return rv;
}
-void SSLClientSocketNSS::OnHandshakeIOComplete(int result) {
- EnterFunction(result);
- int rv = DoHandshakeLoop(result);
- if (rv != ERR_IO_PENDING) {
- net_log_.EndEventWithNetErrorCode(net::NetLog::TYPE_SSL_CONNECT, rv);
- DoConnectCallback(rv);
+void SSLClientSocketNSS::Core::OnRecvComplete(int result) {
+ DCHECK(OnNSSTaskRunner());
+
+ if (next_handshake_state_ == STATE_HANDSHAKE) {
+ OnHandshakeIOComplete(result);
+ return;
}
- LeaveFunction("");
+
+ // Network layer received some data, check if client requested to read
+ // decrypted data.
+ if (!user_read_buf_)
+ return;
+
+ int rv = DoReadLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoReadCallback(rv);
}
-void SSLClientSocketNSS::OnSendComplete(int result) {
- EnterFunction(result);
+void SSLClientSocketNSS::Core::OnSendComplete(int result) {
+ DCHECK(OnNSSTaskRunner());
+
if (next_handshake_state_ == STATE_HANDSHAKE) {
- // In handshake phase.
OnHandshakeIOComplete(result);
- LeaveFunction("");
return;
}
@@ -1224,316 +2321,111 @@ void SSLClientSocketNSS::OnSendComplete(int result) {
DoReadCallback(rv_read);
if (user_write_buf_ && rv_write != ERR_IO_PENDING)
DoWriteCallback(rv_write);
-
- LeaveFunction("");
-}
-
-void SSLClientSocketNSS::OnRecvComplete(int result) {
- EnterFunction(result);
- if (next_handshake_state_ == STATE_HANDSHAKE) {
- // In handshake phase.
- OnHandshakeIOComplete(result);
- LeaveFunction("");
- return;
- }
-
- // Network layer received some data, check if client requested to read
- // decrypted data.
- if (!user_read_buf_) {
- LeaveFunction("");
- return;
- }
-
- int rv = DoReadLoop(result);
- if (rv != ERR_IO_PENDING)
- DoReadCallback(rv);
- LeaveFunction("");
}
-int SSLClientSocketNSS::DoHandshakeLoop(int last_io_result) {
- EnterFunction(last_io_result);
- int rv = last_io_result;
- do {
- // Default to STATE_NONE for next state.
- // (This is a quirk carried over from the windows
- // implementation. It makes reading the logs a bit harder.)
- // State handlers can and often do call GotoState just
- // to stay in the current state.
- State state = next_handshake_state_;
- GotoState(STATE_NONE);
- switch (state) {
- case STATE_LOAD_SSL_HOST_INFO:
- DCHECK(rv == OK || rv == ERR_IO_PENDING);
- rv = DoLoadSSLHostInfo();
- break;
- case STATE_HANDSHAKE:
- rv = DoHandshake();
- break;
- case STATE_GET_DOMAIN_BOUND_CERT_COMPLETE:
- rv = DoGetDBCertComplete(rv);
- break;
- case STATE_VERIFY_DNSSEC:
- rv = DoVerifyDNSSEC(rv);
- break;
- case STATE_VERIFY_CERT:
- DCHECK(rv == OK);
- rv = DoVerifyCert(rv);
- break;
- case STATE_VERIFY_CERT_COMPLETE:
- rv = DoVerifyCertComplete(rv);
- break;
- case STATE_NONE:
- default:
- rv = ERR_UNEXPECTED;
- LOG(DFATAL) << "unexpected state " << state;
- break;
- }
+// As part of Connect(), the SSLClientSocketNSS object performs an SSL
+// handshake. This requires network IO, which in turn calls
+// BufferRecvComplete() with a non-zero byte count. This byte count eventually
+// winds its way through the state machine and ends up being passed to the
+// callback. For Read() and Write(), that's what we want. But for Connect(),
+// the caller expects OK (i.e. 0) for success.
+void SSLClientSocketNSS::Core::DoConnectCallback(int rv) {
+ DCHECK(OnNSSTaskRunner());
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ DCHECK(!user_connect_callback_.is_null());
- // Do the actual network I/O
- bool network_moved = DoTransportIO();
- if (network_moved && next_handshake_state_ == STATE_HANDSHAKE) {
- // In general we exit the loop if rv is ERR_IO_PENDING. In this
- // special case we keep looping even if rv is ERR_IO_PENDING because
- // the transport IO may allow DoHandshake to make progress.
- DCHECK(rv == OK || rv == ERR_IO_PENDING);
- rv = OK; // This causes us to stay in the loop.
- }
- } while (rv != ERR_IO_PENDING && next_handshake_state_ != STATE_NONE);
- LeaveFunction("");
- return rv;
+ base::Closure c = base::Bind(
+ base::ResetAndReturn(&user_connect_callback_),
+ rv > OK ? OK : rv);
+ PostOrRunCallback(FROM_HERE, c);
}
-int SSLClientSocketNSS::DoReadLoop(int result) {
- EnterFunction("");
- DCHECK(completed_handshake_);
- DCHECK(next_handshake_state_ == STATE_NONE);
-
- if (result < 0)
- return result;
-
- if (!nss_bufs_) {
- LOG(DFATAL) << "!nss_bufs_";
- int rv = ERR_UNEXPECTED;
- net_log_.AddEvent(NetLog::TYPE_SSL_READ_ERROR,
- make_scoped_refptr(new SSLErrorParams(rv, 0)));
- return rv;
- }
-
- bool network_moved;
- int rv;
- do {
- rv = DoPayloadRead();
- network_moved = DoTransportIO();
- } while (rv == ERR_IO_PENDING && network_moved);
+void SSLClientSocketNSS::Core::DoReadCallback(int rv) {
+ DCHECK(OnNSSTaskRunner());
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ DCHECK(!user_read_callback_.is_null());
- LeaveFunction("");
- return rv;
+ user_read_buf_ = NULL;
+ user_read_buf_len_ = 0;
+ base::Closure c = base::Bind(
+ base::ResetAndReturn(&user_read_callback_),
+ rv);
+ PostOrRunCallback(FROM_HERE, c);
}
-int SSLClientSocketNSS::DoWriteLoop(int result) {
- EnterFunction("");
- DCHECK(completed_handshake_);
- DCHECK(next_handshake_state_ == STATE_NONE);
-
- if (result < 0)
- return result;
-
- if (!nss_bufs_) {
- LOG(DFATAL) << "!nss_bufs_";
- int rv = ERR_UNEXPECTED;
- net_log_.AddEvent(NetLog::TYPE_SSL_WRITE_ERROR,
- make_scoped_refptr(new SSLErrorParams(rv, 0)));
- return rv;
- }
-
- bool network_moved;
- int rv;
- do {
- rv = DoPayloadWrite();
- network_moved = DoTransportIO();
- } while (rv == ERR_IO_PENDING && network_moved);
+void SSLClientSocketNSS::Core::DoWriteCallback(int rv) {
+ DCHECK(OnNSSTaskRunner());
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ DCHECK(!user_write_callback_.is_null());
- LeaveFunction("");
- return rv;
+ // Since Run may result in Write being called, clear |user_write_callback_|
+ // up front.
+ user_write_buf_ = NULL;
+ user_write_buf_len_ = 0;
+ base::Closure c = base::Bind(
+ base::ResetAndReturn(&user_write_callback_),
+ rv);
+ PostOrRunCallback(FROM_HERE, c);
}
-bool SSLClientSocketNSS::LoadSSLHostInfo() {
- const SSLHostInfo::State& state(ssl_host_info_->state());
-
- if (state.certs.empty())
- return true;
-
- const std::vector<std::string>& certs_in = state.certs;
- scoped_array<CERTCertificate*> certs(new CERTCertificate*[certs_in.size()]);
-
- for (size_t i = 0; i < certs_in.size(); i++) {
- SECItem derCert;
- derCert.data =
- const_cast<uint8*>(reinterpret_cast<const uint8*>(certs_in[i].data()));
- derCert.len = certs_in[i].size();
- certs[i] = CERT_NewTempCertificate(
- CERT_GetDefaultCertDB(), &derCert, NULL /* no nickname given */,
- PR_FALSE /* not permanent */, PR_TRUE /* copy DER data */);
- if (!certs[i]) {
- DestroyCertificates(&certs[0], i);
- NOTREACHED();
- return false;
- }
- }
-
- SECStatus rv;
-#ifdef SSL_ENABLE_CACHED_INFO
- rv = SSL_SetPredictedPeerCertificates(nss_fd_, certs.get(), certs_in.size());
- DCHECK_EQ(SECSuccess, rv);
-#else
- rv = SECFailure; // Not implemented.
-#endif
- DestroyCertificates(&certs[0], certs_in.size());
-
- return rv == SECSuccess;
-}
+SECStatus SSLClientSocketNSS::Core::DomainBoundClientAuthHandler(
+ const SECItem* cert_types,
+ CERTCertificate** result_certificate,
+ SECKEYPrivateKey** result_private_key) {
+ DCHECK(OnNSSTaskRunner());
-int SSLClientSocketNSS::DoLoadSSLHostInfo() {
- EnterFunction("");
- int rv = ssl_host_info_->WaitForDataReady(
- base::Bind(&SSLClientSocketNSS::OnHandshakeIOComplete,
- base::Unretained(this)));
- GotoState(STATE_HANDSHAKE);
+ domain_bound_cert_xtn_negotiated_ = true;
- if (rv == OK) {
- if (!LoadSSLHostInfo())
- LOG(WARNING) << "LoadSSLHostInfo failed: " << host_and_port_.ToString();
+ // We have negotiated the domain-bound certificate extension.
+ std::string origin = "https://" + host_and_port_.ToString();
+ std::vector<uint8> requested_cert_types(cert_types->data,
+ cert_types->data + cert_types->len);
+ int error = ERR_UNEXPECTED;
+ if (OnNetworkTaskRunner()) {
+ error = DoGetDomainBoundCert(origin, requested_cert_types);
} else {
- DCHECK_EQ(ERR_IO_PENDING, rv);
- GotoState(STATE_LOAD_SSL_HOST_INFO);
+ bool posted = network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(IgnoreResult(&Core::DoGetDomainBoundCert), this, origin,
+ requested_cert_types));
+ error = posted ? ERR_IO_PENDING : ERR_ABORTED;
}
- LeaveFunction("");
- return rv;
-}
-
-int SSLClientSocketNSS::DoHandshake() {
- EnterFunction("");
- int net_error = net::OK;
- SECStatus rv = SSL_ForceHandshake(nss_fd_);
-
- // TODO(rkn): Handle the case in which server-bound cert generation takes
- // too long and the server has closed the connection. Report some new error
- // code so that the higher level code will attempt to delete the socket and
- // redo the handshake.
-
- if (client_auth_cert_needed_) {
- if (domain_bound_cert_xtn_negotiated_) {
- GotoState(STATE_GET_DOMAIN_BOUND_CERT_COMPLETE);
- net_error = ERR_IO_PENDING;
- } else {
- net_error = ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
- net_log_.AddEvent(NetLog::TYPE_SSL_HANDSHAKE_ERROR,
- make_scoped_refptr(new SSLErrorParams(net_error, 0)));
- // If the handshake already succeeded (because the server requests but
- // doesn't require a client cert), we need to invalidate the SSL session
- // so that we won't try to resume the non-client-authenticated session in
- // the next handshake. This will cause the server to ask for a client
- // cert again.
- if (rv == SECSuccess && SSL_InvalidateSession(nss_fd_) != SECSuccess) {
- LOG(WARNING) << "Couldn't invalidate SSL session: " << PR_GetError();
- }
- }
- } else if (rv == SECSuccess) {
- if (handshake_callback_called_) {
- // We need to see if the predicted certificate chain (in
- // |ssl_host_info_->state().certs) matches the actual certificate chain
- // before we call SaveSSLHostInfo, as that will update
- // |ssl_host_info_|.
- if (ssl_host_info_.get() && !ssl_host_info_->state().certs.empty()) {
- PeerCertificateChain certs(nss_fd_);
- const SSLHostInfo::State& state = ssl_host_info_->state();
- predicted_cert_chain_correct_ = certs.size() == state.certs.size();
- if (predicted_cert_chain_correct_) {
- for (unsigned i = 0; i < certs.size(); i++) {
- if (certs[i]->derCert.len != state.certs[i].size() ||
- memcmp(certs[i]->derCert.data, state.certs[i].data(),
- certs[i]->derCert.len) != 0) {
- predicted_cert_chain_correct_ = false;
- break;
- }
- }
- }
- }
-
-#if defined(SSL_ENABLE_OCSP_STAPLING)
- // TODO(agl): figure out how to plumb an OCSP response into the Mac
- // system library and update IsOCSPStaplingSupported for Mac.
- if (!predicted_cert_chain_correct_ && IsOCSPStaplingSupported()) {
- unsigned int len = 0;
- SSL_GetStapledOCSPResponse(nss_fd_, NULL, &len);
- if (len) {
- const unsigned int orig_len = len;
- scoped_array<uint8> ocsp_response(new uint8[orig_len]);
- SSL_GetStapledOCSPResponse(nss_fd_, ocsp_response.get(), &len);
- DCHECK_EQ(orig_len, len);
-
-#if defined(OS_WIN)
- CRYPT_DATA_BLOB ocsp_response_blob;
- ocsp_response_blob.cbData = len;
- ocsp_response_blob.pbData = ocsp_response.get();
- BOOL ok = CertSetCertificateContextProperty(
- server_cert_->os_cert_handle(),
- CERT_OCSP_RESPONSE_PROP_ID,
- CERT_SET_PROPERTY_IGNORE_PERSIST_ERROR_FLAG,
- &ocsp_response_blob);
- if (!ok) {
- VLOG(1) << "Failed to set OCSP response property: "
- << GetLastError();
- }
-#elif defined(USE_NSS)
- CacheOCSPResponseFromSideChannelFunction cache_ocsp_response =
- GetCacheOCSPResponseFromSideChannelFunction();
- SECItem ocsp_response_item;
- ocsp_response_item.type = siBuffer;
- ocsp_response_item.data = ocsp_response.get();
- ocsp_response_item.len = len;
-
- cache_ocsp_response(
- CERT_GetDefaultCertDB(), server_cert_nss_, PR_Now(),
- &ocsp_response_item, NULL);
-#endif
- }
- }
-#endif
+ if (error == ERR_IO_PENDING) {
+ // Asynchronous case.
+ client_auth_cert_needed_ = true;
+ return SECWouldBlock;
+ }
- SaveSSLHostInfo();
- // SSL handshake is completed. Let's verify the certificate.
- GotoState(STATE_VERIFY_DNSSEC);
- // Done!
- } else {
- // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=562434 -
- // SSL_ForceHandshake returned SECSuccess prematurely.
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::EndEventWithNetErrorCode, weak_net_log_,
+ NetLog::TYPE_SSL_GET_DOMAIN_BOUND_CERT, error));
+ SECStatus rv = SECSuccess;
+ if (error == OK) {
+ // Synchronous success.
+ int result = ImportDBCertAndKey(result_certificate, result_private_key);
+ if (result != OK) {
+ domain_bound_cert_type_ = CLIENT_CERT_INVALID_TYPE;
rv = SECFailure;
- net_error = ERR_SSL_PROTOCOL_ERROR;
- net_log_.AddEvent(NetLog::TYPE_SSL_HANDSHAKE_ERROR,
- make_scoped_refptr(new SSLErrorParams(net_error, 0)));
}
} else {
- PRErrorCode prerr = PR_GetError();
- net_error = HandleNSSError(prerr, true);
-
- // If not done, stay in this state
- if (net_error == ERR_IO_PENDING) {
- GotoState(STATE_HANDSHAKE);
- } else {
- net_log_.AddEvent(
- NetLog::TYPE_SSL_HANDSHAKE_ERROR,
- make_scoped_refptr(new SSLErrorParams(net_error, prerr)));
- }
+ rv = SECFailure;
}
- LeaveFunction("");
- return net_error;
+ int cert_count = (rv == SECSuccess) ? 1 : 0;
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, weak_net_log_,
+ NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
+ make_scoped_refptr(
+ new NetLogIntegerParameter("cert_count",
+ cert_count))));
+ return rv;
}
-int SSLClientSocketNSS::ImportDBCertAndKey(CERTCertificate** cert,
- SECKEYPrivateKey** key) {
+int SSLClientSocketNSS::Core::ImportDBCertAndKey(CERTCertificate** cert,
+ SECKEYPrivateKey** key) {
// Set the certificate.
SECItem cert_item;
cert_item.data = (unsigned char*) domain_bound_cert_.data();
@@ -1576,1056 +2468,1161 @@ int SSLClientSocketNSS::ImportDBCertAndKey(CERTCertificate** cert,
return OK;
}
-int SSLClientSocketNSS::DoGetDBCertComplete(int result) {
- SECStatus rv;
- net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_GET_DOMAIN_BOUND_CERT,
- result);
- client_auth_cert_needed_ = false;
+void SSLClientSocketNSS::Core::UpdateServerCert() {
+ nss_handshake_state_.server_cert_chain.Reset(nss_fd_);
+ nss_handshake_state_.server_cert = X509Certificate::CreateFromDERCertChain(
+ nss_handshake_state_.server_cert_chain.AsStringPieceVector());
+ if (nss_handshake_state_.server_cert) {
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::AddEvent, weak_net_log_,
+ NetLog::TYPE_SSL_CERTIFICATES_RECEIVED,
+ make_scoped_refptr(
+ new X509CertificateNetLogParam(
+ nss_handshake_state_.server_cert))));
+ }
+}
+
+void SSLClientSocketNSS::Core::UpdateConnectionStatus() {
+ SSLChannelInfo channel_info;
+ SECStatus ok = SSL_GetChannelInfo(nss_fd_,
+ &channel_info, sizeof(channel_info));
+ if (ok == SECSuccess &&
+ channel_info.length == sizeof(channel_info) &&
+ channel_info.cipherSuite) {
+ nss_handshake_state_.ssl_connection_status |=
+ (static_cast<int>(channel_info.cipherSuite) &
+ SSL_CONNECTION_CIPHERSUITE_MASK) <<
+ SSL_CONNECTION_CIPHERSUITE_SHIFT;
+
+ nss_handshake_state_.ssl_connection_status |=
+ (static_cast<int>(channel_info.compressionMethod) &
+ SSL_CONNECTION_COMPRESSION_MASK) <<
+ SSL_CONNECTION_COMPRESSION_SHIFT;
+
+ // NSS 3.12.x doesn't have version macros for TLS 1.1 and 1.2 (because NSS
+ // doesn't support them yet), so we use 0x0302 and 0x0303 directly.
+ int version = SSL_CONNECTION_VERSION_UNKNOWN;
+ if (channel_info.protocolVersion < SSL_LIBRARY_VERSION_3_0) {
+ // All versions less than SSL_LIBRARY_VERSION_3_0 are treated as SSL
+ // version 2.
+ version = SSL_CONNECTION_VERSION_SSL2;
+ } else if (channel_info.protocolVersion == SSL_LIBRARY_VERSION_3_0) {
+ version = SSL_CONNECTION_VERSION_SSL3;
+ } else if (channel_info.protocolVersion == SSL_LIBRARY_VERSION_3_1_TLS) {
+ version = SSL_CONNECTION_VERSION_TLS1;
+ } else if (channel_info.protocolVersion == 0x0302) {
+ version = SSL_CONNECTION_VERSION_TLS1_1;
+ } else if (channel_info.protocolVersion == 0x0303) {
+ version = SSL_CONNECTION_VERSION_TLS1_2;
+ }
+ nss_handshake_state_.ssl_connection_status |=
+ (version & SSL_CONNECTION_VERSION_MASK) <<
+ SSL_CONNECTION_VERSION_SHIFT;
+ }
+
+ // SSL_HandshakeNegotiatedExtension was added in NSS 3.12.6.
+ // Since SSL_MAX_EXTENSIONS was added at the same time, we can test
+ // SSL_MAX_EXTENSIONS for the presence of SSL_HandshakeNegotiatedExtension.
+#if defined(SSL_MAX_EXTENSIONS)
+ PRBool peer_supports_renego_ext;
+ ok = SSL_HandshakeNegotiatedExtension(nss_fd_, ssl_renegotiation_info_xtn,
+ &peer_supports_renego_ext);
+ if (ok == SECSuccess) {
+ if (!peer_supports_renego_ext) {
+ nss_handshake_state_.ssl_connection_status |=
+ SSL_CONNECTION_NO_RENEGOTIATION_EXTENSION;
+ // Log an informational message if the server does not support secure
+ // renegotiation (RFC 5746).
+ VLOG(1) << "The server " << host_and_port_.ToString()
+ << " does not support the TLS renegotiation_info extension.";
+ }
+ UMA_HISTOGRAM_ENUMERATION("Net.RenegotiationExtensionSupported",
+ peer_supports_renego_ext, 2);
+ }
+#endif
+
+ if (ssl_config_.version_fallback) {
+ nss_handshake_state_.ssl_connection_status |=
+ SSL_CONNECTION_VERSION_FALLBACK;
+ }
+}
+
+void SSLClientSocketNSS::Core::RecordDomainBoundCertSupport() const {
+ if (nss_handshake_state_.resumed_handshake)
+ return;
+
+ // Since this enum is used for a histogram, do not change or re-use values.
+ enum {
+ DISABLED = 0,
+ CLIENT_ONLY = 1,
+ CLIENT_AND_SERVER = 2,
+ DOMAIN_BOUND_CERT_USAGE_MAX
+ } supported = DISABLED;
+#ifdef SSL_ENABLE_OB_CERTS
+ if (domain_bound_cert_xtn_negotiated_)
+ supported = CLIENT_AND_SERVER;
+ else if (ssl_config_.domain_bound_certs_enabled)
+ supported = CLIENT_ONLY;
+#endif
+ UMA_HISTOGRAM_ENUMERATION("DomainBoundCerts.Support", supported,
+ DOMAIN_BOUND_CERT_USAGE_MAX);
+}
+
+int SSLClientSocketNSS::Core::DoBufferRecv(IOBuffer* read_buffer, int len) {
+ DCHECK(OnNetworkTaskRunner());
+ DCHECK_GT(len, 0);
+
+ if (detached_)
+ return ERR_ABORTED;
+
+ int rv = transport_->socket()->Read(
+ read_buffer, len,
+ base::Bind(&Core::BufferRecvComplete, base::Unretained(this),
+ scoped_refptr<IOBuffer>(read_buffer)));
+
+ if (!OnNSSTaskRunner() && rv != ERR_IO_PENDING) {
+ nss_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Core::BufferRecvComplete, this,
+ scoped_refptr<IOBuffer>(read_buffer), rv));
+ return rv;
+ }
+
+ return rv;
+}
+
+int SSLClientSocketNSS::Core::DoBufferSend(IOBuffer* send_buffer, int len) {
+ DCHECK(OnNetworkTaskRunner());
+ DCHECK_GT(len, 0);
+
+ if (detached_)
+ return ERR_ABORTED;
+
+ int rv = transport_->socket()->Write(
+ send_buffer, len,
+ base::Bind(&Core::BufferSendComplete,
+ base::Unretained(this)));
+
+ if (!OnNSSTaskRunner() && rv != ERR_IO_PENDING) {
+ nss_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&Core::BufferSendComplete, this, rv));
+ return rv;
+ }
+
+ return rv;
+}
+
+int SSLClientSocketNSS::Core::DoGetDomainBoundCert(
+ const std::string& origin,
+ const std::vector<uint8>& requested_cert_types) {
+ DCHECK(OnNetworkTaskRunner());
+
+ if (detached_)
+ return ERR_FAILED;
+
+ weak_net_log_->BeginEvent(NetLog::TYPE_SSL_GET_DOMAIN_BOUND_CERT, NULL);
+
+ int rv = server_bound_cert_service_->GetDomainBoundCert(
+ origin,
+ requested_cert_types,
+ &domain_bound_cert_type_,
+ &domain_bound_private_key_,
+ &domain_bound_cert_,
+ base::Bind(&Core::OnGetDomainBoundCertComplete, base::Unretained(this)),
+ &domain_bound_cert_request_handle_);
+
+ if (rv != ERR_IO_PENDING && !OnNSSTaskRunner()) {
+ nss_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&Core::OnHandshakeIOComplete, this, rv));
+ return ERR_IO_PENDING;
+ }
+
+ return rv;
+}
+
+void SSLClientSocketNSS::Core::OnHandshakeStateUpdated(
+ const HandshakeState& state) {
+ network_handshake_state_ = state;
+}
+
+void SSLClientSocketNSS::Core::BufferSendComplete(int result) {
+ if (!OnNSSTaskRunner()) {
+ if (detached_)
+ return;
+
+ nss_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Core::BufferSendComplete, this, result));
+ return;
+ }
+
+ DCHECK(OnNSSTaskRunner());
+
+ memio_PutWriteResult(nss_bufs_, MapErrorToNSS(result));
+ transport_send_busy_ = false;
+ OnSendComplete(result);
+}
+
+void SSLClientSocketNSS::Core::OnHandshakeIOComplete(int result) {
+ if (!OnNSSTaskRunner()) {
+ if (detached_)
+ return;
+
+ nss_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Core::OnHandshakeIOComplete, this, result));
+ return;
+ }
+
+ DCHECK(OnNSSTaskRunner());
+
+ int rv = DoHandshakeLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoConnectCallback(rv);
+}
+
+void SSLClientSocketNSS::Core::OnGetDomainBoundCertComplete(int result) {
+ DCHECK(OnNetworkTaskRunner());
+
domain_bound_cert_request_handle_ = NULL;
+ OnHandshakeIOComplete(result);
+}
- if (result != OK) {
- // Failed to get a DBC. Proceed without.
- rv = SSL_RestartHandshakeAfterCertReq(nss_fd_, NULL, NULL, NULL);
- if (rv != SECSuccess)
- return MapNSSError(PORT_GetError());
- GotoState(STATE_HANDSHAKE);
- return OK;
+void SSLClientSocketNSS::Core::BufferRecvComplete(
+ IOBuffer* read_buffer,
+ int result) {
+ DCHECK(read_buffer);
+
+ if (!OnNSSTaskRunner()) {
+ if (detached_)
+ return;
+
+ nss_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Core::BufferRecvComplete, this,
+ scoped_refptr<IOBuffer>(read_buffer), result));
+ return;
}
- CERTCertificate* cert;
- SECKEYPrivateKey* key;
- int error = ImportDBCertAndKey(&cert, &key);
- if (error != OK)
- return error;
+ DCHECK(OnNSSTaskRunner());
+
+ if (result > 0) {
+ char* buf;
+ int nb = memio_GetReadParams(nss_bufs_, &buf);
+ CHECK_GE(nb, result);
+ memcpy(buf, read_buffer->data(), result);
+ } else if (result == 0) {
+ transport_recv_eof_ = true;
+ }
+
+ memio_PutReadResult(nss_bufs_, MapErrorToNSS(result));
+ transport_recv_busy_ = false;
+ OnRecvComplete(result);
+}
+
+void SSLClientSocketNSS::Core::PostOrRunCallback(
+ const tracked_objects::Location& location,
+ const base::Closure& task) {
+ if (!OnNetworkTaskRunner()) {
+ network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&Core::PostOrRunCallback, this, location, task));
+ return;
+ }
+
+ if (detached_ || task.is_null())
+ return;
+ task.Run();
+}
+
+SSLClientSocketNSS::SSLClientSocketNSS(
+ base::SingleThreadTaskRunner* nss_task_runner,
+ ClientSocketHandle* transport_socket,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ SSLHostInfo* ssl_host_info,
+ const SSLClientSocketContext& context)
+ : nss_task_runner_(nss_task_runner),
+ transport_(transport_socket),
+ host_and_port_(host_and_port),
+ ssl_config_(ssl_config),
+ server_cert_verify_result_(NULL),
+ cert_verifier_(context.cert_verifier),
+ server_bound_cert_service_(context.server_bound_cert_service),
+ ssl_session_cache_shard_(context.ssl_session_cache_shard),
+ completed_handshake_(false),
+ next_handshake_state_(STATE_NONE),
+ nss_fd_(NULL),
+ net_log_(transport_socket->socket()->NetLog()),
+ ssl_host_info_(ssl_host_info),
+ transport_security_state_(context.transport_security_state),
+ valid_thread_id_(base::kInvalidThreadId) {
+ EnterFunction("");
+ InitCore();
+ LeaveFunction("");
+}
+
+SSLClientSocketNSS::~SSLClientSocketNSS() {
+ EnterFunction("");
+ Disconnect();
+ LeaveFunction("");
+}
+
+// static
+void SSLClientSocket::ClearSessionCache() {
+ // SSL_ClearSessionCache can't be called before NSS is initialized. Don't
+ // bother initializing NSS just to clear an empty SSL session cache.
+ if (!NSS_IsInitialized())
+ return;
+
+ SSL_ClearSessionCache();
+}
+
+void SSLClientSocketNSS::GetSSLInfo(SSLInfo* ssl_info) {
+ EnterFunction("");
+ ssl_info->Reset();
+ if (core_->state().server_cert_chain.empty() ||
+ !core_->state().server_cert_chain[0]) {
+ return;
+ }
+
+ ssl_info->cert_status = server_cert_verify_result_->cert_status;
+ ssl_info->cert = server_cert_verify_result_->verified_cert;
+ ssl_info->connection_status =
+ core_->state().ssl_connection_status;
+ ssl_info->public_key_hashes = server_cert_verify_result_->public_key_hashes;
+ for (std::vector<SHA1Fingerprint>::const_iterator
+ i = side_pinned_public_keys_.begin();
+ i != side_pinned_public_keys_.end(); i++) {
+ ssl_info->public_key_hashes.push_back(*i);
+ }
+ ssl_info->is_issued_by_known_root =
+ server_cert_verify_result_->is_issued_by_known_root;
+ ssl_info->client_cert_sent = WasDomainBoundCertSent() ||
+ (ssl_config_.send_client_cert && ssl_config_.client_cert);
+
+ PRUint16 cipher_suite = SSLConnectionStatusToCipherSuite(
+ core_->state().ssl_connection_status);
+ SSLCipherSuiteInfo cipher_info;
+ SECStatus ok = SSL_GetCipherSuiteInfo(cipher_suite,
+ &cipher_info, sizeof(cipher_info));
+ if (ok == SECSuccess) {
+ ssl_info->security_bits = cipher_info.effectiveKeyBits;
+ } else {
+ ssl_info->security_bits = -1;
+ LOG(DFATAL) << "SSL_GetCipherSuiteInfo returned " << PR_GetError()
+ << " for cipherSuite " << cipher_suite;
+ }
+
+ ssl_info->handshake_type = core_->state().resumed_handshake ?
+ SSLInfo::HANDSHAKE_RESUME : SSLInfo::HANDSHAKE_FULL;
+
+ LeaveFunction("");
+}
+
+void SSLClientSocketNSS::GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) {
+ EnterFunction("");
+ // TODO(rch): switch SSLCertRequestInfo.host_and_port to a HostPortPair
+ cert_request_info->host_and_port = host_and_port_.ToString();
+ cert_request_info->client_certs = core_->state().client_certs;
+ LeaveFunction(cert_request_info->client_certs.size());
+}
+
+int SSLClientSocketNSS::ExportKeyingMaterial(const base::StringPiece& label,
+ bool has_context,
+ const base::StringPiece& context,
+ unsigned char* out,
+ unsigned int outlen) {
+ if (!IsConnected())
+ return ERR_SOCKET_NOT_CONNECTED;
- CERTCertificateList* cert_chain = CERT_CertChainFromCert(cert,
- certUsageSSLClient,
- PR_FALSE);
- net_log_.AddEvent(NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
- make_scoped_refptr(new NetLogIntegerParameter("cert_count",
- cert_chain->len)));
- rv = SSL_RestartHandshakeAfterCertReq(nss_fd_, cert, key, cert_chain);
- if (rv != SECSuccess)
+ // SSL_ExportKeyingMaterial may block the current thread if |core_| is in
+ // the midst of a handshake.
+ SECStatus result = SSL_ExportKeyingMaterial(
+ nss_fd_, label.data(), label.size(), has_context,
+ reinterpret_cast<const unsigned char*>(context.data()),
+ context.length(), out, outlen);
+ if (result != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_ExportKeyingMaterial", "");
return MapNSSError(PORT_GetError());
-
- GotoState(STATE_HANDSHAKE);
- set_domain_bound_cert_type(domain_bound_cert_type_);
+ }
return OK;
}
-int SSLClientSocketNSS::DoVerifyDNSSEC(int result) {
- DNSValidationResult r = CheckDNSSECChain(host_and_port_.host(),
- server_cert_nss_,
- host_and_port_.port());
- if (r == DNSVR_SUCCESS) {
- local_server_cert_verify_result_.cert_status |= CERT_STATUS_IS_DNSSEC;
- local_server_cert_verify_result_.verified_cert = server_cert_;
- server_cert_verify_result_ = &local_server_cert_verify_result_;
- GotoState(STATE_VERIFY_CERT_COMPLETE);
- return OK;
- }
+SSLClientSocket::NextProtoStatus
+SSLClientSocketNSS::GetNextProto(std::string* proto,
+ std::string* server_protos) {
+ *proto = core_->state().next_proto;
+ *server_protos = core_->state().server_protos;
+ return core_->state().next_proto_status;
+}
- GotoState(STATE_VERIFY_CERT);
+int SSLClientSocketNSS::Connect(const CompletionCallback& callback) {
+ EnterFunction("");
+ DCHECK(transport_.get());
+ DCHECK_EQ(STATE_NONE, next_handshake_state_);
+ DCHECK(user_connect_callback_.is_null());
- return OK;
-}
+ EnsureThreadIdAssigned();
-int SSLClientSocketNSS::DoVerifyCert(int result) {
- DCHECK(server_cert_nss_);
+ net_log_.BeginEvent(NetLog::TYPE_SSL_CONNECT, NULL);
- GotoState(STATE_VERIFY_CERT_COMPLETE);
+ int rv = Init();
+ if (rv != OK) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_CONNECT, rv);
+ return rv;
+ }
- // If the certificate is expected to be bad we can use the
- // expectation as the cert status. Don't use |server_cert_| here
- // because it can be set to NULL in case we failed to create
- // X509Certificate in UpdateServerCert(). This may happen when this
- // code is used inside sandbox.
- base::StringPiece der_cert(
- reinterpret_cast<char*>(server_cert_nss_->derCert.data),
- server_cert_nss_->derCert.len);
- CertStatus cert_status;
- if (ssl_config_.IsAllowedBadCert(der_cert, &cert_status)) {
- DCHECK(start_cert_verification_time_.is_null());
- VLOG(1) << "Received an expected bad cert with status: " << cert_status;
- server_cert_verify_result_ = &local_server_cert_verify_result_;
- local_server_cert_verify_result_.Reset();
- local_server_cert_verify_result_.cert_status = cert_status;
- local_server_cert_verify_result_.verified_cert = server_cert_;
- return OK;
+ rv = InitializeSSLOptions();
+ if (rv != OK) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_CONNECT, rv);
+ return rv;
}
- // We may have failed to create X509Certificate object if we are
- // running inside sandbox.
- if (!server_cert_) {
- server_cert_verify_result_ = &local_server_cert_verify_result_;
- local_server_cert_verify_result_.Reset();
- local_server_cert_verify_result_.cert_status = CERT_STATUS_INVALID;
- return ERR_CERT_INVALID;
+ rv = InitializeSSLPeerName();
+ if (rv != OK) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_CONNECT, rv);
+ return rv;
}
- start_cert_verification_time_ = base::TimeTicks::Now();
+ if (ssl_config_.cached_info_enabled && ssl_host_info_.get()) {
+ GotoState(STATE_LOAD_SSL_HOST_INFO);
+ } else {
+ GotoState(STATE_HANDSHAKE);
+ }
- if (ssl_host_info_.get() && !ssl_host_info_->state().certs.empty() &&
- predicted_cert_chain_correct_) {
- // If the SSLHostInfo had a prediction for the certificate chain of this
- // server then it will have optimistically started a verification of that
- // chain. So, if the prediction was correct, we should wait for that
- // verification to finish rather than start our own.
- net_log_.AddEvent(NetLog::TYPE_SSL_VERIFICATION_MERGED, NULL);
- UMA_HISTOGRAM_ENUMERATION("Net.SSLVerificationMerged", 1 /* true */, 2);
- base::TimeTicks end_time = ssl_host_info_->verification_end_time();
- if (end_time.is_null())
- end_time = base::TimeTicks::Now();
- UMA_HISTOGRAM_TIMES("Net.SSLVerificationMergedMsSaved",
- end_time - ssl_host_info_->verification_start_time());
- server_cert_verify_result_ = &ssl_host_info_->cert_verify_result();
- return ssl_host_info_->WaitForCertVerification(
- base::Bind(&SSLClientSocketNSS::OnHandshakeIOComplete,
- base::Unretained(this)));
+ rv = DoHandshakeLoop(OK);
+ if (rv == ERR_IO_PENDING) {
+ user_connect_callback_ = callback;
} else {
- UMA_HISTOGRAM_ENUMERATION("Net.SSLVerificationMerged", 0 /* false */, 2);
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_CONNECT, rv);
}
- int flags = 0;
- if (ssl_config_.rev_checking_enabled)
- flags |= X509Certificate::VERIFY_REV_CHECKING_ENABLED;
- if (ssl_config_.verify_ev_cert)
- flags |= X509Certificate::VERIFY_EV_CERT;
- if (ssl_config_.cert_io_enabled)
- flags |= X509Certificate::VERIFY_CERT_IO_ENABLED;
- verifier_.reset(new SingleRequestCertVerifier(cert_verifier_));
- server_cert_verify_result_ = &local_server_cert_verify_result_;
- return verifier_->Verify(
- server_cert_, host_and_port_.host(), flags,
- SSLConfigService::GetCRLSet(),
- &local_server_cert_verify_result_,
- base::Bind(&SSLClientSocketNSS::OnHandshakeIOComplete,
- base::Unretained(this)),
- net_log_);
+ LeaveFunction("");
+ return rv > OK ? OK : rv;
}
-// Derived from AuthCertificateCallback() in
-// mozilla/source/security/manager/ssl/src/nsNSSCallbacks.cpp.
-int SSLClientSocketNSS::DoVerifyCertComplete(int result) {
- verifier_.reset();
+void SSLClientSocketNSS::Disconnect() {
+ EnterFunction("");
- if (!start_cert_verification_time_.is_null()) {
- base::TimeDelta verify_time =
- base::TimeTicks::Now() - start_cert_verification_time_;
- if (result == OK)
- UMA_HISTOGRAM_TIMES("Net.SSLCertVerificationTime", verify_time);
- else
- UMA_HISTOGRAM_TIMES("Net.SSLCertVerificationTimeError", verify_time);
- }
+ CHECK(CalledOnValidThread());
- // We used to remember the intermediate CA certs in the NSS database
- // persistently. However, NSS opens a connection to the SQLite database
- // during NSS initialization and doesn't close the connection until NSS
- // shuts down. If the file system where the database resides is gone,
- // the database connection goes bad. What's worse, the connection won't
- // recover when the file system comes back. Until this NSS or SQLite bug
- // is fixed, we need to avoid using the NSS database for non-essential
- // purposes. See https://bugzilla.mozilla.org/show_bug.cgi?id=508081 and
- // http://crbug.com/15630 for more info.
+ // Shut down anything that may call us back.
+ core_->Detach();
+ verifier_.reset();
+ transport_->socket()->Disconnect();
- // TODO(hclam): Skip logging if server cert was expected to be bad because
- // |server_cert_verify_result_| doesn't contain all the information about
- // the cert.
- if (result == OK)
- LogConnectionTypeMetrics();
+ // Reset object state.
+ user_connect_callback_.Reset();
+ local_server_cert_verify_result_.Reset();
+ server_cert_verify_result_ = NULL;
+ completed_handshake_ = false;
+ start_cert_verification_time_ = base::TimeTicks();
+ InitCore();
- completed_handshake_ = true;
+ LeaveFunction("");
+}
- if (!user_read_callback_.is_null()) {
- int rv = DoReadLoop(OK);
- if (rv != ERR_IO_PENDING)
- DoReadCallback(rv);
- }
+bool SSLClientSocketNSS::IsConnected() const {
+ // Ideally, we should also check if we have received the close_notify alert
+ // message from the server, and return false in that case. We're not doing
+ // that, so this function may return a false positive. Since the upper
+ // layer (HttpNetworkTransaction) needs to handle a persistent connection
+ // closed by the server when we send a request anyway, a false positive in
+ // exchange for simpler code is a good trade-off.
+ EnterFunction("");
+ bool ret = completed_handshake_ && transport_->socket()->IsConnected();
+ LeaveFunction("");
+ return ret;
+}
-#if defined(OFFICIAL_BUILD) && !defined(OS_ANDROID)
- // Take care of any mandates for public key pinning.
- //
- // Pinning is only enabled for official builds to make sure that others don't
- // end up with pins that cannot be easily updated.
+bool SSLClientSocketNSS::IsConnectedAndIdle() const {
+ // Unlike IsConnected, this method doesn't return a false positive.
//
- // TODO(agl): we might have an issue here where a request for foo.example.com
- // merges into a SPDY connection to www.example.com, and gets a different
- // certificate.
+ // Strictly speaking, we should check if we have received the close_notify
+ // alert message from the server, and return false in that case. Although
+ // the close_notify alert message means EOF in the SSL layer, it is just
+ // bytes to the transport layer below, so
+ // transport_->socket()->IsConnectedAndIdle() returns the desired false
+ // when we receive close_notify.
+ EnterFunction("");
+ bool ret = completed_handshake_ && transport_->socket()->IsConnectedAndIdle();
+ LeaveFunction("");
+ return ret;
+}
- const CertStatus cert_status = server_cert_verify_result_->cert_status;
- if ((result == OK || (IsCertificateError(result) &&
- IsCertStatusMinorError(cert_status))) &&
- server_cert_verify_result_->is_issued_by_known_root &&
- transport_security_state_) {
- bool sni_available =
- ssl_config_.version_max >= SSL_PROTOCOL_VERSION_TLS1 ||
- ssl_config_.version_fallback;
- const std::string& host = host_and_port_.host();
+int SSLClientSocketNSS::GetPeerAddress(AddressList* address) const {
+ return transport_->socket()->GetPeerAddress(address);
+}
- TransportSecurityState::DomainState domain_state;
- if (transport_security_state_->GetDomainState(host, sni_available,
- &domain_state) &&
- domain_state.HasPins()) {
- if (!domain_state.IsChainOfPublicKeysPermitted(
- server_cert_verify_result_->public_key_hashes)) {
- const base::Time build_time = base::GetBuildTime();
- // Pins are not enforced if the build is sufficiently old. Chrome
- // users should get updates every six weeks or so, but it's possible
- // that some users will stop getting updates for some reason. We
- // don't want those users building up as a pool of people with bad
- // pins.
- if ((base::Time::Now() - build_time).InDays() < 70 /* 10 weeks */) {
- result = ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN;
- UMA_HISTOGRAM_BOOLEAN("Net.PublicKeyPinSuccess", false);
- TransportSecurityState::ReportUMAOnPinFailure(host);
- }
- } else {
- UMA_HISTOGRAM_BOOLEAN("Net.PublicKeyPinSuccess", true);
- }
- }
- }
-#endif
+int SSLClientSocketNSS::GetLocalAddress(IPEndPoint* address) const {
+ return transport_->socket()->GetLocalAddress(address);
+}
- // Exit DoHandshakeLoop and return the result to the caller to Connect.
- DCHECK(next_handshake_state_ == STATE_NONE);
- return result;
+const BoundNetLog& SSLClientSocketNSS::NetLog() const {
+ return net_log_;
}
-int SSLClientSocketNSS::DoPayloadRead() {
- EnterFunction(user_read_buf_len_);
- DCHECK(user_read_buf_);
- DCHECK_GT(user_read_buf_len_, 0);
- int rv = PR_Read(nss_fd_, user_read_buf_->data(), user_read_buf_len_);
- if (client_auth_cert_needed_) {
- // We don't need to invalidate the non-client-authenticated SSL session
- // because the server will renegotiate anyway.
- LeaveFunction("");
- rv = ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
- net_log_.AddEvent(NetLog::TYPE_SSL_READ_ERROR,
- make_scoped_refptr(new SSLErrorParams(rv, 0)));
- return rv;
+void SSLClientSocketNSS::SetSubresourceSpeculation() {
+ if (transport_.get() && transport_->socket()) {
+ transport_->socket()->SetSubresourceSpeculation();
+ } else {
+ NOTREACHED();
}
- if (rv >= 0) {
- net_log_.AddByteTransferEvent(NetLog::TYPE_SSL_SOCKET_BYTES_RECEIVED, rv,
- user_read_buf_->data());
- LeaveFunction("");
- return rv;
+}
+
+void SSLClientSocketNSS::SetOmniboxSpeculation() {
+ if (transport_.get() && transport_->socket()) {
+ transport_->socket()->SetOmniboxSpeculation();
+ } else {
+ NOTREACHED();
}
- PRErrorCode prerr = PR_GetError();
- if (prerr == PR_WOULD_BLOCK_ERROR) {
- LeaveFunction("");
- return ERR_IO_PENDING;
+}
+
+bool SSLClientSocketNSS::WasEverUsed() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->WasEverUsed();
}
- LeaveFunction("");
- rv = HandleNSSError(prerr, false);
- net_log_.AddEvent(NetLog::TYPE_SSL_READ_ERROR,
- make_scoped_refptr(new SSLErrorParams(rv, prerr)));
- return rv;
+ NOTREACHED();
+ return false;
}
-int SSLClientSocketNSS::DoPayloadWrite() {
- EnterFunction(user_write_buf_len_);
- DCHECK(user_write_buf_);
- int rv = PR_Write(nss_fd_, user_write_buf_->data(), user_write_buf_len_);
- if (rv >= 0) {
- net_log_.AddByteTransferEvent(NetLog::TYPE_SSL_SOCKET_BYTES_SENT, rv,
- user_write_buf_->data());
- LeaveFunction("");
- return rv;
+bool SSLClientSocketNSS::UsingTCPFastOpen() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->UsingTCPFastOpen();
}
- PRErrorCode prerr = PR_GetError();
- if (prerr == PR_WOULD_BLOCK_ERROR) {
- LeaveFunction("");
- return ERR_IO_PENDING;
+ NOTREACHED();
+ return false;
+}
+
+int64 SSLClientSocketNSS::NumBytesRead() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->NumBytesRead();
}
- LeaveFunction("");
- rv = HandleNSSError(prerr, false);
- net_log_.AddEvent(NetLog::TYPE_SSL_WRITE_ERROR,
- make_scoped_refptr(new SSLErrorParams(rv, prerr)));
- return rv;
+ NOTREACHED();
+ return -1;
}
-void SSLClientSocketNSS::LogConnectionTypeMetrics() const {
- UpdateConnectionTypeHistograms(CONNECTION_SSL);
- if (server_cert_verify_result_->has_md5)
- UpdateConnectionTypeHistograms(CONNECTION_SSL_MD5);
- if (server_cert_verify_result_->has_md2)
- UpdateConnectionTypeHistograms(CONNECTION_SSL_MD2);
- if (server_cert_verify_result_->has_md4)
- UpdateConnectionTypeHistograms(CONNECTION_SSL_MD4);
- if (server_cert_verify_result_->has_md5_ca)
- UpdateConnectionTypeHistograms(CONNECTION_SSL_MD5_CA);
- if (server_cert_verify_result_->has_md2_ca)
- UpdateConnectionTypeHistograms(CONNECTION_SSL_MD2_CA);
- int ssl_version = SSLConnectionStatusToVersion(ssl_connection_status_);
- switch (ssl_version) {
- case SSL_CONNECTION_VERSION_SSL2:
- UpdateConnectionTypeHistograms(CONNECTION_SSL_SSL2);
- break;
- case SSL_CONNECTION_VERSION_SSL3:
- UpdateConnectionTypeHistograms(CONNECTION_SSL_SSL3);
- break;
- case SSL_CONNECTION_VERSION_TLS1:
- UpdateConnectionTypeHistograms(CONNECTION_SSL_TLS1);
- break;
- case SSL_CONNECTION_VERSION_TLS1_1:
- UpdateConnectionTypeHistograms(CONNECTION_SSL_TLS1_1);
- break;
- case SSL_CONNECTION_VERSION_TLS1_2:
- UpdateConnectionTypeHistograms(CONNECTION_SSL_TLS1_2);
- break;
- };
+base::TimeDelta SSLClientSocketNSS::GetConnectTimeMicros() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->GetConnectTimeMicros();
+ }
+ NOTREACHED();
+ return base::TimeDelta::FromMicroseconds(-1);
}
-// SaveSSLHostInfo saves the certificate chain of the connection so that we can
-// start verification faster in the future.
-void SSLClientSocketNSS::SaveSSLHostInfo() {
- if (!ssl_host_info_.get())
- return;
+int SSLClientSocketNSS::Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(core_);
- // If the SSLHostInfo hasn't managed to load from disk yet then we can't save
- // anything.
- if (ssl_host_info_->WaitForDataReady(net::CompletionCallback()) != OK)
- return;
+ EnterFunction(buf_len);
+ int rv = core_->Read(buf, buf_len, callback);
+ LeaveFunction(rv);
- SSLHostInfo::State* state = ssl_host_info_->mutable_state();
+ return rv;
+}
- state->certs.clear();
- PeerCertificateChain certs(nss_fd_);
- for (unsigned i = 0; i < certs.size(); i++) {
- if (certs[i]->derCert.len > std::numeric_limits<uint16>::max())
- return;
+int SSLClientSocketNSS::Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(core_);
- state->certs.push_back(std::string(
- reinterpret_cast<char*>(certs[i]->derCert.data),
- certs[i]->derCert.len));
- }
+ EnterFunction(buf_len);
+ int rv = core_->Write(buf, buf_len, callback);
+ LeaveFunction(rv);
- ssl_host_info_->Persist();
+ return rv;
}
-// Do as much network I/O as possible between the buffer and the
-// transport socket. Return true if some I/O performed, false
-// otherwise (error or ERR_IO_PENDING).
-bool SSLClientSocketNSS::DoTransportIO() {
- EnterFunction("");
- bool network_moved = false;
- if (nss_bufs_ != NULL) {
- int rv;
- // Read and write as much data as we can. The loop is neccessary
- // because Write() may return synchronously.
- do {
- rv = BufferSend();
- if (rv > 0)
- network_moved = true;
- } while (rv > 0);
- if (!transport_recv_eof_ && BufferRecv() >= 0)
- network_moved = true;
- }
- LeaveFunction(network_moved);
- return network_moved;
+bool SSLClientSocketNSS::SetReceiveBufferSize(int32 size) {
+ return transport_->socket()->SetReceiveBufferSize(size);
}
-// Return 0 for EOF,
-// > 0 for bytes transferred immediately,
-// < 0 for error (or the non-error ERR_IO_PENDING).
-int SSLClientSocketNSS::BufferSend() {
- if (transport_send_busy_)
- return ERR_IO_PENDING;
+bool SSLClientSocketNSS::SetSendBufferSize(int32 size) {
+ return transport_->socket()->SetSendBufferSize(size);
+}
+int SSLClientSocketNSS::Init() {
EnterFunction("");
- const char* buf1;
- const char* buf2;
- unsigned int len1, len2;
- memio_GetWriteParams(nss_bufs_, &buf1, &len1, &buf2, &len2);
- const unsigned int len = len1 + len2;
-
- int rv = 0;
- if (len) {
- scoped_refptr<IOBuffer> send_buffer(new IOBuffer(len));
- memcpy(send_buffer->data(), buf1, len1);
- memcpy(send_buffer->data() + len1, buf2, len2);
- rv = transport_->socket()->Write(
- send_buffer, len,
- base::Bind(&SSLClientSocketNSS::BufferSendComplete,
- base::Unretained(this)));
- if (rv == ERR_IO_PENDING) {
- transport_send_busy_ = true;
- } else {
- memio_PutWriteResult(nss_bufs_, MapErrorToNSS(rv));
- }
+ // Initialize the NSS SSL library in a threadsafe way. This also
+ // initializes the NSS base library.
+ EnsureNSSSSLInit();
+ if (!NSS_IsInitialized())
+ return ERR_UNEXPECTED;
+#if !defined(OS_MACOSX) && !defined(OS_WIN)
+ if (ssl_config_.cert_io_enabled) {
+ // We must call EnsureNSSHttpIOInit() here, on the IO thread, to get the IO
+ // loop by MessageLoopForIO::current().
+ // X509Certificate::Verify() runs on a worker thread of CertVerifier.
+ EnsureNSSHttpIOInit();
}
+#endif
- LeaveFunction(rv);
- return rv;
+ LeaveFunction("");
+ return OK;
}
-void SSLClientSocketNSS::BufferSendComplete(int result) {
- EnterFunction(result);
- memio_PutWriteResult(nss_bufs_, MapErrorToNSS(result));
- transport_send_busy_ = false;
- OnSendComplete(result);
- LeaveFunction("");
+void SSLClientSocketNSS::InitCore() {
+ core_ = new Core(base::ThreadTaskRunnerHandle::Get(), nss_task_runner_,
+ transport_.get(), host_and_port_, ssl_config_, &net_log_,
+ server_bound_cert_service_);
}
-int SSLClientSocketNSS::BufferRecv() {
- if (transport_recv_busy_) return ERR_IO_PENDING;
+int SSLClientSocketNSS::InitializeSSLOptions() {
+ // Transport connected, now hook it up to nss
+ // TODO(port): specify rx and tx buffer sizes separately
+ nss_fd_ = memio_CreateIOLayer(kRecvBufferSize);
+ if (nss_fd_ == NULL) {
+ return ERR_OUT_OF_MEMORY; // TODO(port): map NSPR error code.
+ }
+
+ // Grab pointer to buffers
+ memio_Private* nss_bufs = memio_GetSecret(nss_fd_);
+
+ /* Create SSL state machine */
+ /* Push SSL onto our fake I/O socket */
+ nss_fd_ = SSL_ImportFD(NULL, nss_fd_);
+ if (nss_fd_ == NULL) {
+ LogFailedNSSFunction(net_log_, "SSL_ImportFD", "");
+ return ERR_OUT_OF_MEMORY; // TODO(port): map NSPR/NSS error code.
+ }
+ // TODO(port): set more ssl options! Check errors!
- char* buf;
- int nb = memio_GetReadParams(nss_bufs_, &buf);
- EnterFunction(nb);
int rv;
- if (!nb) {
- // buffer too full to read into, so no I/O possible at moment
- rv = ERR_IO_PENDING;
- } else {
- recv_buffer_ = new IOBuffer(nb);
- rv = transport_->socket()->Read(
- recv_buffer_, nb,
- base::Bind(&SSLClientSocketNSS::BufferRecvComplete,
- base::Unretained(this)));
- if (rv == ERR_IO_PENDING) {
- transport_recv_busy_ = true;
- } else {
- if (rv > 0) {
- memcpy(buf, recv_buffer_->data(), rv);
- } else if (rv == 0) {
- transport_recv_eof_ = true;
- }
- memio_PutReadResult(nss_bufs_, MapErrorToNSS(rv));
- recv_buffer_ = NULL;
- }
+
+ rv = SSL_OptionSet(nss_fd_, SSL_SECURITY, PR_TRUE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_SECURITY");
+ return ERR_UNEXPECTED;
}
- LeaveFunction(rv);
- return rv;
-}
-void SSLClientSocketNSS::BufferRecvComplete(int result) {
- EnterFunction(result);
- if (result > 0) {
- char* buf;
- memio_GetReadParams(nss_bufs_, &buf);
- memcpy(buf, recv_buffer_->data(), result);
- } else if (result == 0) {
- transport_recv_eof_ = true;
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SSL2, PR_FALSE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_SSL2");
+ return ERR_UNEXPECTED;
}
- recv_buffer_ = NULL;
- memio_PutReadResult(nss_bufs_, MapErrorToNSS(result));
- transport_recv_busy_ = false;
- OnRecvComplete(result);
- LeaveFunction("");
-}
-int SSLClientSocketNSS::HandleNSSError(PRErrorCode nss_error,
- bool handshake_error) {
- int net_error = handshake_error ? MapNSSHandshakeError(nss_error) :
- MapNSSError(nss_error);
+ // Don't do V2 compatible hellos because they don't support TLS extensions.
+ rv = SSL_OptionSet(nss_fd_, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_V2_COMPATIBLE_HELLO");
+ return ERR_UNEXPECTED;
+ }
-#if defined(OS_WIN)
- // On Windows, a handle to the HCRYPTPROV is cached in the X509Certificate
- // os_cert_handle() as an optimization. However, if the certificate
- // private key is stored on a smart card, and the smart card is removed,
- // the cached HCRYPTPROV will not be able to obtain the HCRYPTKEY again,
- // preventing client certificate authentication. Because the
- // X509Certificate may outlive the individual SSLClientSocketNSS, due to
- // caching in X509Certificate, this failure ends up preventing client
- // certificate authentication with the same certificate for all future
- // attempts, even after the smart card has been re-inserted. By setting
- // the CERT_KEY_PROV_HANDLE_PROP_ID to NULL, the cached HCRYPTPROV will
- // typically be freed. This allows a new HCRYPTPROV to be obtained from
- // the certificate on the next attempt, which should succeed if the smart
- // card has been re-inserted, or will typically prompt the user to
- // re-insert the smart card if not.
- if ((net_error == ERR_SSL_CLIENT_AUTH_CERT_NO_PRIVATE_KEY ||
- net_error == ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED) &&
- ssl_config_.send_client_cert && ssl_config_.client_cert) {
- CertSetCertificateContextProperty(
- ssl_config_.client_cert->os_cert_handle(),
- CERT_KEY_PROV_HANDLE_PROP_ID, 0, NULL);
+ SSLVersionRange version_range;
+ version_range.min = ssl_config_.version_min;
+ version_range.max = ssl_config_.version_max;
+ rv = SSL_VersionRangeSet(nss_fd_, &version_range);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_VersionRangeSet", "");
+ return ERR_NO_SSL_VERSIONS_ENABLED;
+ }
+
+ for (std::vector<uint16>::const_iterator it =
+ ssl_config_.disabled_cipher_suites.begin();
+ it != ssl_config_.disabled_cipher_suites.end(); ++it) {
+ // This will fail if the specified cipher is not implemented by NSS, but
+ // the failure is harmless.
+ SSL_CipherPrefSet(nss_fd_, *it, PR_FALSE);
}
-#endif
-
- return net_error;
-}
-// static
-// NSS calls this if an incoming certificate needs to be verified.
-// Do nothing but return SECSuccess.
-// This is called only in full handshake mode.
-// Peer certificate is retrieved in HandshakeCallback() later, which is called
-// in full handshake mode or in resumption handshake mode.
-SECStatus SSLClientSocketNSS::OwnAuthCertHandler(void* arg,
- PRFileDesc* socket,
- PRBool checksig,
- PRBool is_server) {
-#ifdef SSL_ENABLE_FALSE_START
- SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg);
- if (!that->server_cert_nss_) {
- // Only need to turn off False Start in the initial handshake. Also, it is
- // unsafe to call SSL_OptionSet in a renegotiation because the "first
- // handshake" lock isn't already held, which will result in an assertion
- // failure in the ssl_Get1stHandshakeLock call in SSL_OptionSet.
- PRBool npn;
- SECStatus rv = SSL_HandshakeNegotiatedExtension(socket,
- ssl_next_proto_nego_xtn,
- &npn);
- if (rv != SECSuccess || !npn) {
- // If the server doesn't support NPN, then we don't do False Start with
- // it.
- SSL_OptionSet(socket, SSL_ENABLE_FALSE_START, PR_FALSE);
- }
+#ifdef SSL_ENABLE_SESSION_TICKETS
+ // Support RFC 5077
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SESSION_TICKETS, PR_TRUE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(
+ net_log_, "SSL_OptionSet", "SSL_ENABLE_SESSION_TICKETS");
}
+#else
+ #error "You need to install NSS-3.12 or later to build chromium"
#endif
- // Tell NSS to not verify the certificate.
- return SECSuccess;
-}
-
-// static
-bool SSLClientSocketNSS::DomainBoundCertNegotiated(PRFileDesc* socket) {
- // TODO(wtc,mattm): this is temporary while DBC support is changed into
- // Channel ID.
- return false;
-}
+#ifdef SSL_ENABLE_DEFLATE
+ // Some web servers have been found to break if TLS is used *or* if DEFLATE
+ // is advertised. Thus, if TLS is disabled (probably because we are doing
+ // SSLv3 fallback), we disable DEFLATE also.
+ // See http://crbug.com/31628
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_DEFLATE,
+ ssl_config_.version_max >= SSL_PROTOCOL_VERSION_TLS1);
+ if (rv != SECSuccess)
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_DEFLATE");
+#endif
-SECStatus SSLClientSocketNSS::DomainBoundClientAuthHandler(
- const SECItem* cert_types,
- CERTCertificate** result_certificate,
- SECKEYPrivateKey** result_private_key) {
- domain_bound_cert_xtn_negotiated_ = true;
+#ifdef SSL_ENABLE_FALSE_START
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_FALSE_START,
+ ssl_config_.false_start_enabled);
+ if (rv != SECSuccess)
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_FALSE_START");
+#endif
- // We have negotiated the domain-bound certificate extension.
- std::string origin = "https://" + host_and_port_.ToString();
- std::vector<uint8> requested_cert_types(cert_types->data,
- cert_types->data + cert_types->len);
- net_log_.BeginEvent(NetLog::TYPE_SSL_GET_DOMAIN_BOUND_CERT, NULL);
- int error = server_bound_cert_service_->GetDomainBoundCert(
- origin,
- requested_cert_types,
- &domain_bound_cert_type_,
- &domain_bound_private_key_,
- &domain_bound_cert_,
- base::Bind(&SSLClientSocketNSS::OnHandshakeIOComplete,
- base::Unretained(this)),
- &domain_bound_cert_request_handle_);
+#ifdef SSL_ENABLE_RENEGOTIATION
+ // We allow servers to request renegotiation. Since we're a client,
+ // prohibiting this is rather a waste of time. Only servers are in a
+ // position to prevent renegotiation attacks.
+ // http://extendedsubset.com/?p=8
- if (error == ERR_IO_PENDING) {
- // Asynchronous case.
- client_auth_cert_needed_ = true;
- return SECWouldBlock;
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_RENEGOTIATION,
+ SSL_RENEGOTIATE_TRANSITIONAL);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(
+ net_log_, "SSL_OptionSet", "SSL_ENABLE_RENEGOTIATION");
}
- net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_GET_DOMAIN_BOUND_CERT,
- error);
+#endif // SSL_ENABLE_RENEGOTIATION
- SECStatus rv = SECSuccess;
- if (error == OK) {
- // Synchronous success.
- int result = ImportDBCertAndKey(result_certificate,
- result_private_key);
- if (result == OK) {
- set_domain_bound_cert_type(domain_bound_cert_type_);
- } else {
- rv = SECFailure;
+#ifdef SSL_CBC_RANDOM_IV
+ rv = SSL_OptionSet(nss_fd_, SSL_CBC_RANDOM_IV,
+ ssl_config_.false_start_enabled);
+ if (rv != SECSuccess)
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_CBC_RANDOM_IV");
+#endif
+
+#ifdef SSL_ENABLE_OCSP_STAPLING
+ if (IsOCSPStaplingSupported()) {
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_OCSP_STAPLING, PR_TRUE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet",
+ "SSL_ENABLE_OCSP_STAPLING");
}
- } else {
- rv = SECFailure; // Synchronous failure.
}
+#endif
- int cert_count = (rv == SECSuccess) ? 1 : 0;
- net_log_.AddEvent(NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
- make_scoped_refptr(new NetLogIntegerParameter("cert_count",
- cert_count)));
- return rv;
-}
-
-#if defined(NSS_PLATFORM_CLIENT_AUTH)
-// static
-// NSS calls this if a client certificate is needed.
-SECStatus SSLClientSocketNSS::PlatformClientAuthHandler(
- void* arg,
- PRFileDesc* socket,
- CERTDistNames* ca_names,
- CERTCertList** result_certs,
- void** result_private_key,
- CERTCertificate** result_nss_certificate,
- SECKEYPrivateKey** result_nss_private_key) {
- SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg);
+#ifdef SSL_ENABLE_CACHED_INFO
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_CACHED_INFO,
+ ssl_config_.cached_info_enabled);
+ if (rv != SECSuccess)
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_CACHED_INFO");
+#endif
- that->net_log_.AddEvent(NetLog::TYPE_SSL_CLIENT_CERT_REQUESTED, NULL);
+#ifdef SSL_ENABLE_OB_CERTS
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_OB_CERTS,
+ ssl_config_.domain_bound_certs_enabled);
+ if (rv != SECSuccess)
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_OB_CERTS");
+#endif
- const SECItem* cert_types = SSL_GetRequestedClientCertificateTypes(socket);
+#ifdef SSL_ENCRYPT_CLIENT_CERTS
+ // For now, enable the encrypted client certificates extension only if
+ // server-bound certificates are enabled.
+ rv = SSL_OptionSet(nss_fd_, SSL_ENCRYPT_CLIENT_CERTS,
+ ssl_config_.domain_bound_certs_enabled);
+ if (rv != SECSuccess)
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENCRYPT_CLIENT_CERTS");
+#endif
- // Check if a domain-bound certificate is requested.
- if (DomainBoundCertNegotiated(socket)) {
- return that->DomainBoundClientAuthHandler(
- cert_types, result_nss_certificate, result_nss_private_key);
+ rv = SSL_OptionSet(nss_fd_, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_HANDSHAKE_AS_CLIENT");
+ return ERR_UNEXPECTED;
}
- that->client_auth_cert_needed_ = !that->ssl_config_.send_client_cert;
-#if defined(OS_WIN)
- if (that->ssl_config_.send_client_cert) {
- if (that->ssl_config_.client_cert) {
- PCCERT_CONTEXT cert_context =
- that->ssl_config_.client_cert->os_cert_handle();
+ if (!core_->Init(nss_fd_, nss_bufs))
+ return ERR_UNEXPECTED;
- HCRYPTPROV_OR_NCRYPT_KEY_HANDLE crypt_prov = 0;
- DWORD key_spec = 0;
- BOOL must_free = FALSE;
- BOOL acquired_key = CryptAcquireCertificatePrivateKey(
- cert_context, CRYPT_ACQUIRE_CACHE_FLAG, NULL,
- &crypt_prov, &key_spec, &must_free);
+ // Tell SSL the hostname we're trying to connect to.
+ SSL_SetURL(nss_fd_, host_and_port_.host().c_str());
- if (acquired_key) {
- // Since we passed CRYPT_ACQUIRE_CACHE_FLAG, |must_free| must be false
- // according to the MSDN documentation.
- CHECK_EQ(must_free, FALSE);
- DCHECK_NE(key_spec, CERT_NCRYPT_KEY_SPEC);
+ // Tell SSL we're a client; needed if not letting NSPR do socket I/O
+ SSL_ResetHandshake(nss_fd_, PR_FALSE);
- SECItem der_cert;
- der_cert.type = siDERCertBuffer;
- der_cert.data = cert_context->pbCertEncoded;
- der_cert.len = cert_context->cbCertEncoded;
+ return OK;
+}
- // TODO(rsleevi): Error checking for NSS allocation errors.
- CERTCertDBHandle* db_handle = CERT_GetDefaultCertDB();
- CERTCertificate* user_cert = CERT_NewTempCertificate(
- db_handle, &der_cert, NULL, PR_FALSE, PR_TRUE);
- if (!user_cert) {
- // Importing the certificate can fail for reasons including a serial
- // number collision. See crbug.com/97355.
- that->net_log_.AddEvent(NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
- make_scoped_refptr(new NetLogIntegerParameter("cert_count", 0)));
- return SECFailure;
- }
- CERTCertList* cert_chain = CERT_NewCertList();
- CERT_AddCertToListTail(cert_chain, user_cert);
+int SSLClientSocketNSS::InitializeSSLPeerName() {
+ // Tell NSS who we're connected to
+ AddressList peer_address;
+ int err = transport_->socket()->GetPeerAddress(&peer_address);
+ if (err != OK)
+ return err;
- // Add the intermediates.
- X509Certificate::OSCertHandles intermediates =
- that->ssl_config_.client_cert->GetIntermediateCertificates();
- for (X509Certificate::OSCertHandles::const_iterator it =
- intermediates.begin(); it != intermediates.end(); ++it) {
- der_cert.data = (*it)->pbCertEncoded;
- der_cert.len = (*it)->cbCertEncoded;
+ SockaddrStorage storage;
+ if (!peer_address.front().ToSockAddr(storage.addr, &storage.addr_len))
+ return ERR_UNEXPECTED;
- CERTCertificate* intermediate = CERT_NewTempCertificate(
- db_handle, &der_cert, NULL, PR_FALSE, PR_TRUE);
- if (!intermediate) {
- CERT_DestroyCertList(cert_chain);
- that->net_log_.AddEvent(NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
- make_scoped_refptr(new NetLogIntegerParameter("cert_count",
- 0)));
- return SECFailure;
- }
- CERT_AddCertToListTail(cert_chain, intermediate);
- }
- PCERT_KEY_CONTEXT key_context = reinterpret_cast<PCERT_KEY_CONTEXT>(
- PORT_ZAlloc(sizeof(CERT_KEY_CONTEXT)));
- key_context->cbSize = sizeof(*key_context);
- // NSS will free this context when no longer in use, but the
- // |must_free| result from CryptAcquireCertificatePrivateKey was false
- // so we increment the refcount to negate NSS's future decrement.
- CryptContextAddRef(crypt_prov, NULL, 0);
- key_context->hCryptProv = crypt_prov;
- key_context->dwKeySpec = key_spec;
- *result_private_key = key_context;
- *result_certs = cert_chain;
+ PRNetAddr peername;
+ memset(&peername, 0, sizeof(peername));
+ DCHECK_LE(static_cast<size_t>(storage.addr_len), sizeof(peername));
+ size_t len = std::min(static_cast<size_t>(storage.addr_len),
+ sizeof(peername));
+ memcpy(&peername, storage.addr, len);
- int cert_count = 1 + intermediates.size();
- that->net_log_.AddEvent(NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
- make_scoped_refptr(new NetLogIntegerParameter("cert_count",
- cert_count)));
- return SECSuccess;
- }
- LOG(WARNING) << "Client cert found without private key";
- }
- // Send no client certificate.
- that->net_log_.AddEvent(NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
- make_scoped_refptr(new NetLogIntegerParameter("cert_count", 0)));
- return SECFailure;
+ // Adjust the address family field for BSD, whose sockaddr
+ // structure has a one-byte length and one-byte address family
+ // field at the beginning. PRNetAddr has a two-byte address
+ // family field at the beginning.
+ peername.raw.family = storage.addr->sa_family;
+
+ memio_SetPeerName(nss_fd_, &peername);
+
+ // Set the peer ID for session reuse. This is necessary when we create an
+ // SSL tunnel through a proxy -- GetPeerName returns the proxy's address
+ // rather than the destination server's address in that case.
+ std::string peer_id = host_and_port_.ToString();
+ // If the ssl_session_cache_shard_ is non-empty, we append it to the peer id.
+ // This will cause session cache misses between sockets with different values
+ // of ssl_session_cache_shard_ and this is used to partition the session cache
+ // for incognito mode.
+ if (!ssl_session_cache_shard_.empty()) {
+ peer_id += "/" + ssl_session_cache_shard_;
}
+ SECStatus rv = SSL_SetSockPeerID(nss_fd_, const_cast<char*>(peer_id.c_str()));
+ if (rv != SECSuccess)
+ LogFailedNSSFunction(net_log_, "SSL_SetSockPeerID", peer_id.c_str());
- that->client_certs_.clear();
+ return OK;
+}
+
+void SSLClientSocketNSS::DoConnectCallback(int rv) {
+ EnterFunction(rv);
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ DCHECK(!user_connect_callback_.is_null());
+
+ base::ResetAndReturn(&user_connect_callback_).Run(rv > OK ? OK : rv);
+ LeaveFunction("");
+}
- std::vector<CERT_NAME_BLOB> issuer_list(ca_names->nnames);
- for (int i = 0; i < ca_names->nnames; ++i) {
- issuer_list[i].cbData = ca_names->names[i].len;
- issuer_list[i].pbData = ca_names->names[i].data;
+void SSLClientSocketNSS::OnHandshakeIOComplete(int result) {
+ EnterFunction(result);
+ int rv = DoHandshakeLoop(result);
+ if (rv != ERR_IO_PENDING) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_CONNECT, rv);
+ DoConnectCallback(rv);
}
+ LeaveFunction("");
+}
- // Client certificates of the user are in the "MY" system certificate store.
- HCERTSTORE my_cert_store = CertOpenSystemStore(NULL, L"MY");
- if (!my_cert_store) {
- LOG(ERROR) << "Could not open the \"MY\" system certificate store: "
- << GetLastError();
- that->net_log_.AddEvent(NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
- make_scoped_refptr(new NetLogIntegerParameter("cert_count", 0)));
- return SECFailure;
- }
+void SSLClientSocketNSS::LoadSSLHostInfo() {
+ const SSLHostInfo::State& state(ssl_host_info_->state());
- // Enumerate the client certificates.
- CERT_CHAIN_FIND_BY_ISSUER_PARA find_by_issuer_para;
- memset(&find_by_issuer_para, 0, sizeof(find_by_issuer_para));
- find_by_issuer_para.cbSize = sizeof(find_by_issuer_para);
- find_by_issuer_para.pszUsageIdentifier = szOID_PKIX_KP_CLIENT_AUTH;
- find_by_issuer_para.cIssuer = ca_names->nnames;
- find_by_issuer_para.rgIssuer = ca_names->nnames ? &issuer_list[0] : NULL;
- find_by_issuer_para.pfnFindCallback = ClientCertFindCallback;
+ if (state.certs.empty())
+ return;
- PCCERT_CHAIN_CONTEXT chain_context = NULL;
- DWORD find_flags = CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_FLAG |
- CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_URL_FLAG;
+ const std::vector<std::string>& certs_in = state.certs;
+ core_->SetPredictedCertificates(certs_in);
+}
- for (;;) {
- // Find a certificate chain.
- chain_context = CertFindChainInStore(my_cert_store,
- X509_ASN_ENCODING,
- find_flags,
- CERT_CHAIN_FIND_BY_ISSUER,
- &find_by_issuer_para,
- chain_context);
- if (!chain_context) {
- DWORD err = GetLastError();
- if (err != CRYPT_E_NOT_FOUND)
- DLOG(ERROR) << "CertFindChainInStore failed: " << err;
- break;
- }
+int SSLClientSocketNSS::DoLoadSSLHostInfo() {
+ EnterFunction("");
+ int rv = ssl_host_info_->WaitForDataReady(
+ base::Bind(&SSLClientSocketNSS::OnHandshakeIOComplete,
+ base::Unretained(this)));
+ GotoState(STATE_HANDSHAKE);
- // Get the leaf certificate.
- PCCERT_CONTEXT cert_context =
- chain_context->rgpChain[0]->rgpElement[0]->pCertContext;
- // Create a copy the handle, so that we can close the "MY" certificate store
- // before returning from this function.
- PCCERT_CONTEXT cert_context2;
- BOOL ok = CertAddCertificateContextToStore(NULL, cert_context,
- CERT_STORE_ADD_USE_EXISTING,
- &cert_context2);
- if (!ok) {
- NOTREACHED();
- continue;
- }
+ if (rv == OK) {
+ LoadSSLHostInfo();
+ } else {
+ DCHECK_EQ(ERR_IO_PENDING, rv);
+ GotoState(STATE_LOAD_SSL_HOST_INFO);
+ }
- // Copy the rest of the chain. Copying the chain stops gracefully if an
- // error is encountered, with the partial chain being used as the
- // intermediates, as opposed to failing to consider the client certificate
- // at all.
- net::X509Certificate::OSCertHandles intermediates;
- for (DWORD i = 1; i < chain_context->rgpChain[0]->cElement; i++) {
- PCCERT_CONTEXT intermediate_copy;
- ok = CertAddCertificateContextToStore(
- NULL, chain_context->rgpChain[0]->rgpElement[i]->pCertContext,
- CERT_STORE_ADD_USE_EXISTING, &intermediate_copy);
- if (!ok) {
- NOTREACHED();
+ LeaveFunction("");
+ return rv;
+}
+
+int SSLClientSocketNSS::DoHandshakeLoop(int last_io_result) {
+ EnterFunction(last_io_result);
+ int rv = last_io_result;
+ do {
+ // Default to STATE_NONE for next state.
+ // (This is a quirk carried over from the windows
+ // implementation. It makes reading the logs a bit harder.)
+ // State handlers can and often do call GotoState just
+ // to stay in the current state.
+ State state = next_handshake_state_;
+ GotoState(STATE_NONE);
+ switch (state) {
+ case STATE_LOAD_SSL_HOST_INFO:
+ DCHECK(rv == OK || rv == ERR_IO_PENDING);
+ rv = DoLoadSSLHostInfo();
+ break;
+ case STATE_HANDSHAKE:
+ rv = DoHandshake();
+ break;
+ case STATE_HANDSHAKE_COMPLETE:
+ rv = DoHandshakeComplete(rv);
+ break;
+ case STATE_VERIFY_DNSSEC:
+ rv = DoVerifyDNSSEC(rv);
+ break;
+ case STATE_VERIFY_CERT:
+ DCHECK(rv == OK);
+ rv = DoVerifyCert(rv);
+ break;
+ case STATE_VERIFY_CERT_COMPLETE:
+ rv = DoVerifyCertComplete(rv);
+ break;
+ case STATE_NONE:
+ default:
+ rv = ERR_UNEXPECTED;
+ LOG(DFATAL) << "unexpected state " << state;
break;
- }
- intermediates.push_back(intermediate_copy);
}
+ } while (rv != ERR_IO_PENDING && next_handshake_state_ != STATE_NONE);
+ LeaveFunction("");
+ return rv;
+}
- scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromHandle(
- cert_context2, intermediates);
- that->client_certs_.push_back(cert);
-
- X509Certificate::FreeOSCertHandle(cert_context2);
- for (net::X509Certificate::OSCertHandles::iterator it =
- intermediates.begin(); it != intermediates.end(); ++it) {
- net::X509Certificate::FreeOSCertHandle(*it);
- }
- }
+int SSLClientSocketNSS::DoHandshake() {
+ EnterFunction("");
+ int rv = core_->Connect(
+ base::Bind(&SSLClientSocketNSS::OnHandshakeIOComplete,
+ base::Unretained(this)));
+ GotoState(STATE_HANDSHAKE_COMPLETE);
- BOOL ok = CertCloseStore(my_cert_store, CERT_CLOSE_STORE_CHECK_FLAG);
- DCHECK(ok);
+ LeaveFunction(rv);
+ return rv;
+}
- // Tell NSS to suspend the client authentication. We will then abort the
- // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED.
- return SECWouldBlock;
-#elif defined(OS_MACOSX)
- if (that->ssl_config_.send_client_cert) {
- if (that->ssl_config_.client_cert) {
- OSStatus os_error = noErr;
- SecIdentityRef identity = NULL;
- SecKeyRef private_key = NULL;
- CFArrayRef chain =
- that->ssl_config_.client_cert->CreateClientCertificateChain();
- if (chain) {
- identity = reinterpret_cast<SecIdentityRef>(
- const_cast<void*>(CFArrayGetValueAtIndex(chain, 0)));
- }
- if (identity)
- os_error = SecIdentityCopyPrivateKey(identity, &private_key);
+int SSLClientSocketNSS::DoHandshakeComplete(int result) {
+ EnterFunction(result);
- if (chain && identity && os_error == noErr) {
- // TODO(rsleevi): Error checking for NSS allocation errors.
- *result_certs = CERT_NewCertList();
- *result_private_key = private_key;
+ if (result == OK) {
+ SaveSSLHostInfo();
+ // SSL handshake is completed. Let's verify the certificate.
+ GotoState(STATE_VERIFY_DNSSEC);
+ // Done!
+ }
+ if (core_->state().domain_bound_cert_type != CLIENT_CERT_INVALID_TYPE)
+ set_domain_bound_cert_type(core_->state().domain_bound_cert_type);
- for (CFIndex i = 0; i < CFArrayGetCount(chain); ++i) {
- CSSM_DATA cert_data;
- SecCertificateRef cert_ref;
- if (i == 0) {
- cert_ref = that->ssl_config_.client_cert->os_cert_handle();
- } else {
- cert_ref = reinterpret_cast<SecCertificateRef>(
- const_cast<void*>(CFArrayGetValueAtIndex(chain, i)));
- }
- os_error = SecCertificateGetData(cert_ref, &cert_data);
- if (os_error != noErr)
- break;
+ LeaveFunction(result);
+ return result;
+}
- SECItem der_cert;
- der_cert.type = siDERCertBuffer;
- der_cert.data = cert_data.Data;
- der_cert.len = cert_data.Length;
- CERTCertificate* nss_cert = CERT_NewTempCertificate(
- CERT_GetDefaultCertDB(), &der_cert, NULL, PR_FALSE, PR_TRUE);
- if (!nss_cert) {
- // In the event of an NSS error we make up an OS error and reuse
- // the error handling, below.
- os_error = errSecCreateChainFailed;
- break;
- }
- CERT_AddCertToListTail(*result_certs, nss_cert);
- }
- }
- if (os_error == noErr) {
- int cert_count = 0;
- if (chain) {
- cert_count = CFArrayGetCount(chain);
- CFRelease(chain);
- }
- that->net_log_.AddEvent(NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
- make_scoped_refptr(new NetLogIntegerParameter("cert_count",
- cert_count)));
- return SECSuccess;
- }
- OSSTATUS_LOG(WARNING, os_error)
- << "Client cert found, but could not be used";
- if (*result_certs) {
- CERT_DestroyCertList(*result_certs);
- *result_certs = NULL;
- }
- if (*result_private_key)
- *result_private_key = NULL;
- if (private_key)
- CFRelease(private_key);
- if (chain)
- CFRelease(chain);
- }
- // Send no client certificate.
- that->net_log_.AddEvent(NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
- make_scoped_refptr(new NetLogIntegerParameter("cert_count", 0)));
- return SECFailure;
- }
- that->client_certs_.clear();
+int SSLClientSocketNSS::DoVerifyDNSSEC(int result) {
+ DCHECK(!core_->state().server_cert_chain.empty());
+ DCHECK(core_->state().server_cert_chain[0]);
- // First, get the cert issuer names allowed by the server.
- std::vector<CertPrincipal> valid_issuers;
- int n = ca_names->nnames;
- for (int i = 0; i < n; i++) {
- // Parse each name into a CertPrincipal object.
- CertPrincipal p;
- if (p.ParseDistinguishedName(ca_names->names[i].data,
- ca_names->names[i].len)) {
- valid_issuers.push_back(p);
- }
+ DNSValidationResult r = CheckDNSSECChain(
+ host_and_port_.host(), core_->state().server_cert_chain[0],
+ host_and_port_.port());
+ if (r == DNSVR_SUCCESS) {
+ local_server_cert_verify_result_.cert_status |= CERT_STATUS_IS_DNSSEC;
+ local_server_cert_verify_result_.verified_cert =
+ core_->state().server_cert;
+ server_cert_verify_result_ = &local_server_cert_verify_result_;
+ GotoState(STATE_VERIFY_CERT_COMPLETE);
+ return OK;
}
- // Now get the available client certs whose issuers are allowed by the server.
- X509Certificate::GetSSLClientCertificates(that->host_and_port_.host(),
- valid_issuers,
- &that->client_certs_);
+ GotoState(STATE_VERIFY_CERT);
- // Tell NSS to suspend the client authentication. We will then abort the
- // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED.
- return SECWouldBlock;
-#else
- return SECFailure;
-#endif
+ return OK;
}
-#else // NSS_PLATFORM_CLIENT_AUTH
-
-// static
-// NSS calls this if a client certificate is needed.
-// Based on Mozilla's NSS_GetClientAuthData.
-SECStatus SSLClientSocketNSS::ClientAuthHandler(
- void* arg,
- PRFileDesc* socket,
- CERTDistNames* ca_names,
- CERTCertificate** result_certificate,
- SECKEYPrivateKey** result_private_key) {
- SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg);
-
- that->net_log_.AddEvent(NetLog::TYPE_SSL_CLIENT_CERT_REQUESTED, NULL);
+int SSLClientSocketNSS::DoVerifyCert(int result) {
+ DCHECK(!core_->state().server_cert_chain.empty());
+ DCHECK(core_->state().server_cert_chain[0]);
- const SECItem* cert_types = SSL_GetRequestedClientCertificateTypes(socket);
+ GotoState(STATE_VERIFY_CERT_COMPLETE);
- // Check if a domain-bound certificate is requested.
- if (DomainBoundCertNegotiated(socket)) {
- return that->DomainBoundClientAuthHandler(
- cert_types, result_certificate, result_private_key);
+ // If the certificate is expected to be bad we can use the expectation as
+ // the cert status.
+ base::StringPiece der_cert(
+ reinterpret_cast<char*>(
+ core_->state().server_cert_chain[0]->derCert.data),
+ core_->state().server_cert_chain[0]->derCert.len);
+ CertStatus cert_status;
+ if (ssl_config_.IsAllowedBadCert(der_cert, &cert_status)) {
+ DCHECK(start_cert_verification_time_.is_null());
+ VLOG(1) << "Received an expected bad cert with status: " << cert_status;
+ server_cert_verify_result_ = &local_server_cert_verify_result_;
+ local_server_cert_verify_result_.Reset();
+ local_server_cert_verify_result_.cert_status = cert_status;
+ local_server_cert_verify_result_.verified_cert =
+ core_->state().server_cert;
+ return OK;
}
- // Regular client certificate requested.
- that->client_auth_cert_needed_ = !that->ssl_config_.send_client_cert;
- void* wincx = SSL_RevealPinArg(socket);
-
- // Second pass: a client certificate should have been selected.
- if (that->ssl_config_.send_client_cert) {
- if (that->ssl_config_.client_cert) {
- CERTCertificate* cert = CERT_DupCertificate(
- that->ssl_config_.client_cert->os_cert_handle());
- SECKEYPrivateKey* privkey = PK11_FindKeyByAnyCert(cert, wincx);
- if (privkey) {
- // TODO(jsorianopastor): We should wait for server certificate
- // verification before sending our credentials. See
- // http://crbug.com/13934.
- *result_certificate = cert;
- *result_private_key = privkey;
- // A cert_count of -1 means the number of certificates is unknown.
- // NSS will construct the certificate chain.
- that->net_log_.AddEvent(NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
- make_scoped_refptr(new NetLogIntegerParameter("cert_count", -1)));
- return SECSuccess;
- }
- LOG(WARNING) << "Client cert found without private key";
- }
- // Send no client certificate.
- that->net_log_.AddEvent(NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
- make_scoped_refptr(new NetLogIntegerParameter("cert_count", 0)));
- return SECFailure;
+ // We may have failed to create X509Certificate object if we are
+ // running inside sandbox.
+ if (!core_->state().server_cert) {
+ server_cert_verify_result_ = &local_server_cert_verify_result_;
+ local_server_cert_verify_result_.Reset();
+ local_server_cert_verify_result_.cert_status = CERT_STATUS_INVALID;
+ return ERR_CERT_INVALID;
}
- // Iterate over all client certificates.
- CERTCertList* client_certs = CERT_FindUserCertsByUsage(
- CERT_GetDefaultCertDB(), certUsageSSLClient,
- PR_FALSE, PR_FALSE, wincx);
- if (client_certs) {
- for (CERTCertListNode* node = CERT_LIST_HEAD(client_certs);
- !CERT_LIST_END(node, client_certs);
- node = CERT_LIST_NEXT(node)) {
- // Only offer unexpired certificates.
- if (CERT_CheckCertValidTimes(node->cert, PR_Now(), PR_TRUE) !=
- secCertTimeValid)
- continue;
- // Filter by issuer.
- //
- // TODO(davidben): This does a binary comparison of the DER-encoded
- // issuers. We should match according to RFC 5280 sec. 7.1. We should find
- // an appropriate NSS function or add one if needbe.
- if (ca_names->nnames &&
- NSS_CmpCertChainWCANames(node->cert, ca_names) != SECSuccess)
- continue;
- X509Certificate* x509_cert = X509Certificate::CreateFromHandle(
- node->cert, net::X509Certificate::OSCertHandles());
- that->client_certs_.push_back(x509_cert);
- }
- CERT_DestroyCertList(client_certs);
+ start_cert_verification_time_ = base::TimeTicks::Now();
+
+ if (ssl_host_info_.get() && !ssl_host_info_->state().certs.empty() &&
+ core_->state().predicted_cert_chain_correct) {
+ // If the SSLHostInfo had a prediction for the certificate chain of this
+ // server then it will have optimistically started a verification of that
+ // chain. So, if the prediction was correct, we should wait for that
+ // verification to finish rather than start our own.
+ net_log_.AddEvent(NetLog::TYPE_SSL_VERIFICATION_MERGED, NULL);
+ UMA_HISTOGRAM_ENUMERATION("Net.SSLVerificationMerged", 1 /* true */, 2);
+ base::TimeTicks end_time = ssl_host_info_->verification_end_time();
+ if (end_time.is_null())
+ end_time = base::TimeTicks::Now();
+ UMA_HISTOGRAM_TIMES("Net.SSLVerificationMergedMsSaved",
+ end_time - ssl_host_info_->verification_start_time());
+ server_cert_verify_result_ = &ssl_host_info_->cert_verify_result();
+ return ssl_host_info_->WaitForCertVerification(
+ base::Bind(&SSLClientSocketNSS::OnHandshakeIOComplete,
+ base::Unretained(this)));
+ } else {
+ UMA_HISTOGRAM_ENUMERATION("Net.SSLVerificationMerged", 0 /* false */, 2);
}
- // Tell NSS to suspend the client authentication. We will then abort the
- // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED.
- return SECWouldBlock;
+ int flags = 0;
+ if (ssl_config_.rev_checking_enabled)
+ flags |= X509Certificate::VERIFY_REV_CHECKING_ENABLED;
+ if (ssl_config_.verify_ev_cert)
+ flags |= X509Certificate::VERIFY_EV_CERT;
+ if (ssl_config_.cert_io_enabled)
+ flags |= X509Certificate::VERIFY_CERT_IO_ENABLED;
+ verifier_.reset(new SingleRequestCertVerifier(cert_verifier_));
+ server_cert_verify_result_ = &local_server_cert_verify_result_;
+ return verifier_->Verify(
+ core_->state().server_cert, host_and_port_.host(), flags,
+ SSLConfigService::GetCRLSet(), &local_server_cert_verify_result_,
+ base::Bind(&SSLClientSocketNSS::OnHandshakeIOComplete,
+ base::Unretained(this)),
+ net_log_);
}
-#endif // NSS_PLATFORM_CLIENT_AUTH
-void SSLClientSocketNSS::RecordDomainBoundCertSupport() const {
- PRBool last_handshake_resumed;
- SECStatus ok = SSL_HandshakeResumedSession(nss_fd_, &last_handshake_resumed);
- if (ok != SECSuccess || last_handshake_resumed)
- return;
+// Derived from AuthCertificateCallback() in
+// mozilla/source/security/manager/ssl/src/nsNSSCallbacks.cpp.
+int SSLClientSocketNSS::DoVerifyCertComplete(int result) {
+ verifier_.reset();
- // Since this enum is used for a histogram, do not change or re-use values.
- enum {
- DISABLED = 0,
- CLIENT_ONLY = 1,
- CLIENT_AND_SERVER = 2,
- DOMAIN_BOUND_CERT_USAGE_MAX
- } supported = DISABLED;
-#ifdef SSL_ENABLE_OB_CERTS
- if (domain_bound_cert_xtn_negotiated_)
- supported = CLIENT_AND_SERVER;
- else if (ssl_config_.domain_bound_certs_enabled)
- supported = CLIENT_ONLY;
-#endif
- UMA_HISTOGRAM_ENUMERATION("DomainBoundCerts.Support", supported,
- DOMAIN_BOUND_CERT_USAGE_MAX);
-}
+ if (!start_cert_verification_time_.is_null()) {
+ base::TimeDelta verify_time =
+ base::TimeTicks::Now() - start_cert_verification_time_;
+ if (result == OK)
+ UMA_HISTOGRAM_TIMES("Net.SSLCertVerificationTime", verify_time);
+ else
+ UMA_HISTOGRAM_TIMES("Net.SSLCertVerificationTimeError", verify_time);
+ }
-// static
-// NSS calls this when handshake is completed.
-// After the SSL handshake is finished, use CertVerifier to verify
-// the saved server certificate.
-void SSLClientSocketNSS::HandshakeCallback(PRFileDesc* socket,
- void* arg) {
- SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg);
+ // We used to remember the intermediate CA certs in the NSS database
+ // persistently. However, NSS opens a connection to the SQLite database
+ // during NSS initialization and doesn't close the connection until NSS
+ // shuts down. If the file system where the database resides is gone,
+ // the database connection goes bad. What's worse, the connection won't
+ // recover when the file system comes back. Until this NSS or SQLite bug
+ // is fixed, we need to avoid using the NSS database for non-essential
+ // purposes. See https://bugzilla.mozilla.org/show_bug.cgi?id=508081 and
+ // http://crbug.com/15630 for more info.
- that->handshake_callback_called_ = true;
+ // TODO(hclam): Skip logging if server cert was expected to be bad because
+ // |server_cert_verify_result_| doesn't contain all the information about
+ // the cert.
+ if (result == OK)
+ LogConnectionTypeMetrics();
- that->RecordDomainBoundCertSupport();
- that->UpdateServerCert();
- that->UpdateConnectionStatus();
-}
+ completed_handshake_ = true;
-// NextProtoCallback is called by NSS during the handshake, if the server
-// supports NPN, to select a protocol from the list that the server offered.
-// See the comment in net/third_party/nss/ssl/ssl.h for the meanings of the
-// arguments.
-// static
-SECStatus
-SSLClientSocketNSS::NextProtoCallback(void* arg,
- PRFileDesc* nss_fd,
- const unsigned char* protos,
- unsigned int protos_len,
- unsigned char* proto_out,
- unsigned int* proto_out_len,
- unsigned int proto_max_len) {
- SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg);
+#if defined(OFFICIAL_BUILD) && !defined(OS_ANDROID)
+ // Take care of any mandates for public key pinning.
+ //
+ // Pinning is only enabled for official builds to make sure that others don't
+ // end up with pins that cannot be easily updated.
+ //
+ // TODO(agl): we might have an issue here where a request for foo.example.com
+ // merges into a SPDY connection to www.example.com, and gets a different
+ // certificate.
- // For each protocol in server preference, see if we support it.
- for (unsigned int i = 0; i < protos_len; ) {
- const size_t len = protos[i];
- for (std::vector<std::string>::const_iterator
- j = that->ssl_config_.next_protos.begin();
- j != that->ssl_config_.next_protos.end(); j++) {
- // Having very long elements in the |next_protos| vector isn't a disaster
- // because they'll never be selected, but it does indicate an error
- // somewhere.
- DCHECK_LT(j->size(), 256u);
+ const CertStatus cert_status = server_cert_verify_result_->cert_status;
+ if ((result == OK || (IsCertificateError(result) &&
+ IsCertStatusMinorError(cert_status))) &&
+ server_cert_verify_result_->is_issued_by_known_root &&
+ transport_security_state_) {
+ bool sni_available =
+ ssl_config_.version_max >= SSL_PROTOCOL_VERSION_TLS1 ||
+ ssl_config_.version_fallback;
+ const std::string& host = host_and_port_.host();
- if (j->size() == len &&
- memcmp(&protos[i + 1], j->data(), len) == 0) {
- that->next_proto_status_ = kNextProtoNegotiated;
- that->next_proto_ = *j;
- break;
+ TransportSecurityState::DomainState domain_state;
+ if (transport_security_state_->GetDomainState(host, sni_available,
+ &domain_state) &&
+ domain_state.HasPins()) {
+ if (!domain_state.IsChainOfPublicKeysPermitted(
+ server_cert_verify_result_->public_key_hashes)) {
+ const base::Time build_time = base::GetBuildTime();
+ // Pins are not enforced if the build is sufficiently old. Chrome
+ // users should get updates every six weeks or so, but it's possible
+ // that some users will stop getting updates for some reason. We
+ // don't want those users building up as a pool of people with bad
+ // pins.
+ if ((base::Time::Now() - build_time).InDays() < 70 /* 10 weeks */) {
+ result = ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN;
+ UMA_HISTOGRAM_BOOLEAN("Net.PublicKeyPinSuccess", false);
+ TransportSecurityState::ReportUMAOnPinFailure(host);
+ }
+ } else {
+ UMA_HISTOGRAM_BOOLEAN("Net.PublicKeyPinSuccess", true);
}
}
+ }
+#endif
+
+ // Exit DoHandshakeLoop and return the result to the caller to Connect.
+ DCHECK_EQ(STATE_NONE, next_handshake_state_);
+ return result;
+}
- if (that->next_proto_status_ == kNextProtoNegotiated)
+void SSLClientSocketNSS::LogConnectionTypeMetrics() const {
+ UpdateConnectionTypeHistograms(CONNECTION_SSL);
+ if (server_cert_verify_result_->has_md5)
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_MD5);
+ if (server_cert_verify_result_->has_md2)
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_MD2);
+ if (server_cert_verify_result_->has_md4)
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_MD4);
+ if (server_cert_verify_result_->has_md5_ca)
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_MD5_CA);
+ if (server_cert_verify_result_->has_md2_ca)
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_MD2_CA);
+ int ssl_version = SSLConnectionStatusToVersion(
+ core_->state().ssl_connection_status);
+ switch (ssl_version) {
+ case SSL_CONNECTION_VERSION_SSL2:
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_SSL2);
+ break;
+ case SSL_CONNECTION_VERSION_SSL3:
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_SSL3);
+ break;
+ case SSL_CONNECTION_VERSION_TLS1:
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_TLS1);
+ break;
+ case SSL_CONNECTION_VERSION_TLS1_1:
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_TLS1_1);
+ break;
+ case SSL_CONNECTION_VERSION_TLS1_2:
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_TLS1_2);
break;
+ };
+}
- // NSS checks that the data in |protos| is well formed, so we know that
- // this doesn't cause us to jump off the end of the buffer.
- i += len + 1;
- }
+// SaveSSLHostInfo saves the certificate chain of the connection so that we can
+// start verification faster in the future.
+void SSLClientSocketNSS::SaveSSLHostInfo() {
+ if (!ssl_host_info_.get())
+ return;
- that->server_protos_.assign(
- reinterpret_cast<const char*>(protos), protos_len);
+ // If the SSLHostInfo hasn't managed to load from disk yet then we can't save
+ // anything.
+ if (ssl_host_info_->WaitForDataReady(net::CompletionCallback()) != OK)
+ return;
- // If we didn't find a protocol, we select the first one from our list.
- if (that->next_proto_status_ != kNextProtoNegotiated) {
- that->next_proto_status_ = kNextProtoNoOverlap;
- that->next_proto_ = that->ssl_config_.next_protos[0];
- }
+ SSLHostInfo::State* state = ssl_host_info_->mutable_state();
- if (that->next_proto_.size() > proto_max_len) {
- PORT_SetError(SEC_ERROR_OUTPUT_LEN);
- return SECFailure;
+ state->certs.clear();
+ const PeerCertificateChain& certs = core_->state().server_cert_chain;
+ for (unsigned i = 0; i < certs.size(); i++) {
+ if (certs[i] == NULL ||
+ certs[i]->derCert.len > std::numeric_limits<uint16>::max()) {
+ return;
+ }
+
+ state->certs.push_back(std::string(
+ reinterpret_cast<char*>(certs[i]->derCert.data),
+ certs[i]->derCert.len));
}
- memcpy(proto_out, that->next_proto_.data(), that->next_proto_.size());
- *proto_out_len = that->next_proto_.size();
- return SECSuccess;
+
+ ssl_host_info_->Persist();
}
void SSLClientSocketNSS::EnsureThreadIdAssigned() const {
« no previous file with comments | « net/socket/ssl_client_socket_nss.h ('k') | net/socket/ssl_client_socket_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698