diff options
author | erg@google.com <erg@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-26 22:47:11 +0000 |
---|---|---|
committer | erg@google.com <erg@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-26 22:47:11 +0000 |
commit | d100e44f64d4abb2cc244cb61bb736c602146767 (patch) | |
tree | bfdd81d5424b2335e8543044dd726b0d30666663 /net/socket/ssl_client_socket_nss.cc | |
parent | 5d8054efc1e1f26ea806e46869df5e0a84e41a4c (diff) | |
download | chromium_src-d100e44f64d4abb2cc244cb61bb736c602146767.zip chromium_src-d100e44f64d4abb2cc244cb61bb736c602146767.tar.gz chromium_src-d100e44f64d4abb2cc244cb61bb736c602146767.tar.bz2 |
More net/ method ordering.
BUG=68682
TEST=compiles
Review URL: http://codereview.chromium.org/6339012
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@72710 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/socket/ssl_client_socket_nss.cc')
-rw-r--r-- | net/socket/ssl_client_socket_nss.cc | 2239 |
1 files changed, 1119 insertions, 1120 deletions
diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc index d006d58..a71af2c 100644 --- a/net/socket/ssl_client_socket_nss.cc +++ b/net/socket/ssl_client_socket_nss.cc @@ -279,6 +279,162 @@ class PeerCertificateChain { 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 { + DNSVR_SUCCESS, // the cert is immediately acceptable. + DNSVR_FAILURE, // the cert is unconditionally rejected. + DNSVR_CONTINUE, // perform CA validation as usual. +}; + +// VerifyTXTRecords processes the RRDATA for a number of DNS TXT records and +// checks them against the given certificate. +// dnssec: if true then the TXT records are DNSSEC validated. In this case, +// DNSVR_SUCCESS may be returned. +// server_cert_nss: the certificate to validate +// rrdatas: the TXT records for the current domain. +DNSValidationResult VerifyTXTRecords( + bool dnssec, + CERTCertificate* server_cert_nss, + const std::vector<base::StringPiece>& rrdatas) { + bool found_well_formed_record = false; + bool matched_record = false; + + for (std::vector<base::StringPiece>::const_iterator + i = rrdatas.begin(); i != rrdatas.end(); ++i) { + std::map<std::string, std::string> m( + DNSSECChainVerifier::ParseTLSTXTRecord(*i)); + if (m.empty()) + continue; + + std::map<std::string, std::string>::const_iterator j; + j = m.find("v"); + if (j == m.end() || j->second != "tls1") + continue; + + j = m.find("ha"); + + HASH_HashType hash_algorithm; + unsigned hash_length; + if (j == m.end() || j->second == "sha1") { + hash_algorithm = HASH_AlgSHA1; + hash_length = SHA1_LENGTH; + } else if (j->second == "sha256") { + hash_algorithm = HASH_AlgSHA256; + hash_length = SHA256_LENGTH; + } else { + continue; + } + + j = m.find("h"); + if (j == m.end()) + continue; + + std::vector<uint8> given_hash; + if (!base::HexStringToBytes(j->second, &given_hash)) + continue; + + if (given_hash.size() != hash_length) + continue; + + uint8 calculated_hash[SHA256_LENGTH]; // SHA256 is the largest. + SECStatus rv; + + j = m.find("hr"); + if (j == m.end() || j->second == "pubkey") { + rv = HASH_HashBuf(hash_algorithm, calculated_hash, + server_cert_nss->derPublicKey.data, + server_cert_nss->derPublicKey.len); + } else if (j->second == "cert") { + rv = HASH_HashBuf(hash_algorithm, calculated_hash, + server_cert_nss->derCert.data, + server_cert_nss->derCert.len); + } else { + continue; + } + + if (rv != SECSuccess) + NOTREACHED(); + + found_well_formed_record = true; + + if (memcmp(calculated_hash, &given_hash[0], hash_length) == 0) { + matched_record = true; + if (dnssec) + return DNSVR_SUCCESS; + } + } + + if (found_well_formed_record && !matched_record) + return DNSVR_FAILURE; + + return DNSVR_CONTINUE; +} + +// CheckDNSSECChain tries to validate a DNSSEC chain embedded in +// |server_cert_nss_|. It returns true iff a chain is found that proves the +// value of a TXT record that contains a valid public key fingerprint. +DNSValidationResult CheckDNSSECChain( + const std::string& hostname, + CERTCertificate* server_cert_nss) { + if (!server_cert_nss) + return DNSVR_CONTINUE; + + // CERT_FindCertExtensionByOID isn't exported so we have to install an OID, + // get a tag for it and find the extension by using that tag. + static SECOidTag dnssec_chain_tag; + static bool dnssec_chain_tag_valid; + if (!dnssec_chain_tag_valid) { + // It's harmless if multiple threads enter this block concurrently. + static const uint8 kDNSSECChainOID[] = + // 1.3.6.1.4.1.11129.2.1.4 + // (iso.org.dod.internet.private.enterprises.google.googleSecurity. + // certificateExtensions.dnssecEmbeddedChain) + {0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x04}; + SECOidData oid_data; + memset(&oid_data, 0, sizeof(oid_data)); + oid_data.oid.data = const_cast<uint8*>(kDNSSECChainOID); + oid_data.oid.len = sizeof(kDNSSECChainOID); + oid_data.desc = "DNSSEC chain"; + oid_data.supportedExtension = SUPPORTED_CERT_EXTENSION; + dnssec_chain_tag = SECOID_AddEntry(&oid_data); + DCHECK_NE(SEC_OID_UNKNOWN, dnssec_chain_tag); + dnssec_chain_tag_valid = true; + } + + SECItem dnssec_embedded_chain; + SECStatus rv = CERT_FindCertExtension(server_cert_nss, + dnssec_chain_tag, &dnssec_embedded_chain); + if (rv != SECSuccess) + return DNSVR_CONTINUE; + + base::StringPiece chain( + reinterpret_cast<char*>(dnssec_embedded_chain.data), + dnssec_embedded_chain.len); + std::string dns_hostname; + if (!DNSDomainFromDot(hostname, &dns_hostname)) + return DNSVR_CONTINUE; + DNSSECChainVerifier verifier(dns_hostname, chain); + DNSSECChainVerifier::Error err = verifier.Verify(); + if (err != DNSSECChainVerifier::OK) { + LOG(ERROR) << "DNSSEC chain verification failed: " << err; + return DNSVR_CONTINUE; + } + + if (verifier.rrtype() != kDNS_TXT) + return DNSVR_CONTINUE; + + DNSValidationResult r = VerifyTXTRecords( + true /* DNSSEC verified */, server_cert_nss, verifier.rrdatas()); + SECITEM_FreeItem(&dnssec_embedded_chain, PR_FALSE); + return r; +} + } // namespace SSLClientSocketNSS::SSLClientSocketNSS(ClientSocketHandle* transport_socket, @@ -333,156 +489,94 @@ SSLClientSocketNSS::~SSLClientSocketNSS() { LeaveFunction(""); } -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) - // We must call EnsureOCSPInit() here, on the IO thread, to get the IO loop - // by MessageLoopForIO::current(). - // X509Certificate::Verify() runs on a worker thread of CertVerifier. - EnsureOCSPInit(); -#endif - - LeaveFunction(""); - return OK; +// static +void SSLClientSocketNSS::ClearSessionCache() { + SSL_ClearSessionCache(); } -// SaveSnapStartInfo extracts the information needed to perform a Snap Start -// with this server in the future (if any) and tells |ssl_host_info_| to -// preserve it. -void SSLClientSocketNSS::SaveSnapStartInfo() { - if (!ssl_host_info_.get()) - return; - - // If the SSLHostInfo hasn't managed to load from disk yet then we can't save - // anything. - if (ssl_host_info_->WaitForDataReady(NULL) != OK) - return; +void SSLClientSocketNSS::GetSSLInfo(SSLInfo* ssl_info) { + EnterFunction(""); + ssl_info->Reset(); - SECStatus rv; - SSLSnapStartResult snap_start_type; - rv = SSL_GetSnapStartResult(nss_fd_, &snap_start_type); - if (rv != SECSuccess) { - NOTREACHED(); - return; - } - net_log_.AddEvent(NetLog::TYPE_SSL_SNAP_START, - new NetLogIntegerParameter("type", snap_start_type)); - if (snap_start_type == SSL_SNAP_START_FULL || - snap_start_type == SSL_SNAP_START_RESUME) { - // If we did a successful Snap Start then our information was correct and - // there's no point saving it again. + if (!server_cert_) { + LOG(DFATAL) << "!server_cert_"; return; } - const unsigned char* hello_data; - unsigned hello_data_len; - rv = SSL_GetPredictedServerHelloData(nss_fd_, &hello_data, &hello_data_len); - if (rv != SECSuccess) { - NOTREACHED(); - return; - } - if (hello_data_len > std::numeric_limits<uint16>::max()) - return; - SSLHostInfo::State* state = ssl_host_info_->mutable_state(); + ssl_info->cert_status = server_cert_verify_result_->cert_status; + DCHECK(server_cert_ != NULL); + ssl_info->cert = server_cert_; + ssl_info->connection_status = ssl_connection_status_; - if (hello_data_len > 0) { - state->server_hello = - std::string(reinterpret_cast<const char *>(hello_data), hello_data_len); - state->npn_valid = true; - state->npn_status = GetNextProto(&state->npn_protocol); + 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 { - state->server_hello.clear(); - state->npn_valid = false; - } - - 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; - - state->certs.push_back(std::string( - reinterpret_cast<char*>(certs[i]->derCert.data), - certs[i]->derCert.len)); + ssl_info->security_bits = -1; + LOG(DFATAL) << "SSL_GetCipherSuiteInfo returned " << PR_GetError() + << " for cipherSuite " << cipher_suite; } - - ssl_host_info_->Persist(); + LeaveFunction(""); } -static void DestroyCertificates(CERTCertificate** certs, unsigned len) { - for (unsigned i = 0; i < len; i++) - CERT_DestroyCertificate(certs[i]); +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()); } -// LoadSnapStartInfo parses |info|, which contains data previously serialised -// by |SaveSnapStartInfo|, and sets the predicted certificates and ServerHello -// data on the NSS socket. Returns true on success. If this function returns -// false, the caller should try a normal TLS handshake. -bool SSLClientSocketNSS::LoadSnapStartInfo() { - const SSLHostInfo::State& state(ssl_host_info_->state()); - - if (state.server_hello.empty() || - state.certs.empty() || - !state.npn_valid) { - return false; +SSLClientSocket::NextProtoStatus +SSLClientSocketNSS::GetNextProto(std::string* proto) { +#if defined(SSL_NEXT_PROTO_NEGOTIATED) + if (!handshake_callback_called_) { + DCHECK(pseudo_connected_); + predicted_npn_proto_used_ = true; + *proto = predicted_npn_proto_; + return predicted_npn_status_; } - SECStatus rv; - rv = SSL_SetPredictedServerHelloData( - nss_fd_, - reinterpret_cast<const uint8*>(state.server_hello.data()), - state.server_hello.size()); - DCHECK_EQ(SECSuccess, rv); - - 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; - } + unsigned char buf[255]; + int state; + unsigned len; + SECStatus rv = SSL_GetNextProto(nss_fd_, &state, buf, &len, sizeof(buf)); + if (rv != SECSuccess) { + NOTREACHED() << "Error return from SSL_GetNextProto: " << rv; + proto->clear(); + return kNextProtoUnsupported; } - - rv = SSL_SetPredictedPeerCertificates(nss_fd_, certs.get(), certs_in.size()); - DestroyCertificates(&certs[0], certs_in.size()); - DCHECK_EQ(SECSuccess, rv); - - if (state.npn_valid) { - predicted_npn_status_ = state.npn_status; - predicted_npn_proto_ = state.npn_protocol; + // We don't check for truncation because sizeof(buf) is large enough to hold + // the maximum protocol size. + switch (state) { + case SSL_NEXT_PROTO_NO_SUPPORT: + proto->clear(); + return kNextProtoUnsupported; + case SSL_NEXT_PROTO_NEGOTIATED: + *proto = std::string(reinterpret_cast<char*>(buf), len); + return kNextProtoNegotiated; + case SSL_NEXT_PROTO_NO_OVERLAP: + *proto = std::string(reinterpret_cast<char*>(buf), len); + return kNextProtoNoOverlap; + default: + NOTREACHED() << "Unknown status from SSL_GetNextProto: " << state; + proto->clear(); + return kNextProtoUnsupported; } - - return true; -} - -bool SSLClientSocketNSS::IsNPNProtocolMispredicted() { - DCHECK(handshake_callback_called_); - if (!predicted_npn_proto_used_) - return false; - std::string npn_proto; - GetNextProto(&npn_proto); - return predicted_npn_proto_ != npn_proto; +#else + // No NPN support in the libssl that we are building with. + proto->clear(); + return kNextProtoUnsupported; +#endif } -void SSLClientSocketNSS::UncorkAfterTimeout() { - corked_ = false; - int nsent; - do { - nsent = BufferSend(); - } while (nsent > 0); +void SSLClientSocketNSS::UseDNSSEC(DNSSECProvider* provider) { + dnssec_provider_ = provider; } int SSLClientSocketNSS::Connect(CompletionCallback* callback) { @@ -542,6 +636,228 @@ int SSLClientSocketNSS::Connect(CompletionCallback* callback) { return rv > OK ? OK : rv; } +void SSLClientSocketNSS::Disconnect() { + EnterFunction(""); + + // TODO(wtc): Send SSL close_notify alert. + if (nss_fd_ != NULL) { + PR_Close(nss_fd_); + nss_fd_ = NULL; + } + + // Shut down anything that may call us back (through buffer_send_callback_, + // buffer_recv_callback, or handshake_io_callback_). + verifier_.reset(); + transport_->socket()->Disconnect(); + + // Reset object state + transport_send_busy_ = false; + transport_recv_busy_ = false; + user_connect_callback_ = NULL; + user_read_callback_ = NULL; + user_write_callback_ = NULL; + 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; + pseudo_connected_ = false; + eset_mitm_detected_ = false; + start_cert_verification_time_ = base::TimeTicks(); + predicted_cert_chain_correct_ = false; + peername_initialized_ = false; + nss_bufs_ = NULL; + client_certs_.clear(); + client_auth_cert_needed_ = false; + + LeaveFunction(""); +} + +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 = (pseudo_connected_ || completed_handshake_) && + transport_->socket()->IsConnected(); + LeaveFunction(""); + return ret; +} + +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 = (pseudo_connected_ || completed_handshake_) && + transport_->socket()->IsConnectedAndIdle(); + LeaveFunction(""); + return ret; +} + +int SSLClientSocketNSS::GetPeerAddress(AddressList* address) const { + return transport_->socket()->GetPeerAddress(address); +} + +const BoundNetLog& SSLClientSocketNSS::NetLog() const { + return net_log_; +} + +void SSLClientSocketNSS::SetSubresourceSpeculation() { + if (transport_.get() && transport_->socket()) { + transport_->socket()->SetSubresourceSpeculation(); + } else { + NOTREACHED(); + } +} + +void SSLClientSocketNSS::SetOmniboxSpeculation() { + if (transport_.get() && transport_->socket()) { + transport_->socket()->SetOmniboxSpeculation(); + } else { + NOTREACHED(); + } +} + +bool SSLClientSocketNSS::WasEverUsed() const { + if (transport_.get() && transport_->socket()) { + return transport_->socket()->WasEverUsed(); + } + NOTREACHED(); + return false; +} + +bool SSLClientSocketNSS::UsingTCPFastOpen() const { + if (transport_.get() && transport_->socket()) { + return transport_->socket()->UsingTCPFastOpen(); + } + NOTREACHED(); + return false; +} + +int SSLClientSocketNSS::Read(IOBuffer* buf, int buf_len, + CompletionCallback* callback) { + EnterFunction(buf_len); + DCHECK(!user_read_callback_); + DCHECK(!user_connect_callback_); + DCHECK(!user_read_buf_); + DCHECK(nss_bufs_); + + user_read_buf_ = buf; + user_read_buf_len_ = buf_len; + + if (!completed_handshake_) { + // In this case we have lied about being connected in order to merge the + // first Write into a Snap Start handshake. We'll leave the read hanging + // until the handshake has completed. + DCHECK(pseudo_connected_); + + user_read_callback_ = callback; + LeaveFunction(ERR_IO_PENDING); + return ERR_IO_PENDING; + } + + int rv = DoReadLoop(OK); + + if (rv == ERR_IO_PENDING) { + user_read_callback_ = callback; + } else { + user_read_buf_ = NULL; + user_read_buf_len_ = 0; + } + LeaveFunction(rv); + return rv; +} + +int SSLClientSocketNSS::Write(IOBuffer* buf, int buf_len, + CompletionCallback* callback) { + EnterFunction(buf_len); + if (!pseudo_connected_) { + DCHECK(completed_handshake_); + DCHECK(next_handshake_state_ == STATE_NONE); + DCHECK(!user_connect_callback_); + } + DCHECK(!user_write_callback_); + DCHECK(!user_write_buf_); + DCHECK(nss_bufs_); + + user_write_buf_ = buf; + user_write_buf_len_ = buf_len; + + if (next_handshake_state_ == STATE_SNAP_START_WAIT_FOR_WRITE) { + // We lied about being connected and we have been waiting for this write in + // order to merge it into the Snap Start handshake. We'll leave the write + // pending until the handshake completes. + DCHECK(pseudo_connected_); + int rv = DoHandshakeLoop(OK); + if (rv == ERR_IO_PENDING) { + user_write_callback_ = callback; + } else { + user_write_buf_ = NULL; + user_write_buf_len_ = 0; + } + if (rv != OK) + return rv; + } + + if (corked_) { + corked_ = false; + uncork_timer_.Reset(); + } + int rv = DoWriteLoop(OK); + + if (rv == ERR_IO_PENDING) { + user_write_callback_ = callback; + } else { + user_write_buf_ = NULL; + user_write_buf_len_ = 0; + } + LeaveFunction(rv); + return rv; +} + +bool SSLClientSocketNSS::SetReceiveBufferSize(int32 size) { + return transport_->socket()->SetReceiveBufferSize(size); +} + +bool SSLClientSocketNSS::SetSendBufferSize(int32 size) { + return transport_->socket()->SetSendBufferSize(size); +} + +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) + // We must call EnsureOCSPInit() here, on the IO thread, to get the IO loop + // by MessageLoopForIO::current(). + // X509Certificate::Verify() runs on a worker thread of CertVerifier. + EnsureOCSPInit(); +#endif + + LeaveFunction(""); + return OK; +} + int SSLClientSocketNSS::InitializeSSLOptions() { // Transport connected, now hook it up to nss // TODO(port): specify rx and tx buffer sizes separately @@ -764,214 +1080,6 @@ int SSLClientSocketNSS::InitializeSSLPeerName() { return OK; } -void SSLClientSocketNSS::Disconnect() { - EnterFunction(""); - - // TODO(wtc): Send SSL close_notify alert. - if (nss_fd_ != NULL) { - PR_Close(nss_fd_); - nss_fd_ = NULL; - } - - // Shut down anything that may call us back (through buffer_send_callback_, - // buffer_recv_callback, or handshake_io_callback_). - verifier_.reset(); - transport_->socket()->Disconnect(); - - // Reset object state - transport_send_busy_ = false; - transport_recv_busy_ = false; - user_connect_callback_ = NULL; - user_read_callback_ = NULL; - user_write_callback_ = NULL; - 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; - pseudo_connected_ = false; - eset_mitm_detected_ = false; - start_cert_verification_time_ = base::TimeTicks(); - predicted_cert_chain_correct_ = false; - peername_initialized_ = false; - nss_bufs_ = NULL; - client_certs_.clear(); - client_auth_cert_needed_ = false; - - LeaveFunction(""); -} - -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 = (pseudo_connected_ || completed_handshake_) && - transport_->socket()->IsConnected(); - LeaveFunction(""); - return ret; -} - -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 = (pseudo_connected_ || completed_handshake_) && - transport_->socket()->IsConnectedAndIdle(); - LeaveFunction(""); - return ret; -} - -int SSLClientSocketNSS::GetPeerAddress(AddressList* address) const { - return transport_->socket()->GetPeerAddress(address); -} - -const BoundNetLog& SSLClientSocketNSS::NetLog() const { - return net_log_; -} - -void SSLClientSocketNSS::SetSubresourceSpeculation() { - if (transport_.get() && transport_->socket()) { - transport_->socket()->SetSubresourceSpeculation(); - } else { - NOTREACHED(); - } -} - -void SSLClientSocketNSS::SetOmniboxSpeculation() { - if (transport_.get() && transport_->socket()) { - transport_->socket()->SetOmniboxSpeculation(); - } else { - NOTREACHED(); - } -} - -bool SSLClientSocketNSS::WasEverUsed() const { - if (transport_.get() && transport_->socket()) { - return transport_->socket()->WasEverUsed(); - } - NOTREACHED(); - return false; -} - -bool SSLClientSocketNSS::UsingTCPFastOpen() const { - if (transport_.get() && transport_->socket()) { - return transport_->socket()->UsingTCPFastOpen(); - } - NOTREACHED(); - return false; -} - -int SSLClientSocketNSS::Read(IOBuffer* buf, int buf_len, - CompletionCallback* callback) { - EnterFunction(buf_len); - DCHECK(!user_read_callback_); - DCHECK(!user_connect_callback_); - DCHECK(!user_read_buf_); - DCHECK(nss_bufs_); - - user_read_buf_ = buf; - user_read_buf_len_ = buf_len; - - if (!completed_handshake_) { - // In this case we have lied about being connected in order to merge the - // first Write into a Snap Start handshake. We'll leave the read hanging - // until the handshake has completed. - DCHECK(pseudo_connected_); - - user_read_callback_ = callback; - LeaveFunction(ERR_IO_PENDING); - return ERR_IO_PENDING; - } - - int rv = DoReadLoop(OK); - - if (rv == ERR_IO_PENDING) { - user_read_callback_ = callback; - } else { - user_read_buf_ = NULL; - user_read_buf_len_ = 0; - } - LeaveFunction(rv); - return rv; -} - -int SSLClientSocketNSS::Write(IOBuffer* buf, int buf_len, - CompletionCallback* callback) { - EnterFunction(buf_len); - if (!pseudo_connected_) { - DCHECK(completed_handshake_); - DCHECK(next_handshake_state_ == STATE_NONE); - DCHECK(!user_connect_callback_); - } - DCHECK(!user_write_callback_); - DCHECK(!user_write_buf_); - DCHECK(nss_bufs_); - - user_write_buf_ = buf; - user_write_buf_len_ = buf_len; - - if (next_handshake_state_ == STATE_SNAP_START_WAIT_FOR_WRITE) { - // We lied about being connected and we have been waiting for this write in - // order to merge it into the Snap Start handshake. We'll leave the write - // pending until the handshake completes. - DCHECK(pseudo_connected_); - int rv = DoHandshakeLoop(OK); - if (rv == ERR_IO_PENDING) { - user_write_callback_ = callback; - } else { - user_write_buf_ = NULL; - user_write_buf_len_ = 0; - } - if (rv != OK) - return rv; - } - - if (corked_) { - corked_ = false; - uncork_timer_.Reset(); - } - int rv = DoWriteLoop(OK); - - if (rv == ERR_IO_PENDING) { - user_write_callback_ = callback; - } else { - user_write_buf_ = NULL; - user_write_buf_len_ = 0; - } - LeaveFunction(rv); - return rv; -} - -bool SSLClientSocketNSS::SetReceiveBufferSize(int32 size) { - return transport_->socket()->SetReceiveBufferSize(size); -} - -bool SSLClientSocketNSS::SetSendBufferSize(int32 size) { - return transport_->socket()->SetSendBufferSize(size); -} - -// static -void SSLClientSocketNSS::ClearSessionCache() { - SSL_ClearSessionCache(); -} // Sets server_cert_ and server_cert_nss_ if not yet set. // Returns server_cert_. @@ -1051,91 +1159,6 @@ void SSLClientSocketNSS::UpdateConnectionStatus() { ssl_connection_status_ |= SSL_CONNECTION_SSL3_FALLBACK; } -void SSLClientSocketNSS::GetSSLInfo(SSLInfo* ssl_info) { - EnterFunction(""); - ssl_info->Reset(); - - if (!server_cert_) { - LOG(DFATAL) << "!server_cert_"; - return; - } - - ssl_info->cert_status = server_cert_verify_result_->cert_status; - DCHECK(server_cert_ != NULL); - ssl_info->cert = server_cert_; - ssl_info->connection_status = ssl_connection_status_; - - 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; - } - 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 = client_certs_; - LeaveFunction(cert_request_info->client_certs.size()); -} - -SSLClientSocket::NextProtoStatus -SSLClientSocketNSS::GetNextProto(std::string* proto) { -#if defined(SSL_NEXT_PROTO_NEGOTIATED) - if (!handshake_callback_called_) { - DCHECK(pseudo_connected_); - predicted_npn_proto_used_ = true; - *proto = predicted_npn_proto_; - return predicted_npn_status_; - } - - unsigned char buf[255]; - int state; - unsigned len; - SECStatus rv = SSL_GetNextProto(nss_fd_, &state, buf, &len, sizeof(buf)); - if (rv != SECSuccess) { - NOTREACHED() << "Error return from SSL_GetNextProto: " << rv; - proto->clear(); - return kNextProtoUnsupported; - } - // We don't check for truncation because sizeof(buf) is large enough to hold - // the maximum protocol size. - switch (state) { - case SSL_NEXT_PROTO_NO_SUPPORT: - proto->clear(); - return kNextProtoUnsupported; - case SSL_NEXT_PROTO_NEGOTIATED: - *proto = std::string(reinterpret_cast<char*>(buf), len); - return kNextProtoNegotiated; - case SSL_NEXT_PROTO_NO_OVERLAP: - *proto = std::string(reinterpret_cast<char*>(buf), len); - return kNextProtoNoOverlap; - default: - NOTREACHED() << "Unknown status from SSL_GetNextProto: " << state; - proto->clear(); - return kNextProtoUnsupported; - } -#else - // No NPN support in the libssl that we are building with. - proto->clear(); - return kNextProtoUnsupported; -#endif -} - -void SSLClientSocketNSS::UseDNSSEC(DNSSECProvider* provider) { - dnssec_provider_ = provider; -} - void SSLClientSocketNSS::DoReadCallback(int rv) { EnterFunction(rv); DCHECK(rv != ERR_IO_PENDING); @@ -1250,109 +1273,6 @@ void SSLClientSocketNSS::OnRecvComplete(int result) { LeaveFunction(""); } -// Do network I/O between the given buffer and the given 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 nsent = BufferSend(); - int nreceived = BufferRecv(); - network_moved = (nsent > 0 || nreceived >= 0); - } - LeaveFunction(network_moved); - return network_moved; -} - -// Return 0 for EOF, -// > 0 for bytes transferred immediately, -// < 0 for error (or the non-error ERR_IO_PENDING). -int SSLClientSocketNSS::BufferSend(void) { - if (transport_send_busy_) - return ERR_IO_PENDING; - - 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; - - if (corked_ && len < kRecvBufferSize / 2) - return 0; - - 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, - &buffer_send_callback_); - if (rv == ERR_IO_PENDING) { - transport_send_busy_ = true; - } else { - memio_PutWriteResult(nss_bufs_, MapErrorToNSS(rv)); - } - } - - LeaveFunction(rv); - return rv; -} - -void SSLClientSocketNSS::BufferSendComplete(int result) { - EnterFunction(result); - - // In the case of TCP FastOpen, connect is now finished. - if (!peername_initialized_ && UsingTCPFastOpen()) - InitializeSSLPeerName(); - - memio_PutWriteResult(nss_bufs_, MapErrorToNSS(result)); - transport_send_busy_ = false; - OnSendComplete(result); - LeaveFunction(""); -} - - -int SSLClientSocketNSS::BufferRecv(void) { - if (transport_recv_busy_) return ERR_IO_PENDING; - - 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, &buffer_recv_callback_); - if (rv == ERR_IO_PENDING) { - transport_recv_busy_ = true; - } else { - if (rv > 0) - memcpy(buf, recv_buffer_->data(), rv); - memio_PutReadResult(nss_bufs_, MapErrorToNSS(rv)); - recv_buffer_ = NULL; - } - } - 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); - } - recv_buffer_ = NULL; - memio_PutReadResult(nss_bufs_, MapErrorToNSS(result)); - transport_recv_busy_ = false; - OnRecvComplete(result); - LeaveFunction(""); -} - int SSLClientSocketNSS::DoHandshakeLoop(int last_io_result) { EnterFunction(last_io_result); bool network_moved; @@ -1459,447 +1379,6 @@ int SSLClientSocketNSS::DoWriteLoop(int result) { return rv; } -// 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 - // In the event that we are False Starting this connection, we wish to send - // out the Finished message and first application data record in the same - // packet. This prevents non-determinism when talking to False Start - // intolerant servers which, otherwise, might see the two messages in - // different reads or not, depending on network conditions. - PRBool false_start = 0; - SECStatus rv = SSL_OptionGet(socket, SSL_ENABLE_FALSE_START, &false_start); - DCHECK_EQ(SECSuccess, rv); - - if (false_start) { - SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); - - // ESET anti-virus is capable of intercepting HTTPS connections on Windows. - // However, it is False Start intolerant and causes the connections to hang - // forever. We detect ESET by the issuer of the leaf certificate and set a - // flag to return a specific error, giving the user instructions for - // reconfiguring ESET. - CERTCertificate* cert = SSL_PeerCertificate(that->nss_fd_); - if (cert) { - char* common_name = CERT_GetCommonName(&cert->issuer); - if (common_name) { - if (strcmp(common_name, "ESET_RootSslCert") == 0) - that->eset_mitm_detected_ = true; - if (strcmp(common_name, - "ContentWatch Root Certificate Authority") == 0) { - // This is NetNanny. NetNanny are updating their product so we - // silently disable False Start for now. - rv = SSL_OptionSet(socket, SSL_ENABLE_FALSE_START, PR_FALSE); - DCHECK_EQ(SECSuccess, rv); - false_start = 0; - } - PORT_Free(common_name); - } - CERT_DestroyCertificate(cert); - } - - if (false_start && !that->handshake_callback_called_) { - that->corked_ = true; - that->uncork_timer_.Start( - base::TimeDelta::FromMilliseconds(kCorkTimeoutMs), - that, &SSLClientSocketNSS::UncorkAfterTimeout); - } - } -#endif - - // Tell NSS to not verify the certificate. - return SECSuccess; -} - -#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) { - SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); - - 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 (VLOG_IS_ON(1)) { - do { - DWORD size_needed = 0; - BOOL got_info = CertGetCertificateContextProperty( - cert_context, CERT_KEY_PROV_INFO_PROP_ID, NULL, &size_needed); - if (!got_info) { - VLOG(1) << "Failed to get key prov info size " << GetLastError(); - break; - } - std::vector<BYTE> raw_info(size_needed); - got_info = CertGetCertificateContextProperty( - cert_context, CERT_KEY_PROV_INFO_PROP_ID, &raw_info[0], - &size_needed); - if (!got_info) { - VLOG(1) << "Failed to get key prov info " << GetLastError(); - break; - } - PCRYPT_KEY_PROV_INFO info = - reinterpret_cast<PCRYPT_KEY_PROV_INFO>(&raw_info[0]); - VLOG(1) << "Container Name: " << info->pwszContainerName - << "\nProvider Name: " << info->pwszProvName - << "\nProvider Type: " << info->dwProvType - << "\nFlags: " << info->dwFlags - << "\nProvider Param Count: " << info->cProvParam - << "\nKey Specifier: " << info->dwKeySpec; - } while (false); - - do { - DWORD size_needed = 0; - BOOL got_identifier = CertGetCertificateContextProperty( - cert_context, CERT_KEY_IDENTIFIER_PROP_ID, NULL, &size_needed); - if (!got_identifier) { - VLOG(1) << "Failed to get key identifier size " - << GetLastError(); - break; - } - std::vector<BYTE> raw_id(size_needed); - got_identifier = CertGetCertificateContextProperty( - cert_context, CERT_KEY_IDENTIFIER_PROP_ID, &raw_id[0], - &size_needed); - if (!got_identifier) { - VLOG(1) << "Failed to get key identifier " << GetLastError(); - break; - } - VLOG(1) << "Key Identifier: " << base::HexEncode(&raw_id[0], - size_needed); - } while (false); - } - HCRYPTPROV provider = NULL; - DWORD key_spec = AT_KEYEXCHANGE; - BOOL must_free = FALSE; - BOOL acquired_key = CryptAcquireCertificatePrivateKey( - cert_context, - CRYPT_ACQUIRE_CACHE_FLAG | CRYPT_ACQUIRE_COMPARE_KEY_FLAG, - NULL, &provider, &key_spec, &must_free); - if (acquired_key && provider) { - DCHECK_NE(key_spec, CERT_NCRYPT_KEY_SPEC); - - // The certificate cache may have been updated/used, in which case, - // duplicate the existing handle, since NSS will free it when no - // longer in use. - if (!must_free) - CryptContextAddRef(provider, NULL, 0); - - SECItem der_cert; - der_cert.type = siDERCertBuffer; - der_cert.data = cert_context->pbCertEncoded; - der_cert.len = cert_context->cbCertEncoded; - - // TODO(rsleevi): Error checking for NSS allocation errors. - *result_certs = CERT_NewCertList(); - CERTCertDBHandle* db_handle = CERT_GetDefaultCertDB(); - CERTCertificate* user_cert = CERT_NewTempCertificate( - db_handle, &der_cert, NULL, PR_FALSE, PR_TRUE); - CERT_AddCertToListTail(*result_certs, user_cert); - - // 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; - - CERTCertificate* intermediate = CERT_NewTempCertificate( - db_handle, &der_cert, NULL, PR_FALSE, PR_TRUE); - CERT_AddCertToListTail(*result_certs, intermediate); - } - // TODO(wtc): |key_spec| should be passed along with |provider|. - *result_private_key = reinterpret_cast<void*>(provider); - return SECSuccess; - } - LOG(WARNING) << "Client cert found without private key"; - } - // Send no client certificate. - return SECFailure; - } - - that->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) { - LOG(ERROR) << "Could not open the \"MY\" system certificate store: " - << GetLastError(); - 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; - - for (;;) { - // Find a certificate chain. - chain_context = CertFindChainInStore(my_cert_store, - X509_ASN_ENCODING, - 0, - 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; - // Copy it to our own certificate store, so that we can close the "MY" - // certificate store before returning from this function. - PCCERT_CONTEXT cert_context2; - BOOL ok = CertAddCertificateContextToStore(X509Certificate::cert_store(), - cert_context, - CERT_STORE_ADD_USE_EXISTING, - &cert_context2); - if (!ok) { - NOTREACHED(); - continue; - } - - // Copy the rest of the chain to our own store as well. Copying the chain - // stops gracefully if an error is encountered, with the partial chain - // being used as the intermediates, rather than failing to consider the - // client certificate. - net::X509Certificate::OSCertHandles intermediates; - for (DWORD i = 1; i < chain_context->rgpChain[0]->cElement; i++) { - PCCERT_CONTEXT intermediate_copy; - ok = CertAddCertificateContextToStore(X509Certificate::cert_store(), - 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, X509Certificate::SOURCE_LONE_CERT_IMPORT, - 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); - } - } - - BOOL ok = CertCloseStore(my_cert_store, CERT_CLOSE_STORE_CHECK_FLAG); - DCHECK(ok); - - // 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); - - if (chain && identity && os_error == noErr) { - // TODO(rsleevi): Error checking for NSS allocation errors. - *result_certs = CERT_NewCertList(); - *result_private_key = reinterpret_cast<void*>(private_key); - - 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; - - 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); - CERT_AddCertToListTail(*result_certs, nss_cert); - } - } - if (os_error == noErr) { - CFRelease(chain); - return SECSuccess; - } - LOG(WARNING) << "Client cert found, but could not be used: " - << os_error; - 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. - return SECFailure; - } - - that->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(that->host_and_port_.host(), - valid_issuers, - &that->client_certs_); - - // 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 -} - -#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->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; - return SECSuccess; - } - LOG(WARNING) << "Client cert found without private key"; - } - // Send no client certificate. - return SECFailure; - } - - // 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, X509Certificate::SOURCE_LONE_CERT_IMPORT, - net::X509Certificate::OSCertHandles()); - that->client_certs_.push_back(x509_cert); - } - CERT_DestroyCertList(client_certs); - } - - // 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 -// 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); - - that->handshake_callback_called_ = true; - - that->UpdateServerCert(); - that->UpdateConnectionStatus(); -} - int SSLClientSocketNSS::DoSnapStartLoadInfo() { EnterFunction(""); int rv = ssl_host_info_->WaitForDataReady(&handshake_io_callback_); @@ -2083,158 +1562,6 @@ int SSLClientSocketNSS::DoHandshake() { return net_error; } -// DNSValidationResult enumerates the possible outcomes from processing a -// set of DNS records. -enum DNSValidationResult { - DNSVR_SUCCESS, // the cert is immediately acceptable. - DNSVR_FAILURE, // the cert is unconditionally rejected. - DNSVR_CONTINUE, // perform CA validation as usual. -}; - -// VerifyTXTRecords processes the RRDATA for a number of DNS TXT records and -// checks them against the given certificate. -// dnssec: if true then the TXT records are DNSSEC validated. In this case, -// DNSVR_SUCCESS may be returned. -// server_cert_nss: the certificate to validate -// rrdatas: the TXT records for the current domain. -static DNSValidationResult VerifyTXTRecords( - bool dnssec, - CERTCertificate* server_cert_nss, - const std::vector<base::StringPiece>& rrdatas) { - bool found_well_formed_record = false; - bool matched_record = false; - - for (std::vector<base::StringPiece>::const_iterator - i = rrdatas.begin(); i != rrdatas.end(); ++i) { - std::map<std::string, std::string> m( - DNSSECChainVerifier::ParseTLSTXTRecord(*i)); - if (m.empty()) - continue; - - std::map<std::string, std::string>::const_iterator j; - j = m.find("v"); - if (j == m.end() || j->second != "tls1") - continue; - - j = m.find("ha"); - - HASH_HashType hash_algorithm; - unsigned hash_length; - if (j == m.end() || j->second == "sha1") { - hash_algorithm = HASH_AlgSHA1; - hash_length = SHA1_LENGTH; - } else if (j->second == "sha256") { - hash_algorithm = HASH_AlgSHA256; - hash_length = SHA256_LENGTH; - } else { - continue; - } - - j = m.find("h"); - if (j == m.end()) - continue; - - std::vector<uint8> given_hash; - if (!base::HexStringToBytes(j->second, &given_hash)) - continue; - - if (given_hash.size() != hash_length) - continue; - - uint8 calculated_hash[SHA256_LENGTH]; // SHA256 is the largest. - SECStatus rv; - - j = m.find("hr"); - if (j == m.end() || j->second == "pubkey") { - rv = HASH_HashBuf(hash_algorithm, calculated_hash, - server_cert_nss->derPublicKey.data, - server_cert_nss->derPublicKey.len); - } else if (j->second == "cert") { - rv = HASH_HashBuf(hash_algorithm, calculated_hash, - server_cert_nss->derCert.data, - server_cert_nss->derCert.len); - } else { - continue; - } - - if (rv != SECSuccess) - NOTREACHED(); - - found_well_formed_record = true; - - if (memcmp(calculated_hash, &given_hash[0], hash_length) == 0) { - matched_record = true; - if (dnssec) - return DNSVR_SUCCESS; - } - } - - if (found_well_formed_record && !matched_record) - return DNSVR_FAILURE; - - return DNSVR_CONTINUE; -} - - -// CheckDNSSECChain tries to validate a DNSSEC chain embedded in -// |server_cert_nss_|. It returns true iff a chain is found that proves the -// value of a TXT record that contains a valid public key fingerprint. -static DNSValidationResult CheckDNSSECChain( - const std::string& hostname, - CERTCertificate* server_cert_nss) { - if (!server_cert_nss) - return DNSVR_CONTINUE; - - // CERT_FindCertExtensionByOID isn't exported so we have to install an OID, - // get a tag for it and find the extension by using that tag. - static SECOidTag dnssec_chain_tag; - static bool dnssec_chain_tag_valid; - if (!dnssec_chain_tag_valid) { - // It's harmless if multiple threads enter this block concurrently. - static const uint8 kDNSSECChainOID[] = - // 1.3.6.1.4.1.11129.2.1.4 - // (iso.org.dod.internet.private.enterprises.google.googleSecurity. - // certificateExtensions.dnssecEmbeddedChain) - {0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x04}; - SECOidData oid_data; - memset(&oid_data, 0, sizeof(oid_data)); - oid_data.oid.data = const_cast<uint8*>(kDNSSECChainOID); - oid_data.oid.len = sizeof(kDNSSECChainOID); - oid_data.desc = "DNSSEC chain"; - oid_data.supportedExtension = SUPPORTED_CERT_EXTENSION; - dnssec_chain_tag = SECOID_AddEntry(&oid_data); - DCHECK_NE(SEC_OID_UNKNOWN, dnssec_chain_tag); - dnssec_chain_tag_valid = true; - } - - SECItem dnssec_embedded_chain; - SECStatus rv = CERT_FindCertExtension(server_cert_nss, - dnssec_chain_tag, &dnssec_embedded_chain); - if (rv != SECSuccess) - return DNSVR_CONTINUE; - - base::StringPiece chain( - reinterpret_cast<char*>(dnssec_embedded_chain.data), - dnssec_embedded_chain.len); - std::string dns_hostname; - if (!DNSDomainFromDot(hostname, &dns_hostname)) - return DNSVR_CONTINUE; - DNSSECChainVerifier verifier(dns_hostname, chain); - DNSSECChainVerifier::Error err = verifier.Verify(); - if (err != DNSSECChainVerifier::OK) { - LOG(ERROR) << "DNSSEC chain verification failed: " << err; - return DNSVR_CONTINUE; - } - - if (verifier.rrtype() != kDNS_TXT) - return DNSVR_CONTINUE; - - DNSValidationResult r = VerifyTXTRecords( - true /* DNSSEC verified */, server_cert_nss, verifier.rrdatas()); - SECITEM_FreeItem(&dnssec_embedded_chain, PR_FALSE); - return r; -} - int SSLClientSocketNSS::DoVerifyDNSSEC(int result) { if (ssl_config_.dns_cert_provenance_checking_enabled && dns_cert_checker_) { @@ -2515,4 +1842,676 @@ void SSLClientSocketNSS::LogConnectionTypeMetrics() const { }; } +// SaveSnapStartInfo extracts the information needed to perform a Snap Start +// with this server in the future (if any) and tells |ssl_host_info_| to +// preserve it. +void SSLClientSocketNSS::SaveSnapStartInfo() { + if (!ssl_host_info_.get()) + return; + + // If the SSLHostInfo hasn't managed to load from disk yet then we can't save + // anything. + if (ssl_host_info_->WaitForDataReady(NULL) != OK) + return; + + SECStatus rv; + SSLSnapStartResult snap_start_type; + rv = SSL_GetSnapStartResult(nss_fd_, &snap_start_type); + if (rv != SECSuccess) { + NOTREACHED(); + return; + } + net_log_.AddEvent(NetLog::TYPE_SSL_SNAP_START, + new NetLogIntegerParameter("type", snap_start_type)); + if (snap_start_type == SSL_SNAP_START_FULL || + snap_start_type == SSL_SNAP_START_RESUME) { + // If we did a successful Snap Start then our information was correct and + // there's no point saving it again. + return; + } + + const unsigned char* hello_data; + unsigned hello_data_len; + rv = SSL_GetPredictedServerHelloData(nss_fd_, &hello_data, &hello_data_len); + if (rv != SECSuccess) { + NOTREACHED(); + return; + } + if (hello_data_len > std::numeric_limits<uint16>::max()) + return; + SSLHostInfo::State* state = ssl_host_info_->mutable_state(); + + if (hello_data_len > 0) { + state->server_hello = + std::string(reinterpret_cast<const char *>(hello_data), hello_data_len); + state->npn_valid = true; + state->npn_status = GetNextProto(&state->npn_protocol); + } else { + state->server_hello.clear(); + state->npn_valid = false; + } + + 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; + + state->certs.push_back(std::string( + reinterpret_cast<char*>(certs[i]->derCert.data), + certs[i]->derCert.len)); + } + + ssl_host_info_->Persist(); +} + +// LoadSnapStartInfo parses |info|, which contains data previously serialised +// by |SaveSnapStartInfo|, and sets the predicted certificates and ServerHello +// data on the NSS socket. Returns true on success. If this function returns +// false, the caller should try a normal TLS handshake. +bool SSLClientSocketNSS::LoadSnapStartInfo() { + const SSLHostInfo::State& state(ssl_host_info_->state()); + + if (state.server_hello.empty() || + state.certs.empty() || + !state.npn_valid) { + return false; + } + + SECStatus rv; + rv = SSL_SetPredictedServerHelloData( + nss_fd_, + reinterpret_cast<const uint8*>(state.server_hello.data()), + state.server_hello.size()); + DCHECK_EQ(SECSuccess, rv); + + 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; + } + } + + rv = SSL_SetPredictedPeerCertificates(nss_fd_, certs.get(), certs_in.size()); + DestroyCertificates(&certs[0], certs_in.size()); + DCHECK_EQ(SECSuccess, rv); + + if (state.npn_valid) { + predicted_npn_status_ = state.npn_status; + predicted_npn_proto_ = state.npn_protocol; + } + + return true; +} + +bool SSLClientSocketNSS::IsNPNProtocolMispredicted() { + DCHECK(handshake_callback_called_); + if (!predicted_npn_proto_used_) + return false; + std::string npn_proto; + GetNextProto(&npn_proto); + return predicted_npn_proto_ != npn_proto; +} + +void SSLClientSocketNSS::UncorkAfterTimeout() { + corked_ = false; + int nsent; + do { + nsent = BufferSend(); + } while (nsent > 0); +} + +// Do network I/O between the given buffer and the given 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 nsent = BufferSend(); + int nreceived = BufferRecv(); + network_moved = (nsent > 0 || nreceived >= 0); + } + LeaveFunction(network_moved); + return network_moved; +} + +// Return 0 for EOF, +// > 0 for bytes transferred immediately, +// < 0 for error (or the non-error ERR_IO_PENDING). +int SSLClientSocketNSS::BufferSend(void) { + if (transport_send_busy_) + return ERR_IO_PENDING; + + 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; + + if (corked_ && len < kRecvBufferSize / 2) + return 0; + + 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, + &buffer_send_callback_); + if (rv == ERR_IO_PENDING) { + transport_send_busy_ = true; + } else { + memio_PutWriteResult(nss_bufs_, MapErrorToNSS(rv)); + } + } + + LeaveFunction(rv); + return rv; +} + +void SSLClientSocketNSS::BufferSendComplete(int result) { + EnterFunction(result); + + // In the case of TCP FastOpen, connect is now finished. + if (!peername_initialized_ && UsingTCPFastOpen()) + InitializeSSLPeerName(); + + memio_PutWriteResult(nss_bufs_, MapErrorToNSS(result)); + transport_send_busy_ = false; + OnSendComplete(result); + LeaveFunction(""); +} + +int SSLClientSocketNSS::BufferRecv(void) { + if (transport_recv_busy_) return ERR_IO_PENDING; + + 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, &buffer_recv_callback_); + if (rv == ERR_IO_PENDING) { + transport_recv_busy_ = true; + } else { + if (rv > 0) + memcpy(buf, recv_buffer_->data(), rv); + memio_PutReadResult(nss_bufs_, MapErrorToNSS(rv)); + recv_buffer_ = NULL; + } + } + 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); + } + recv_buffer_ = NULL; + memio_PutReadResult(nss_bufs_, MapErrorToNSS(result)); + transport_recv_busy_ = false; + OnRecvComplete(result); + LeaveFunction(""); +} + +// 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 + // In the event that we are False Starting this connection, we wish to send + // out the Finished message and first application data record in the same + // packet. This prevents non-determinism when talking to False Start + // intolerant servers which, otherwise, might see the two messages in + // different reads or not, depending on network conditions. + PRBool false_start = 0; + SECStatus rv = SSL_OptionGet(socket, SSL_ENABLE_FALSE_START, &false_start); + DCHECK_EQ(SECSuccess, rv); + + if (false_start) { + SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); + + // ESET anti-virus is capable of intercepting HTTPS connections on Windows. + // However, it is False Start intolerant and causes the connections to hang + // forever. We detect ESET by the issuer of the leaf certificate and set a + // flag to return a specific error, giving the user instructions for + // reconfiguring ESET. + CERTCertificate* cert = SSL_PeerCertificate(that->nss_fd_); + if (cert) { + char* common_name = CERT_GetCommonName(&cert->issuer); + if (common_name) { + if (strcmp(common_name, "ESET_RootSslCert") == 0) + that->eset_mitm_detected_ = true; + if (strcmp(common_name, + "ContentWatch Root Certificate Authority") == 0) { + // This is NetNanny. NetNanny are updating their product so we + // silently disable False Start for now. + rv = SSL_OptionSet(socket, SSL_ENABLE_FALSE_START, PR_FALSE); + DCHECK_EQ(SECSuccess, rv); + false_start = 0; + } + PORT_Free(common_name); + } + CERT_DestroyCertificate(cert); + } + + if (false_start && !that->handshake_callback_called_) { + that->corked_ = true; + that->uncork_timer_.Start( + base::TimeDelta::FromMilliseconds(kCorkTimeoutMs), + that, &SSLClientSocketNSS::UncorkAfterTimeout); + } + } +#endif + + // Tell NSS to not verify the certificate. + return SECSuccess; +} + +#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) { + SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); + + 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 (VLOG_IS_ON(1)) { + do { + DWORD size_needed = 0; + BOOL got_info = CertGetCertificateContextProperty( + cert_context, CERT_KEY_PROV_INFO_PROP_ID, NULL, &size_needed); + if (!got_info) { + VLOG(1) << "Failed to get key prov info size " << GetLastError(); + break; + } + std::vector<BYTE> raw_info(size_needed); + got_info = CertGetCertificateContextProperty( + cert_context, CERT_KEY_PROV_INFO_PROP_ID, &raw_info[0], + &size_needed); + if (!got_info) { + VLOG(1) << "Failed to get key prov info " << GetLastError(); + break; + } + PCRYPT_KEY_PROV_INFO info = + reinterpret_cast<PCRYPT_KEY_PROV_INFO>(&raw_info[0]); + VLOG(1) << "Container Name: " << info->pwszContainerName + << "\nProvider Name: " << info->pwszProvName + << "\nProvider Type: " << info->dwProvType + << "\nFlags: " << info->dwFlags + << "\nProvider Param Count: " << info->cProvParam + << "\nKey Specifier: " << info->dwKeySpec; + } while (false); + + do { + DWORD size_needed = 0; + BOOL got_identifier = CertGetCertificateContextProperty( + cert_context, CERT_KEY_IDENTIFIER_PROP_ID, NULL, &size_needed); + if (!got_identifier) { + VLOG(1) << "Failed to get key identifier size " + << GetLastError(); + break; + } + std::vector<BYTE> raw_id(size_needed); + got_identifier = CertGetCertificateContextProperty( + cert_context, CERT_KEY_IDENTIFIER_PROP_ID, &raw_id[0], + &size_needed); + if (!got_identifier) { + VLOG(1) << "Failed to get key identifier " << GetLastError(); + break; + } + VLOG(1) << "Key Identifier: " << base::HexEncode(&raw_id[0], + size_needed); + } while (false); + } + HCRYPTPROV provider = NULL; + DWORD key_spec = AT_KEYEXCHANGE; + BOOL must_free = FALSE; + BOOL acquired_key = CryptAcquireCertificatePrivateKey( + cert_context, + CRYPT_ACQUIRE_CACHE_FLAG | CRYPT_ACQUIRE_COMPARE_KEY_FLAG, + NULL, &provider, &key_spec, &must_free); + if (acquired_key && provider) { + DCHECK_NE(key_spec, CERT_NCRYPT_KEY_SPEC); + + // The certificate cache may have been updated/used, in which case, + // duplicate the existing handle, since NSS will free it when no + // longer in use. + if (!must_free) + CryptContextAddRef(provider, NULL, 0); + + SECItem der_cert; + der_cert.type = siDERCertBuffer; + der_cert.data = cert_context->pbCertEncoded; + der_cert.len = cert_context->cbCertEncoded; + + // TODO(rsleevi): Error checking for NSS allocation errors. + *result_certs = CERT_NewCertList(); + CERTCertDBHandle* db_handle = CERT_GetDefaultCertDB(); + CERTCertificate* user_cert = CERT_NewTempCertificate( + db_handle, &der_cert, NULL, PR_FALSE, PR_TRUE); + CERT_AddCertToListTail(*result_certs, user_cert); + + // 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; + + CERTCertificate* intermediate = CERT_NewTempCertificate( + db_handle, &der_cert, NULL, PR_FALSE, PR_TRUE); + CERT_AddCertToListTail(*result_certs, intermediate); + } + // TODO(wtc): |key_spec| should be passed along with |provider|. + *result_private_key = reinterpret_cast<void*>(provider); + return SECSuccess; + } + LOG(WARNING) << "Client cert found without private key"; + } + // Send no client certificate. + return SECFailure; + } + + that->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) { + LOG(ERROR) << "Could not open the \"MY\" system certificate store: " + << GetLastError(); + 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; + + for (;;) { + // Find a certificate chain. + chain_context = CertFindChainInStore(my_cert_store, + X509_ASN_ENCODING, + 0, + 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; + // Copy it to our own certificate store, so that we can close the "MY" + // certificate store before returning from this function. + PCCERT_CONTEXT cert_context2; + BOOL ok = CertAddCertificateContextToStore(X509Certificate::cert_store(), + cert_context, + CERT_STORE_ADD_USE_EXISTING, + &cert_context2); + if (!ok) { + NOTREACHED(); + continue; + } + + // Copy the rest of the chain to our own store as well. Copying the chain + // stops gracefully if an error is encountered, with the partial chain + // being used as the intermediates, rather than failing to consider the + // client certificate. + net::X509Certificate::OSCertHandles intermediates; + for (DWORD i = 1; i < chain_context->rgpChain[0]->cElement; i++) { + PCCERT_CONTEXT intermediate_copy; + ok = CertAddCertificateContextToStore(X509Certificate::cert_store(), + 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, X509Certificate::SOURCE_LONE_CERT_IMPORT, + 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); + } + } + + BOOL ok = CertCloseStore(my_cert_store, CERT_CLOSE_STORE_CHECK_FLAG); + DCHECK(ok); + + // 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); + + if (chain && identity && os_error == noErr) { + // TODO(rsleevi): Error checking for NSS allocation errors. + *result_certs = CERT_NewCertList(); + *result_private_key = reinterpret_cast<void*>(private_key); + + 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; + + 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); + CERT_AddCertToListTail(*result_certs, nss_cert); + } + } + if (os_error == noErr) { + CFRelease(chain); + return SECSuccess; + } + LOG(WARNING) << "Client cert found, but could not be used: " + << os_error; + 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. + return SECFailure; + } + + that->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(that->host_and_port_.host(), + valid_issuers, + &that->client_certs_); + + // 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 +} + +#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->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; + return SECSuccess; + } + LOG(WARNING) << "Client cert found without private key"; + } + // Send no client certificate. + return SECFailure; + } + + // 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, X509Certificate::SOURCE_LONE_CERT_IMPORT, + net::X509Certificate::OSCertHandles()); + that->client_certs_.push_back(x509_cert); + } + CERT_DestroyCertList(client_certs); + } + + // 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 +// 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); + + that->handshake_callback_called_ = true; + + that->UpdateServerCert(); + that->UpdateConnectionStatus(); +} + } // namespace net |