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

Unified Diff: net/socket/ssl_client_socket_nss.cc

Issue 6339012: More net/ method ordering. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: More done while waiting for previous patch to clear Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « net/socket/ssl_client_socket_nss.h ('k') | net/socket/ssl_server_socket_nss.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: net/socket/ssl_client_socket_nss.cc
diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc
index d006d58ad8296a464ccc49f7154d43cf4bbfde1a..a71af2caad33b336a4ea688091cb8ebfd4b0a499 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,85 +636,307 @@ int SSLClientSocketNSS::Connect(CompletionCallback* callback) {
return rv > OK ? OK : rv;
}
-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.
+void SSLClientSocketNSS::Disconnect() {
+ EnterFunction("");
+
+ // TODO(wtc): Send SSL close_notify alert.
+ if (nss_fd_ != NULL) {
+ PR_Close(nss_fd_);
+ nss_fd_ = NULL;
}
- // Grab pointer to buffers
- nss_bufs_ = memio_GetSecret(nss_fd_);
+ // Shut down anything that may call us back (through buffer_send_callback_,
+ // buffer_recv_callback, or handshake_io_callback_).
+ verifier_.reset();
+ transport_->socket()->Disconnect();
- /* 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.
+ // 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;
}
- // TODO(port): set more ssl options! Check errors!
+ 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;
- int rv;
+ LeaveFunction("");
+}
- rv = SSL_OptionSet(nss_fd_, SSL_SECURITY, PR_TRUE);
- if (rv != SECSuccess) {
- LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_SECURITY");
- return ERR_UNEXPECTED;
- }
+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;
+}
- rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SSL2, PR_FALSE);
- if (rv != SECSuccess) {
- LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_SSL2");
- return ERR_UNEXPECTED;
- }
+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;
+}
- // 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;
- }
+int SSLClientSocketNSS::GetPeerAddress(AddressList* address) const {
+ return transport_->socket()->GetPeerAddress(address);
+}
- rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SSL3, ssl_config_.ssl3_enabled);
- if (rv != SECSuccess) {
- LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_SSL3");
- return ERR_UNEXPECTED;
+const BoundNetLog& SSLClientSocketNSS::NetLog() const {
+ return net_log_;
+}
+
+void SSLClientSocketNSS::SetSubresourceSpeculation() {
+ if (transport_.get() && transport_->socket()) {
+ transport_->socket()->SetSubresourceSpeculation();
+ } else {
+ NOTREACHED();
}
+}
- rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_TLS, ssl_config_.tls1_enabled);
- if (rv != SECSuccess) {
- LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_TLS");
- return ERR_UNEXPECTED;
+void SSLClientSocketNSS::SetOmniboxSpeculation() {
+ if (transport_.get() && transport_->socket()) {
+ transport_->socket()->SetOmniboxSpeculation();
+ } else {
+ NOTREACHED();
}
+}
- 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);
+bool SSLClientSocketNSS::WasEverUsed() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->WasEverUsed();
}
+ NOTREACHED();
+ return 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");
+bool SSLClientSocketNSS::UsingTCPFastOpen() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->UsingTCPFastOpen();
}
-#else
- #error "You need to install NSS-3.12 or later to build chromium"
-#endif
+ NOTREACHED();
+ 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_.tls1_enabled);
- if (rv != SECSuccess)
+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
+ 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_);
+
+ /* 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!
+
+ int rv;
+
+ rv = SSL_OptionSet(nss_fd_, SSL_SECURITY, PR_TRUE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_SECURITY");
+ return ERR_UNEXPECTED;
+ }
+
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SSL2, PR_FALSE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_SSL2");
+ return ERR_UNEXPECTED;
+ }
+
+ // 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;
+ }
+
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SSL3, ssl_config_.ssl3_enabled);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_SSL3");
+ return ERR_UNEXPECTED;
+ }
+
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_TLS, ssl_config_.tls1_enabled);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_TLS");
+ return ERR_UNEXPECTED;
+ }
+
+ 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);
+ }
+
+#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
+
+#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_.tls1_enabled);
+ if (rv != SECSuccess)
LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_DEFLATE");
#endif
@@ -764,247 +1080,39 @@ 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;
+// Sets server_cert_ and server_cert_nss_ if not yet set.
+// Returns server_cert_.
+X509Certificate *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_);
+ server_cert_ = X509Certificate::CreateFromDERCertChain(
+ certs.AsStringPieceVector());
+ }
}
+ return server_cert_;
+}
- // 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;
+// 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;
- 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_.
-X509Certificate *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_);
- server_cert_ = X509Certificate::CreateFromDERCertChain(
- certs.AsStringPieceVector());
- }
- }
- return server_cert_;
-}
-
-// 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;
-
- ssl_connection_status_ |=
- (static_cast<int>(channel_info.compressionMethod) &
- SSL_CONNECTION_COMPRESSION_MASK) <<
- SSL_CONNECTION_COMPRESSION_SHIFT;
+ 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.
@@ -1051,119 +1159,34 @@ 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_;
+void SSLClientSocketNSS::DoReadCallback(int rv) {
+ EnterFunction(rv);
+ DCHECK(rv != ERR_IO_PENDING);
+ DCHECK(user_read_callback_);
- 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;
- }
+ // Since Run may result in Read being called, clear |user_read_callback_|
+ // up front.
+ CompletionCallback* c = user_read_callback_;
+ user_read_callback_ = NULL;
+ user_read_buf_ = NULL;
+ user_read_buf_len_ = 0;
+ c->Run(rv);
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);
- DCHECK(user_read_callback_);
-
- // Since Run may result in Read being called, clear |user_read_callback_|
- // up front.
- CompletionCallback* c = user_read_callback_;
- user_read_callback_ = NULL;
- user_read_buf_ = NULL;
- user_read_buf_len_ = 0;
- c->Run(rv);
- LeaveFunction("");
-}
-
-void SSLClientSocketNSS::DoWriteCallback(int rv) {
- EnterFunction(rv);
- DCHECK(rv != ERR_IO_PENDING);
- DCHECK(user_write_callback_);
-
- // Since Run may result in Write being called, clear |user_write_callback_|
- // up front.
- CompletionCallback* c = user_write_callback_;
- user_write_callback_ = NULL;
- user_write_buf_ = NULL;
- user_write_buf_len_ = 0;
- c->Run(rv);
- LeaveFunction("");
+void SSLClientSocketNSS::DoWriteCallback(int rv) {
+ EnterFunction(rv);
+ DCHECK(rv != ERR_IO_PENDING);
+ DCHECK(user_write_callback_);
+
+ // Since Run may result in Write being called, clear |user_write_callback_|
+ // up front.
+ CompletionCallback* c = user_write_callback_;
+ user_write_callback_ = NULL;
+ user_write_buf_ = NULL;
+ user_write_buf_len_ = 0;
+ c->Run(rv);
+ LeaveFunction("");
}
// As part of Connect(), the SSLClientSocketNSS object performs an SSL
@@ -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,1060 +1379,1139 @@ 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);
+int SSLClientSocketNSS::DoSnapStartLoadInfo() {
+ EnterFunction("");
+ int rv = ssl_host_info_->WaitForDataReady(&handshake_io_callback_);
+ GotoState(STATE_HANDSHAKE);
- // 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);
+ if (rv == OK) {
+ if (ssl_host_info_->WaitForCertVerification(NULL) == OK) {
+ if (LoadSnapStartInfo()) {
+ pseudo_connected_ = true;
+ GotoState(STATE_SNAP_START_WAIT_FOR_WRITE);
+ if (user_connect_callback_)
+ DoConnectCallback(OK);
}
- CERT_DestroyCertificate(cert);
- }
-
- if (false_start && !that->handshake_callback_called_) {
- that->corked_ = true;
- that->uncork_timer_.Start(
- base::TimeDelta::FromMilliseconds(kCorkTimeoutMs),
- that, &SSLClientSocketNSS::UncorkAfterTimeout);
+ } else if (!ssl_host_info_->state().server_hello.empty()) {
+ // A non-empty ServerHello suggests that we would have tried a Snap Start
+ // connection.
+ base::TimeTicks now = base::TimeTicks::Now();
+ const base::TimeDelta duration =
+ now - ssl_host_info_->verification_start_time();
+ UMA_HISTOGRAM_TIMES("Net.SSLSnapStartNeededVerificationInMs", duration);
+ VLOG(1) << "Cannot snap start because verification isn't ready. "
+ << "Wanted verification after "
+ << duration.InMilliseconds() << "ms";
}
+ } else {
+ DCHECK_EQ(ERR_IO_PENDING, rv);
+ GotoState(STATE_SNAP_START_LOAD_INFO);
}
-#endif
- // Tell NSS to not verify the certificate.
- return SECSuccess;
+ LeaveFunction("");
+ 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) {
- 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;
+int SSLClientSocketNSS::DoSnapStartWaitForWrite() {
+ EnterFunction("");
+ // In this state, we're waiting for the first Write call so that we can merge
+ // it into the Snap Start handshake.
+ if (!user_write_buf_) {
+ // We'll lie and say that we're connected in order that someone will call
+ // Write.
+ GotoState(STATE_SNAP_START_WAIT_FOR_WRITE);
+ DCHECK(!user_connect_callback_);
+ LeaveFunction("");
+ return ERR_IO_PENDING;
}
- that->client_certs_.clear();
+ // This is the largest Snap Start application data payload that we'll try to
+ // use. A TCP client can only send three frames of data without an ACK and,
+ // at 2048 bytes, this leaves some space for the rest of the ClientHello
+ // (including possible session ticket).
+ static const int kMaxSnapStartPayloadSize = 2048;
- 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;
+ if (user_write_buf_len_ > kMaxSnapStartPayloadSize) {
+ user_write_buf_len_ = kMaxSnapStartPayloadSize;
+ // When we complete the handshake and call user_write_callback_ we'll say
+ // that we only wrote |kMaxSnapStartPayloadSize| bytes. That way the rest
+ // of the payload will be presented to |Write| again and transmitted as
+ // normal application 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;
- }
+ SECStatus rv = SSL_SetSnapStartApplicationData(
+ nss_fd_, reinterpret_cast<const unsigned char*>(user_write_buf_->data()),
+ user_write_buf_len_);
+ DCHECK_EQ(SECSuccess, rv);
- // 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;
+ GotoState(STATE_HANDSHAKE);
+ LeaveFunction("");
+ return OK;
+}
- PCCERT_CHAIN_CONTEXT chain_context = NULL;
+int SSLClientSocketNSS::DoHandshake() {
+ EnterFunction("");
+ int net_error = net::OK;
+ SECStatus rv = SSL_ForceHandshake(nss_fd_);
- 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;
+ if (client_auth_cert_needed_) {
+ 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_) {
+ if (eset_mitm_detected_) {
+ net_error = ERR_ESET_ANTI_VIRUS_SSL_INTERCEPTION;
+ } else {
+ // We need to see if the predicted certificate chain (in
+ // |ssl_host_info_->state().certs) matches the actual certificate chain
+ // before we call SaveSnapStartInfo, 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;
+ }
+ }
+ }
+ }
- // 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;
- }
+#if defined(SSL_ENABLE_OCSP_STAPLING)
+ const CacheOCSPResponseFromSideChannelFunction cache_ocsp_response =
+ GetCacheOCSPResponseFromSideChannelFunction();
+ // TODO: we need to be able to plumb an OCSP response into the system
+ // libraries. When we do, GetOCSPResponseFromSideChannelFunction
+ // needs to be updated for those platforms.
+ if (!predicted_cert_chain_correct_ && cache_ocsp_response) {
+ 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);
- // 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;
+ 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
+
+ SaveSnapStartInfo();
+ // SSL handshake is completed. It's possible that we mispredicted the
+ // NPN agreed protocol. In this case, we've just sent a request in the
+ // wrong protocol! The higher levels of this network stack aren't
+ // prepared for switching the protocol like that so we make up an error
+ // and rely on the fact that the request will be retried.
+ if (IsNPNProtocolMispredicted()) {
+ LOG(WARNING) << "Mispredicted NPN protocol for "
+ << host_and_port_.ToString();
+ net_error = ERR_SSL_SNAP_START_NPN_MISPREDICTION;
+ } else {
+ // Let's verify the certificate.
+ GotoState(STATE_VERIFY_DNSSEC);
+ }
}
- intermediates.push_back(intermediate_copy);
+ // Done!
+ } else {
+ // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=562434 -
+ // SSL_ForceHandshake returned SECSuccess prematurely.
+ 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 = MapNSSHandshakeError(prerr);
- scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromHandle(
- cert_context2, X509Certificate::SOURCE_LONE_CERT_IMPORT,
- intermediates);
- that->client_certs_.push_back(cert);
+ // If not done, stay in this state
+ if (net_error == ERR_IO_PENDING) {
+ GotoState(STATE_HANDSHAKE);
+ } else {
+ LOG(ERROR) << "handshake failed; NSS error code " << prerr
+ << ", net_error " << net_error;
+ net_log_.AddEvent(
+ NetLog::TYPE_SSL_HANDSHAKE_ERROR,
+ make_scoped_refptr(new SSLErrorParams(net_error, prerr)));
+ }
+ }
- X509Certificate::FreeOSCertHandle(cert_context2);
- for (net::X509Certificate::OSCertHandles::iterator it =
- intermediates.begin(); it != intermediates.end(); ++it) {
- net::X509Certificate::FreeOSCertHandle(*it);
+ LeaveFunction("");
+ return net_error;
+}
+
+int SSLClientSocketNSS::DoVerifyDNSSEC(int result) {
+ if (ssl_config_.dns_cert_provenance_checking_enabled &&
+ dns_cert_checker_) {
+ PeerCertificateChain certs(nss_fd_);
+ dns_cert_checker_->DoAsyncVerification(
+ host_and_port_.host(), certs.AsStringPieceVector());
+ }
+
+ if (ssl_config_.dnssec_enabled) {
+ DNSValidationResult r = CheckDNSSECChain(host_and_port_.host(),
+ server_cert_nss_);
+ if (r == DNSVR_SUCCESS) {
+ local_server_cert_verify_result_.cert_status |= CERT_STATUS_IS_DNSSEC;
+ server_cert_verify_result_ = &local_server_cert_verify_result_;
+ GotoState(STATE_VERIFY_CERT_COMPLETE);
+ return OK;
}
}
- BOOL ok = CertCloseStore(my_cert_store, CERT_CLOSE_STORE_CHECK_FLAG);
- DCHECK(ok);
+ if (dnssec_provider_ == NULL) {
+ GotoState(STATE_VERIFY_CERT);
+ return 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);
+ GotoState(STATE_VERIFY_DNSSEC_COMPLETE);
+ RRResponse* response;
+ dnssec_wait_start_time_ = base::Time::Now();
+ return dnssec_provider_->GetDNSSECRecords(&response, &handshake_io_callback_);
+}
- 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);
+int SSLClientSocketNSS::DoVerifyDNSSECComplete(int result) {
+ RRResponse* response;
+ int err = dnssec_provider_->GetDNSSECRecords(&response, NULL);
+ DCHECK_EQ(err, OK);
- 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;
+ const base::TimeDelta elapsed = base::Time::Now() - dnssec_wait_start_time_;
+ HISTOGRAM_TIMES("Net.DNSSECWaitTime", elapsed);
- 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;
- }
+ GotoState(STATE_VERIFY_CERT);
+ if (!response || response->rrdatas.empty())
+ return OK;
- that->client_certs_.clear();
+ std::vector<base::StringPiece> records;
+ records.resize(response->rrdatas.size());
+ for (unsigned i = 0; i < response->rrdatas.size(); i++)
+ records[i] = base::StringPiece(response->rrdatas[i]);
+ DNSValidationResult r =
+ VerifyTXTRecords(response->dnssec, server_cert_nss_, records);
- // 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);
- }
+ if (!ssl_config_.dnssec_enabled) {
+ // If DNSSEC is not enabled we don't take any action based on the result,
+ // except to record the latency, above.
+ 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_);
+ switch (r) {
+ case DNSVR_FAILURE:
+ GotoState(STATE_VERIFY_CERT_COMPLETE);
+ local_server_cert_verify_result_.cert_status |= CERT_STATUS_NOT_IN_DNS;
+ server_cert_verify_result_ = &local_server_cert_verify_result_;
+ return ERR_CERT_NOT_IN_DNS;
+ case DNSVR_CONTINUE:
+ GotoState(STATE_VERIFY_CERT);
+ break;
+ case DNSVR_SUCCESS:
+ local_server_cert_verify_result_.cert_status |= CERT_STATUS_IS_DNSSEC;
+ server_cert_verify_result_ = &local_server_cert_verify_result_;
+ GotoState(STATE_VERIFY_CERT_COMPLETE);
+ break;
+ default:
+ NOTREACHED();
+ 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);
+int SSLClientSocketNSS::DoVerifyCert(int result) {
+ DCHECK(server_cert_);
- that->client_auth_cert_needed_ = !that->ssl_config_.send_client_cert;
- void* wincx = SSL_RevealPinArg(socket);
+ GotoState(STATE_VERIFY_CERT_COMPLETE);
+ start_cert_verification_time_ = base::TimeTicks::Now();
- // 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;
+ 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(&handshake_io_callback_);
+ } else {
+ UMA_HISTOGRAM_ENUMERATION("Net.SSLVerificationMerged", 0 /* false */, 2);
}
- // 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);
+ 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;
+ 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,
+ &local_server_cert_verify_result_,
+ &handshake_io_callback_);
+}
+
+// Derived from AuthCertificateCallback() in
+// mozilla/source/security/manager/ssl/src/nsNSSCallbacks.cpp.
+int SSLClientSocketNSS::DoVerifyCertComplete(int result) {
+ verifier_.reset();
+
+ 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);
}
- // 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
+ if (ssl_host_info_.get())
+ ssl_host_info_->set_cert_verification_finished_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;
+ // If we have been explicitly told to accept this certificate, override the
+ // result of verifier_.Verify.
+ // Eventually, we should cache the cert verification results so that we don't
+ // need to call verifier_.Verify repeatedly. But for now we need to do this.
+ // Alternatively, we could use the cert's status that we stored along with
+ // the cert in the allowed_bad_certs vector.
+ if (IsCertificateError(result) &&
+ ssl_config_.IsAllowedBadCert(server_cert_)) {
+ VLOG(1) << "accepting bad SSL certificate, as user told us to";
+ result = OK;
+ }
- that->UpdateServerCert();
- that->UpdateConnectionStatus();
-}
+ if (result == OK)
+ LogConnectionTypeMetrics();
-int SSLClientSocketNSS::DoSnapStartLoadInfo() {
- EnterFunction("");
- int rv = ssl_host_info_->WaitForDataReady(&handshake_io_callback_);
- GotoState(STATE_HANDSHAKE);
+ completed_handshake_ = true;
- if (rv == OK) {
- if (ssl_host_info_->WaitForCertVerification(NULL) == OK) {
- if (LoadSnapStartInfo()) {
- pseudo_connected_ = true;
- GotoState(STATE_SNAP_START_WAIT_FOR_WRITE);
- if (user_connect_callback_)
- DoConnectCallback(OK);
+ // If we merged a Write call into the handshake we need to make the
+ // callback now.
+ if (user_write_callback_) {
+ corked_ = false;
+ if (result != OK) {
+ DoWriteCallback(result);
+ } else {
+ SSLSnapStartResult snap_start_type;
+ SECStatus rv = SSL_GetSnapStartResult(nss_fd_, &snap_start_type);
+ DCHECK_EQ(rv, SECSuccess);
+ DCHECK_NE(snap_start_type, SSL_SNAP_START_NONE);
+ if (snap_start_type == SSL_SNAP_START_RECOVERY ||
+ snap_start_type == SSL_SNAP_START_RESUME_RECOVERY) {
+ // If we mispredicted the server's handshake then Snap Start will have
+ // triggered a recovery mode. The misprediction could have been caused
+ // by the server having a different certificate so the application data
+ // wasn't resent. Now that we have verified the certificate, we need to
+ // resend the application data.
+ int bytes_written = DoPayloadWrite();
+ if (bytes_written != ERR_IO_PENDING)
+ DoWriteCallback(bytes_written);
+ } else {
+ DoWriteCallback(user_write_buf_len_);
}
- } else if (!ssl_host_info_->state().server_hello.empty()) {
- // A non-empty ServerHello suggests that we would have tried a Snap Start
- // connection.
- base::TimeTicks now = base::TimeTicks::Now();
- const base::TimeDelta duration =
- now - ssl_host_info_->verification_start_time();
- UMA_HISTOGRAM_TIMES("Net.SSLSnapStartNeededVerificationInMs", duration);
- VLOG(1) << "Cannot snap start because verification isn't ready. "
- << "Wanted verification after "
- << duration.InMilliseconds() << "ms";
}
- } else {
- DCHECK_EQ(ERR_IO_PENDING, rv);
- GotoState(STATE_SNAP_START_LOAD_INFO);
}
+ if (user_read_callback_) {
+ int rv = DoReadLoop(OK);
+ if (rv != ERR_IO_PENDING)
+ DoReadCallback(rv);
+ }
+
+ // Exit DoHandshakeLoop and return the result to the caller to Connect.
+ DCHECK(next_handshake_state_ == STATE_NONE);
+ return result;
+}
+
+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;
+ }
+ if (rv >= 0) {
+ LogData(user_read_buf_->data(), rv);
+ LeaveFunction("");
+ return rv;
+ }
+ PRErrorCode prerr = PR_GetError();
+ if (prerr == PR_WOULD_BLOCK_ERROR) {
+ LeaveFunction("");
+ return ERR_IO_PENDING;
+ }
LeaveFunction("");
+ rv = MapNSSError(prerr);
+ net_log_.AddEvent(NetLog::TYPE_SSL_READ_ERROR,
+ make_scoped_refptr(new SSLErrorParams(rv, prerr)));
return rv;
}
-int SSLClientSocketNSS::DoSnapStartWaitForWrite() {
- EnterFunction("");
- // In this state, we're waiting for the first Write call so that we can merge
- // it into the Snap Start handshake.
- if (!user_write_buf_) {
- // We'll lie and say that we're connected in order that someone will call
- // Write.
- GotoState(STATE_SNAP_START_WAIT_FOR_WRITE);
- DCHECK(!user_connect_callback_);
+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) {
+ LogData(user_write_buf_->data(), rv);
+ LeaveFunction("");
+ return rv;
+ }
+ PRErrorCode prerr = PR_GetError();
+ if (prerr == PR_WOULD_BLOCK_ERROR) {
LeaveFunction("");
return ERR_IO_PENDING;
}
+ LeaveFunction("");
+ rv = MapNSSError(prerr);
+ net_log_.AddEvent(NetLog::TYPE_SSL_WRITE_ERROR,
+ make_scoped_refptr(new SSLErrorParams(rv, prerr)));
+ return rv;
+}
- // This is the largest Snap Start application data payload that we'll try to
- // use. A TCP client can only send three frames of data without an ACK and,
- // at 2048 bytes, this leaves some space for the rest of the ClientHello
- // (including possible session ticket).
- static const int kMaxSnapStartPayloadSize = 2048;
+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;
+ };
+}
- if (user_write_buf_len_ > kMaxSnapStartPayloadSize) {
- user_write_buf_len_ = kMaxSnapStartPayloadSize;
- // When we complete the handshake and call user_write_callback_ we'll say
- // that we only wrote |kMaxSnapStartPayloadSize| bytes. That way the rest
- // of the payload will be presented to |Write| again and transmitted as
- // normal application data.
+// 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;
}
- SECStatus rv = SSL_SetSnapStartApplicationData(
- nss_fd_, reinterpret_cast<const unsigned char*>(user_write_buf_->data()),
- user_write_buf_len_);
- DCHECK_EQ(SECSuccess, rv);
-
- GotoState(STATE_HANDSHAKE);
- LeaveFunction("");
- return OK;
-}
+ 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::DoHandshake() {
- EnterFunction("");
- int net_error = net::OK;
- SECStatus rv = SSL_ForceHandshake(nss_fd_);
+ state->certs.push_back(std::string(
+ reinterpret_cast<char*>(certs[i]->derCert.data),
+ certs[i]->derCert.len));
+ }
- if (client_auth_cert_needed_) {
- 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_) {
- if (eset_mitm_detected_) {
- net_error = ERR_ESET_ANTI_VIRUS_SSL_INTERCEPTION;
- } else {
- // We need to see if the predicted certificate chain (in
- // |ssl_host_info_->state().certs) matches the actual certificate chain
- // before we call SaveSnapStartInfo, 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;
- }
- }
- }
- }
+ ssl_host_info_->Persist();
+}
-#if defined(SSL_ENABLE_OCSP_STAPLING)
- const CacheOCSPResponseFromSideChannelFunction cache_ocsp_response =
- GetCacheOCSPResponseFromSideChannelFunction();
- // TODO: we need to be able to plumb an OCSP response into the system
- // libraries. When we do, GetOCSPResponseFromSideChannelFunction
- // needs to be updated for those platforms.
- if (!predicted_cert_chain_correct_ && cache_ocsp_response) {
- 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);
+// 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());
- SECItem ocsp_response_item;
- ocsp_response_item.type = siBuffer;
- ocsp_response_item.data = ocsp_response.get();
- ocsp_response_item.len = len;
+ if (state.server_hello.empty() ||
+ state.certs.empty() ||
+ !state.npn_valid) {
+ return false;
+ }
- cache_ocsp_response(
- CERT_GetDefaultCertDB(), server_cert_nss_, PR_Now(),
- &ocsp_response_item, NULL);
- }
- }
-#endif
+ SECStatus rv;
+ rv = SSL_SetPredictedServerHelloData(
+ nss_fd_,
+ reinterpret_cast<const uint8*>(state.server_hello.data()),
+ state.server_hello.size());
+ DCHECK_EQ(SECSuccess, rv);
- SaveSnapStartInfo();
- // SSL handshake is completed. It's possible that we mispredicted the
- // NPN agreed protocol. In this case, we've just sent a request in the
- // wrong protocol! The higher levels of this network stack aren't
- // prepared for switching the protocol like that so we make up an error
- // and rely on the fact that the request will be retried.
- if (IsNPNProtocolMispredicted()) {
- LOG(WARNING) << "Mispredicted NPN protocol for "
- << host_and_port_.ToString();
- net_error = ERR_SSL_SNAP_START_NPN_MISPREDICTION;
- } else {
- // 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.
- rv = SECFailure;
- net_error = ERR_SSL_PROTOCOL_ERROR;
- net_log_.AddEvent(NetLog::TYPE_SSL_HANDSHAKE_ERROR,
- make_scoped_refptr(new SSLErrorParams(net_error, 0)));
+ 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;
}
- } else {
- PRErrorCode prerr = PR_GetError();
- net_error = MapNSSHandshakeError(prerr);
+ }
- // If not done, stay in this state
- if (net_error == ERR_IO_PENDING) {
- GotoState(STATE_HANDSHAKE);
- } else {
- LOG(ERROR) << "handshake failed; NSS error code " << prerr
- << ", net_error " << net_error;
- net_log_.AddEvent(
- NetLog::TYPE_SSL_HANDSHAKE_ERROR,
- make_scoped_refptr(new SSLErrorParams(net_error, prerr)));
- }
+ 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;
}
- LeaveFunction("");
- return net_error;
+ return true;
}
-// 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.
-};
+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;
+}
-// 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;
+void SSLClientSocketNSS::UncorkAfterTimeout() {
+ corked_ = false;
+ int nsent;
+ do {
+ nsent = BufferSend();
+ } while (nsent > 0);
+}
- 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;
+// 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;
+}
- std::map<std::string, std::string>::const_iterator j;
- j = m.find("v");
- if (j == m.end() || j->second != "tls1")
- continue;
+// 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;
- j = m.find("ha");
+ 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;
- 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;
+ 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 {
- continue;
+ memio_PutWriteResult(nss_bufs_, MapErrorToNSS(rv));
}
+ }
- j = m.find("h");
- if (j == m.end())
- continue;
+ LeaveFunction(rv);
+ return rv;
+}
- std::vector<uint8> given_hash;
- if (!base::HexStringToBytes(j->second, &given_hash))
- continue;
+void SSLClientSocketNSS::BufferSendComplete(int result) {
+ EnterFunction(result);
- if (given_hash.size() != hash_length)
- continue;
+ // In the case of TCP FastOpen, connect is now finished.
+ if (!peername_initialized_ && UsingTCPFastOpen())
+ InitializeSSLPeerName();
- uint8 calculated_hash[SHA256_LENGTH]; // SHA256 is the largest.
- SECStatus rv;
+ memio_PutWriteResult(nss_bufs_, MapErrorToNSS(result));
+ transport_send_busy_ = false;
+ OnSendComplete(result);
+ LeaveFunction("");
+}
- 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);
+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 {
- continue;
+ if (rv > 0)
+ memcpy(buf, recv_buffer_->data(), rv);
+ memio_PutReadResult(nss_bufs_, MapErrorToNSS(rv));
+ recv_buffer_ = NULL;
}
+ }
+ LeaveFunction(rv);
+ return rv;
+}
- if (rv != SECSuccess)
- NOTREACHED();
+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("");
+}
- found_well_formed_record = true;
+// 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 (memcmp(calculated_hash, &given_hash[0], hash_length) == 0) {
- matched_record = true;
- if (dnssec)
- return DNSVR_SUCCESS;
+ 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 (found_well_formed_record && !matched_record)
- return DNSVR_FAILURE;
+ if (false_start && !that->handshake_callback_called_) {
+ that->corked_ = true;
+ that->uncork_timer_.Start(
+ base::TimeDelta::FromMilliseconds(kCorkTimeoutMs),
+ that, &SSLClientSocketNSS::UncorkAfterTimeout);
+ }
+ }
+#endif
- return DNSVR_CONTINUE;
+ // 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);
-// 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;
+ 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);
- // 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;
- }
+ 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);
- SECItem dnssec_embedded_chain;
- SECStatus rv = CERT_FindCertExtension(server_cert_nss,
- dnssec_chain_tag, &dnssec_embedded_chain);
- if (rv != SECSuccess)
- return DNSVR_CONTINUE;
+ // 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;
- 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;
+ 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;
}
- 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;
-}
+ that->client_certs_.clear();
-int SSLClientSocketNSS::DoVerifyDNSSEC(int result) {
- if (ssl_config_.dns_cert_provenance_checking_enabled &&
- dns_cert_checker_) {
- PeerCertificateChain certs(nss_fd_);
- dns_cert_checker_->DoAsyncVerification(
- host_and_port_.host(), certs.AsStringPieceVector());
+ 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;
}
- if (ssl_config_.dnssec_enabled) {
- DNSValidationResult r = CheckDNSSECChain(host_and_port_.host(),
- server_cert_nss_);
- if (r == DNSVR_SUCCESS) {
- local_server_cert_verify_result_.cert_status |= CERT_STATUS_IS_DNSSEC;
- server_cert_verify_result_ = &local_server_cert_verify_result_;
- GotoState(STATE_VERIFY_CERT_COMPLETE);
- return OK;
- }
+ // 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;
}
- if (dnssec_provider_ == NULL) {
- GotoState(STATE_VERIFY_CERT);
- return OK;
- }
+ // 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;
- GotoState(STATE_VERIFY_DNSSEC_COMPLETE);
- RRResponse* response;
- dnssec_wait_start_time_ = base::Time::Now();
- return dnssec_provider_->GetDNSSECRecords(&response, &handshake_io_callback_);
-}
+ PCCERT_CHAIN_CONTEXT chain_context = NULL;
-int SSLClientSocketNSS::DoVerifyDNSSECComplete(int result) {
- RRResponse* response;
- int err = dnssec_provider_->GetDNSSECRecords(&response, NULL);
- DCHECK_EQ(err, OK);
+ 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;
+ }
- const base::TimeDelta elapsed = base::Time::Now() - dnssec_wait_start_time_;
- HISTOGRAM_TIMES("Net.DNSSECWaitTime", elapsed);
+ // 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;
+ }
- GotoState(STATE_VERIFY_CERT);
- if (!response || response->rrdatas.empty())
- return OK;
+ // 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);
+ }
- std::vector<base::StringPiece> records;
- records.resize(response->rrdatas.size());
- for (unsigned i = 0; i < response->rrdatas.size(); i++)
- records[i] = base::StringPiece(response->rrdatas[i]);
- DNSValidationResult r =
- VerifyTXTRecords(response->dnssec, server_cert_nss_, records);
+ scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromHandle(
+ cert_context2, X509Certificate::SOURCE_LONE_CERT_IMPORT,
+ intermediates);
+ that->client_certs_.push_back(cert);
- if (!ssl_config_.dnssec_enabled) {
- // If DNSSEC is not enabled we don't take any action based on the result,
- // except to record the latency, above.
- return OK;
+ X509Certificate::FreeOSCertHandle(cert_context2);
+ for (net::X509Certificate::OSCertHandles::iterator it =
+ intermediates.begin(); it != intermediates.end(); ++it) {
+ net::X509Certificate::FreeOSCertHandle(*it);
+ }
}
- switch (r) {
- case DNSVR_FAILURE:
- GotoState(STATE_VERIFY_CERT_COMPLETE);
- local_server_cert_verify_result_.cert_status |= CERT_STATUS_NOT_IN_DNS;
- server_cert_verify_result_ = &local_server_cert_verify_result_;
- return ERR_CERT_NOT_IN_DNS;
- case DNSVR_CONTINUE:
- GotoState(STATE_VERIFY_CERT);
- break;
- case DNSVR_SUCCESS:
- local_server_cert_verify_result_.cert_status |= CERT_STATUS_IS_DNSSEC;
- server_cert_verify_result_ = &local_server_cert_verify_result_;
- GotoState(STATE_VERIFY_CERT_COMPLETE);
- break;
- default:
- NOTREACHED();
- GotoState(STATE_VERIFY_CERT);
- }
+ BOOL ok = CertCloseStore(my_cert_store, CERT_CLOSE_STORE_CHECK_FLAG);
+ DCHECK(ok);
- return 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);
-int SSLClientSocketNSS::DoVerifyCert(int result) {
- DCHECK(server_cert_);
+ 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);
- GotoState(STATE_VERIFY_CERT_COMPLETE);
- start_cert_verification_time_ = base::TimeTicks::Now();
+ 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;
- 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(&handshake_io_callback_);
- } else {
- UMA_HISTOGRAM_ENUMERATION("Net.SSLVerificationMerged", 0 /* false */, 2);
+ 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;
}
- 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;
- 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,
- &local_server_cert_verify_result_,
- &handshake_io_callback_);
-}
-
-// Derived from AuthCertificateCallback() in
-// mozilla/source/security/manager/ssl/src/nsNSSCallbacks.cpp.
-int SSLClientSocketNSS::DoVerifyCertComplete(int result) {
- verifier_.reset();
+ that->client_certs_.clear();
- 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);
+ // 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);
+ }
}
- if (ssl_host_info_.get())
- ssl_host_info_->set_cert_verification_finished_time();
+ // 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_);
- // 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.
+ // 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
+}
- // If we have been explicitly told to accept this certificate, override the
- // result of verifier_.Verify.
- // Eventually, we should cache the cert verification results so that we don't
- // need to call verifier_.Verify repeatedly. But for now we need to do this.
- // Alternatively, we could use the cert's status that we stored along with
- // the cert in the allowed_bad_certs vector.
- if (IsCertificateError(result) &&
- ssl_config_.IsAllowedBadCert(server_cert_)) {
- VLOG(1) << "accepting bad SSL certificate, as user told us to";
- result = OK;
- }
+#else // NSS_PLATFORM_CLIENT_AUTH
- if (result == OK)
- LogConnectionTypeMetrics();
+// 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);
- completed_handshake_ = true;
+ that->client_auth_cert_needed_ = !that->ssl_config_.send_client_cert;
+ void* wincx = SSL_RevealPinArg(socket);
- // If we merged a Write call into the handshake we need to make the
- // callback now.
- if (user_write_callback_) {
- corked_ = false;
- if (result != OK) {
- DoWriteCallback(result);
- } else {
- SSLSnapStartResult snap_start_type;
- SECStatus rv = SSL_GetSnapStartResult(nss_fd_, &snap_start_type);
- DCHECK_EQ(rv, SECSuccess);
- DCHECK_NE(snap_start_type, SSL_SNAP_START_NONE);
- if (snap_start_type == SSL_SNAP_START_RECOVERY ||
- snap_start_type == SSL_SNAP_START_RESUME_RECOVERY) {
- // If we mispredicted the server's handshake then Snap Start will have
- // triggered a recovery mode. The misprediction could have been caused
- // by the server having a different certificate so the application data
- // wasn't resent. Now that we have verified the certificate, we need to
- // resend the application data.
- int bytes_written = DoPayloadWrite();
- if (bytes_written != ERR_IO_PENDING)
- DoWriteCallback(bytes_written);
- } else {
- DoWriteCallback(user_write_buf_len_);
+ // 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;
}
- if (user_read_callback_) {
- int rv = DoReadLoop(OK);
- if (rv != ERR_IO_PENDING)
- DoReadCallback(rv);
+ // 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);
}
- // Exit DoHandshakeLoop and return the result to the caller to Connect.
- DCHECK(next_handshake_state_ == STATE_NONE);
- return result;
+ // 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
-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;
- }
- if (rv >= 0) {
- LogData(user_read_buf_->data(), rv);
- LeaveFunction("");
- return rv;
- }
- PRErrorCode prerr = PR_GetError();
- if (prerr == PR_WOULD_BLOCK_ERROR) {
- LeaveFunction("");
- return ERR_IO_PENDING;
- }
- LeaveFunction("");
- rv = MapNSSError(prerr);
- net_log_.AddEvent(NetLog::TYPE_SSL_READ_ERROR,
- make_scoped_refptr(new SSLErrorParams(rv, prerr)));
- return rv;
-}
+// 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);
-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) {
- LogData(user_write_buf_->data(), rv);
- LeaveFunction("");
- return rv;
- }
- PRErrorCode prerr = PR_GetError();
- if (prerr == PR_WOULD_BLOCK_ERROR) {
- LeaveFunction("");
- return ERR_IO_PENDING;
- }
- LeaveFunction("");
- rv = MapNSSError(prerr);
- net_log_.AddEvent(NetLog::TYPE_SSL_WRITE_ERROR,
- make_scoped_refptr(new SSLErrorParams(rv, prerr)));
- return rv;
-}
+ that->handshake_callback_called_ = true;
-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;
- };
+ that->UpdateServerCert();
+ that->UpdateConnectionStatus();
}
} // namespace net
« no previous file with comments | « net/socket/ssl_client_socket_nss.h ('k') | net/socket/ssl_server_socket_nss.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698