| 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 {
|
|
|