// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This file includes code SSLClientSocketNSS::DoVerifyCertComplete() derived // from AuthCertificateCallback() in // mozilla/security/manager/ssl/src/nsNSSCallbacks.cpp. /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Netscape security libraries. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 2000 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Ian McGreer <mcgreer@netscape.com> * Javier Delgadillo <javi@netscape.com> * Kai Engert <kengert@redhat.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "net/socket/ssl_client_socket_nss.h" #if defined(USE_SYSTEM_SSL) #include <dlfcn.h> #endif #include <certdb.h> #include <keyhi.h> #include <nspr.h> #include <nss.h> #include <secerr.h> #include <ssl.h> #include <sslerr.h> #include <pk11pub.h> #include "base/compiler_specific.h" #include "base/logging.h" #include "base/nss_util.h" #include "base/singleton.h" #include "base/string_util.h" #include "net/base/cert_verifier.h" #include "net/base/io_buffer.h" #include "net/base/load_log.h" #include "net/base/net_errors.h" #include "net/base/ssl_cert_request_info.h" #include "net/base/ssl_info.h" #include "net/ocsp/nss_ocsp.h" static const int kRecvBufferSize = 4096; namespace net { // State machines are easier to debug if you log state transitions. // Enable these if you want to see what's going on. #if 1 #define EnterFunction(x) #define LeaveFunction(x) #define GotoState(s) next_handshake_state_ = s #define LogData(s, len) #else #define EnterFunction(x) LOG(INFO) << (void *)this << " " << __FUNCTION__ << \ " enter " << x << \ "; next_handshake_state " << next_handshake_state_ #define LeaveFunction(x) LOG(INFO) << (void *)this << " " << __FUNCTION__ << \ " leave " << x << \ "; next_handshake_state " << next_handshake_state_ #define GotoState(s) do { LOG(INFO) << (void *)this << " " << __FUNCTION__ << \ " jump to state " << s; \ next_handshake_state_ = s; } while (0) #define LogData(s, len) LOG(INFO) << (void *)this << " " << __FUNCTION__ << \ " data [" << std::string(s, len) << "]"; #endif namespace { class NSSSSLInitSingleton { public: NSSSSLInitSingleton() { base::EnsureNSSInit(); NSS_SetDomesticPolicy(); #if defined(USE_SYSTEM_SSL) // Use late binding to avoid scary but benign warning // "Symbol `SSL_ImplementedCiphers' has different size in shared object, // consider re-linking" const PRUint16* pSSL_ImplementedCiphers = static_cast<const PRUint16*>( dlsym(RTLD_DEFAULT, "SSL_ImplementedCiphers")); if (pSSL_ImplementedCiphers == NULL) { NOTREACHED() << "Can't get list of supported ciphers"; return; } #else #define pSSL_ImplementedCiphers SSL_ImplementedCiphers #endif // Explicitly enable exactly those ciphers with keys of at least 80 bits for (int i = 0; i < SSL_NumImplementedCiphers; i++) { SSLCipherSuiteInfo info; if (SSL_GetCipherSuiteInfo(pSSL_ImplementedCiphers[i], &info, sizeof(info)) == SECSuccess) { SSL_CipherPrefSetDefault(pSSL_ImplementedCiphers[i], (info.effectiveKeyBits >= 80)); } } // Enable SSL. SSL_OptionSetDefault(SSL_SECURITY, PR_TRUE); // All other SSL options are set per-session by SSLClientSocket. } ~NSSSSLInitSingleton() { // Have to clear the cache, or NSS_Shutdown fails with SEC_ERROR_BUSY. SSL_ClearSessionCache(); } }; // Initialize the NSS SSL library if it isn't already initialized. This must // be called before any other NSS SSL functions. This function is // thread-safe, and the NSS SSL library will only ever be initialized once. // The NSS SSL library will be properly shut down on program exit. void EnsureNSSSSLInit() { Singleton<NSSSSLInitSingleton>::get(); } // The default error mapping function. // Maps an NSPR error code to a network error code. int MapNSPRError(PRErrorCode err) { // TODO(port): fill this out as we learn what's important switch (err) { case PR_WOULD_BLOCK_ERROR: return ERR_IO_PENDING; case PR_ADDRESS_NOT_SUPPORTED_ERROR: // For connect. case PR_NO_ACCESS_RIGHTS_ERROR: return ERR_ACCESS_DENIED; case PR_IO_TIMEOUT_ERROR: return ERR_TIMED_OUT; case PR_CONNECT_RESET_ERROR: return ERR_CONNECTION_RESET; case PR_CONNECT_ABORTED_ERROR: return ERR_CONNECTION_ABORTED; case PR_CONNECT_REFUSED_ERROR: return ERR_CONNECTION_REFUSED; case PR_HOST_UNREACHABLE_ERROR: case PR_NETWORK_UNREACHABLE_ERROR: return ERR_ADDRESS_UNREACHABLE; case PR_ADDRESS_NOT_AVAILABLE_ERROR: return ERR_ADDRESS_INVALID; case SSL_ERROR_NO_CYPHER_OVERLAP: case SSL_ERROR_UNSUPPORTED_VERSION: return ERR_SSL_VERSION_OR_CIPHER_MISMATCH; case SSL_ERROR_HANDSHAKE_FAILURE_ALERT: return ERR_SSL_PROTOCOL_ERROR; default: { if (IS_SSL_ERROR(err)) { LOG(WARNING) << "Unknown SSL error " << err << " mapped to net::ERR_SSL_PROTOCOL_ERROR"; return ERR_SSL_PROTOCOL_ERROR; } LOG(WARNING) << "Unknown error " << err << " mapped to net::ERR_FAILED"; return ERR_FAILED; } } } // Context-sensitive error mapping functions. int MapHandshakeError(PRErrorCode err) { switch (err) { // If the server closed on us, it is a protocol error. // Some TLS-intolerant servers do this when we request TLS. case PR_END_OF_FILE_ERROR: // The handshake may fail because some signature (for example, the // signature in the ServerKeyExchange message for an ephemeral // Diffie-Hellman cipher suite) is invalid. case SEC_ERROR_BAD_SIGNATURE: return ERR_SSL_PROTOCOL_ERROR; default: return MapNSPRError(err); } } } // namespace #if defined(OS_WIN) // static HCERTSTORE SSLClientSocketNSS::cert_store_ = NULL; #endif SSLClientSocketNSS::SSLClientSocketNSS(ClientSocket* transport_socket, const std::string& hostname, const SSLConfig& ssl_config) : ALLOW_THIS_IN_INITIALIZER_LIST(buffer_send_callback_( this, &SSLClientSocketNSS::BufferSendComplete)), ALLOW_THIS_IN_INITIALIZER_LIST(buffer_recv_callback_( this, &SSLClientSocketNSS::BufferRecvComplete)), transport_send_busy_(false), transport_recv_busy_(false), ALLOW_THIS_IN_INITIALIZER_LIST(handshake_io_callback_( this, &SSLClientSocketNSS::OnHandshakeIOComplete)), transport_(transport_socket), hostname_(hostname), ssl_config_(ssl_config), user_connect_callback_(NULL), user_read_callback_(NULL), user_write_callback_(NULL), user_read_buf_len_(0), user_write_buf_len_(0), server_cert_nss_(NULL), client_auth_cert_needed_(false), completed_handshake_(false), next_handshake_state_(STATE_NONE), nss_fd_(NULL), nss_bufs_(NULL) { EnterFunction(""); } SSLClientSocketNSS::~SSLClientSocketNSS() { EnterFunction(""); Disconnect(); LeaveFunction(""); } int SSLClientSocketNSS::Init() { EnterFunction(""); // Initialize the NSS SSL library in a threadsafe way. This also // initializes the NSS base library. EnsureNSSSSLInit(); #if !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::Connect(CompletionCallback* callback, LoadLog* load_log) { EnterFunction(""); DCHECK(transport_.get()); DCHECK(next_handshake_state_ == STATE_NONE); DCHECK(!user_read_callback_); DCHECK(!user_write_callback_); DCHECK(!user_connect_callback_); DCHECK(!user_read_buf_); DCHECK(!user_write_buf_); LoadLog::BeginEvent(load_log, LoadLog::TYPE_SSL_CONNECT); if (Init() != OK) { NOTREACHED() << "Couldn't initialize nss"; } int rv = InitializeSSLOptions(); if (rv != OK) { LoadLog::EndEvent(load_log, LoadLog::TYPE_SSL_CONNECT); return rv; } GotoState(STATE_HANDSHAKE); rv = DoHandshakeLoop(OK); if (rv == ERR_IO_PENDING) { user_connect_callback_ = callback; load_log_ = load_log; } else { LoadLog::EndEvent(load_log, LoadLog::TYPE_SSL_CONNECT); } LeaveFunction(""); 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. } // Tell NSS who we're connected to PRNetAddr peername; socklen_t len = sizeof(PRNetAddr); int err = transport_->GetPeerName((struct sockaddr *)&peername, &len); if (err) { DLOG(ERROR) << "GetPeerName failed"; // TODO(wtc): Change GetPeerName to return a network error code. return ERR_UNEXPECTED; } memio_SetPeerName(nss_fd_, &peername); // 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) { 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) return ERR_UNEXPECTED; rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SSL2, ssl_config_.ssl2_enabled); if (rv != SECSuccess) return ERR_UNEXPECTED; // SNI is enabled automatically if TLS is enabled -- as long as // SSL_V2_COMPATIBLE_HELLO isn't. // So don't do V2 compatible hellos unless we're really using SSL2, // to avoid errors like // "common name `mail.google.com' != requested host name `gmail.com'" rv = SSL_OptionSet(nss_fd_, SSL_V2_COMPATIBLE_HELLO, ssl_config_.ssl2_enabled); if (rv != SECSuccess) return ERR_UNEXPECTED; rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SSL3, ssl_config_.ssl3_enabled); if (rv != SECSuccess) return ERR_UNEXPECTED; rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_TLS, ssl_config_.tls1_enabled); if (rv != SECSuccess) return ERR_UNEXPECTED; #ifdef SSL_ENABLE_SESSION_TICKETS // Support RFC 5077 rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SESSION_TICKETS, PR_TRUE); if (rv != SECSuccess) LOG(INFO) << "SSL_ENABLE_SESSION_TICKETS failed. Old system nss?"; #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) LOG(INFO) << "SSL_ENABLE_DEFLATE failed. Old system nss?"; #endif #ifdef SSL_ENABLE_RENEGOTIATION // We allow servers to request renegotiation. Since we're a client, // prohibiting this is rather a waste of time. Only servers are in a position // to prevent renegotiation attacks. // http://extendedsubset.com/?p=8 // // This should be changed when NSS 3.12.6 comes out with support for the // renegotiation info extension. // http://code.google.com/p/chromium/issues/detail?id=31647 rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_RENEGOTIATION, SSL_RENEGOTIATE_UNRESTRICTED); if (rv != SECSuccess) LOG(INFO) << "SSL_ENABLE_RENEGOTIATION failed."; #endif #ifdef SSL_NEXT_PROTO_NEGOTIATED if (!ssl_config_.next_protos.empty()) { rv = SSL_SetNextProtoNego( nss_fd_, reinterpret_cast<const unsigned char *>(ssl_config_.next_protos.data()), ssl_config_.next_protos.size()); if (rv != SECSuccess) LOG(INFO) << "SSL_SetNextProtoNego failed."; } #endif rv = SSL_OptionSet(nss_fd_, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE); if (rv != SECSuccess) return ERR_UNEXPECTED; rv = SSL_AuthCertificateHook(nss_fd_, OwnAuthCertHandler, this); if (rv != SECSuccess) return ERR_UNEXPECTED; rv = SSL_GetClientAuthDataHook(nss_fd_, ClientAuthHandler, this); if (rv != SECSuccess) return ERR_UNEXPECTED; rv = SSL_HandshakeCallback(nss_fd_, HandshakeCallback, this); if (rv != SECSuccess) return ERR_UNEXPECTED; // Tell SSL the hostname we're trying to connect to. SSL_SetURL(nss_fd_, hostname_.c_str()); // Set the peer ID for session reuse. This is necessary when we create an // SSL tunnel through a proxy -- GetPeerName returns the proxy's address // rather than the destination server's address in that case. // TODO(wtc): port in peername is not the server's port when a proxy is used. std::string peer_id = StringPrintf("%s:%d", hostname_.c_str(), PR_ntohs(PR_NetAddrInetPort(&peername))); rv = SSL_SetSockPeerID(nss_fd_, const_cast<char*>(peer_id.c_str())); if (rv != SECSuccess) LOG(INFO) << "SSL_SetSockPeerID failed: peer_id=" << peer_id; // Tell SSL we're a client; needed if not letting NSPR do socket I/O SSL_ResetHandshake(nss_fd_, 0); return OK; } void SSLClientSocketNSS::InvalidateSessionIfBadCertificate() { if (UpdateServerCert() != NULL && ssl_config_.IsAllowedBadCert(server_cert_)) { SSL_InvalidateSession(nss_fd_); } } void SSLClientSocketNSS::Disconnect() { EnterFunction(""); // TODO(wtc): Send SSL close_notify alert. if (nss_fd_ != NULL) { InvalidateSessionIfBadCertificate(); PR_Close(nss_fd_); nss_fd_ = NULL; } // Shut down anything that may call us back (through buffer_send_callback_, // buffer_recv_callback, or handshake_io_callback_). verifier_.reset(); transport_->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; } server_cert_verify_result_.Reset(); completed_handshake_ = false; nss_bufs_ = NULL; client_certs_.clear(); client_auth_cert_needed_ = false; LeaveFunction(""); } bool SSLClientSocketNSS::IsConnected() const { // Ideally, we should also check if we have received the close_notify alert // message from the server, and return false in that case. We're not doing // that, so this function may return a false positive. Since the upper // layer (HttpNetworkTransaction) needs to handle a persistent connection // closed by the server when we send a request anyway, a false positive in // exchange for simpler code is a good trade-off. EnterFunction(""); bool ret = completed_handshake_ && transport_->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_->IsConnectedAndIdle() // returns the desired false when we receive close_notify. EnterFunction(""); bool ret = completed_handshake_ && transport_->IsConnectedAndIdle(); LeaveFunction(""); return ret; } int SSLClientSocketNSS::GetPeerName(struct sockaddr* name, socklen_t* namelen) { return transport_->GetPeerName(name, namelen); } int SSLClientSocketNSS::Read(IOBuffer* buf, int buf_len, CompletionCallback* callback) { EnterFunction(buf_len); DCHECK(completed_handshake_); DCHECK(next_handshake_state_ == STATE_NONE); 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; 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); DCHECK(completed_handshake_); DCHECK(next_handshake_state_ == STATE_NONE); DCHECK(!user_write_callback_); DCHECK(!user_connect_callback_); DCHECK(!user_write_buf_); DCHECK(nss_bufs_); user_write_buf_ = buf; user_write_buf_len_ = buf_len; int rv = DoWriteLoop(OK); if (rv == ERR_IO_PENDING) user_write_callback_ = callback; else { user_write_buf_ = NULL; user_write_buf_len_ = 0; } LeaveFunction(rv); return rv; } bool SSLClientSocketNSS::SetReceiveBufferSize(int32 size) { return transport_->SetReceiveBufferSize(size); } bool SSLClientSocketNSS::SetSendBufferSize(int32 size) { return transport_->SetSendBufferSize(size); } X509Certificate *SSLClientSocketNSS::UpdateServerCert() { // We set the server_cert_ from OwnAuthCertHandler(), but this handler // does not necessarily get called if we are continuing a cached SSL // session. if (server_cert_ == NULL) { server_cert_nss_ = SSL_PeerCertificate(nss_fd_); if (server_cert_nss_) { #if defined(OS_WIN) // TODO(wtc): close cert_store_ at shutdown. if (!cert_store_) cert_store_ = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, 0, NULL); PCCERT_CONTEXT cert_context = NULL; BOOL ok = CertAddEncodedCertificateToStore( cert_store_, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, server_cert_nss_->derCert.data, server_cert_nss_->derCert.len, CERT_STORE_ADD_USE_EXISTING, &cert_context); DCHECK(ok); server_cert_ = X509Certificate::CreateFromHandle( cert_context, X509Certificate::SOURCE_FROM_NETWORK); // Add each of the intermediate certificates in the server's chain to // the server's X509Certificate object. This makes them available to // X509Certificate::Verify() for chain building. // TODO(wtc): Since X509Certificate::CreateFromHandle may return a // cached X509Certificate object, we may be adding intermediate CA // certificates to it repeatedly! CERTCertList* cert_list = CERT_GetCertChainFromCert( server_cert_nss_, PR_Now(), certUsageSSLCA); if (cert_list) { for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list); !CERT_LIST_END(node, cert_list); node = CERT_LIST_NEXT(node)) { cert_context = NULL; ok = CertAddEncodedCertificateToStore( cert_store_, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, node->cert->derCert.data, node->cert->derCert.len, CERT_STORE_ADD_USE_EXISTING, &cert_context); DCHECK(ok); if (node->cert != server_cert_nss_) server_cert_->AddIntermediateCertificate(cert_context); } CERT_DestroyCertList(cert_list); } #else server_cert_ = X509Certificate::CreateFromHandle( CERT_DupCertificate(server_cert_nss_), X509Certificate::SOURCE_FROM_NETWORK); #endif } } return server_cert_; } void SSLClientSocketNSS::GetSSLInfo(SSLInfo* ssl_info) { EnterFunction(""); ssl_info->Reset(); if (!server_cert_) return; 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) { SSLCipherSuiteInfo cipher_info; ok = SSL_GetCipherSuiteInfo(channel_info.cipherSuite, &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 " << channel_info.cipherSuite; } UpdateServerCert(); } ssl_info->cert_status = server_cert_verify_result_.cert_status; DCHECK(server_cert_ != NULL); ssl_info->cert = server_cert_; LeaveFunction(""); } void SSLClientSocketNSS::GetSSLCertRequestInfo( SSLCertRequestInfo* cert_request_info) { EnterFunction(""); cert_request_info->host_and_port = hostname_; 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) 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::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(""); } // As part of Connect(), the SSLClientSocketNSS object performs an SSL // handshake. This requires network IO, which in turn calls // BufferRecvComplete() with a non-zero byte count. This byte count eventually // winds its way through the state machine and ends up being passed to the // callback. For Read() and Write(), that's what we want. But for Connect(), // the caller expects OK (i.e. 0) for success. // void SSLClientSocketNSS::DoConnectCallback(int rv) { EnterFunction(rv); DCHECK_NE(rv, ERR_IO_PENDING); DCHECK(user_connect_callback_); CompletionCallback* c = user_connect_callback_; user_connect_callback_ = NULL; c->Run(rv > OK ? OK : rv); LeaveFunction(""); } void SSLClientSocketNSS::OnHandshakeIOComplete(int result) { EnterFunction(result); int rv = DoHandshakeLoop(result); if (rv != ERR_IO_PENDING) { LoadLog::EndEvent(load_log_, net::LoadLog::TYPE_SSL_CONNECT); load_log_ = NULL; DoConnectCallback(rv); } LeaveFunction(""); } void SSLClientSocketNSS::OnSendComplete(int result) { EnterFunction(result); if (next_handshake_state_ != STATE_NONE) { // In handshake phase. OnHandshakeIOComplete(result); LeaveFunction(""); return; } // OnSendComplete may need to call DoPayloadRead while the renegotiation // handshake is in progress. int rv_read = ERR_IO_PENDING; int rv_write = ERR_IO_PENDING; bool network_moved; do { if (user_read_buf_) rv_read = DoPayloadRead(); if (user_write_buf_) rv_write = DoPayloadWrite(); network_moved = DoTransportIO(); } while (rv_read == ERR_IO_PENDING && rv_write == ERR_IO_PENDING && network_moved); if (user_read_buf_ && rv_read != ERR_IO_PENDING) DoReadCallback(rv_read); if (user_write_buf_ && rv_write != ERR_IO_PENDING) DoWriteCallback(rv_write); LeaveFunction(""); } void SSLClientSocketNSS::OnRecvComplete(int result) { EnterFunction(result); if (next_handshake_state_ != STATE_NONE) { // In handshake phase. OnHandshakeIOComplete(result); LeaveFunction(""); return; } // Network layer received some data, check if client requested to read // decrypted data. if (!user_read_buf_) { LeaveFunction(""); return; } int rv = DoReadLoop(result); if (rv != ERR_IO_PENDING) DoReadCallback(rv); LeaveFunction(""); } // Map a Chromium net error code to an NSS error code. // See _MD_unix_map_default_error in the NSS source // tree for inspiration. static PRErrorCode MapErrorToNSS(int result) { if (result >=0) return result; switch (result) { case ERR_IO_PENDING: return PR_WOULD_BLOCK_ERROR; case ERR_ACCESS_DENIED: // For connect, this could be mapped to PR_ADDRESS_NOT_SUPPORTED_ERROR. return PR_NO_ACCESS_RIGHTS_ERROR; case ERR_INTERNET_DISCONNECTED: // Equivalent to ENETDOWN. return PR_NETWORK_UNREACHABLE_ERROR; // Best approximation. case ERR_CONNECTION_TIMED_OUT: case ERR_TIMED_OUT: return PR_IO_TIMEOUT_ERROR; case ERR_CONNECTION_RESET: return PR_CONNECT_RESET_ERROR; case ERR_CONNECTION_ABORTED: return PR_CONNECT_ABORTED_ERROR; case ERR_CONNECTION_REFUSED: return PR_CONNECT_REFUSED_ERROR; case ERR_ADDRESS_UNREACHABLE: return PR_HOST_UNREACHABLE_ERROR; // Also PR_NETWORK_UNREACHABLE_ERROR. case ERR_ADDRESS_INVALID: return PR_ADDRESS_NOT_AVAILABLE_ERROR; default: LOG(WARNING) << "MapErrorToNSS " << result << " mapped to PR_UNKNOWN_ERROR"; return PR_UNKNOWN_ERROR; } } // 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; int nsent = 0; EnterFunction(""); // nss_bufs_ is a circular buffer. It may have two contiguous parts // (before and after the wrap). So this for loop needs two iterations. for (int i = 0; i < 2; ++i) { const char* buf; int nb = memio_GetWriteParams(nss_bufs_, &buf); if (!nb) break; scoped_refptr<IOBuffer> send_buffer = new IOBuffer(nb); memcpy(send_buffer->data(), buf, nb); int rv = transport_->Write(send_buffer, nb, &buffer_send_callback_); if (rv == ERR_IO_PENDING) { transport_send_busy_ = true; break; } else { memio_PutWriteResult(nss_bufs_, MapErrorToNSS(rv)); if (rv < 0) { // Return the error even if the previous Write succeeded. nsent = rv; break; } nsent += rv; } } LeaveFunction(nsent); return nsent; } void SSLClientSocketNSS::BufferSendComplete(int result) { EnterFunction(result); 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_->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; int rv = last_io_result; do { // Default to STATE_NONE for next state. // (This is a quirk carried over from the windows // implementation. It makes reading the logs a bit harder.) // State handlers can and often do call GotoState just // to stay in the current state. State state = next_handshake_state_; GotoState(STATE_NONE); switch (state) { case STATE_NONE: // we're just pumping data between the buffer and the network break; case STATE_HANDSHAKE: rv = DoHandshake(); break; case STATE_VERIFY_CERT: DCHECK(rv == OK); rv = DoVerifyCert(rv); break; case STATE_VERIFY_CERT_COMPLETE: rv = DoVerifyCertComplete(rv); break; default: rv = ERR_UNEXPECTED; NOTREACHED() << "unexpected state"; break; } // Do the actual network I/O network_moved = DoTransportIO(); } while ((rv != ERR_IO_PENDING || network_moved) && next_handshake_state_ != STATE_NONE); LeaveFunction(""); return rv; } int SSLClientSocketNSS::DoReadLoop(int result) { EnterFunction(""); DCHECK(completed_handshake_); DCHECK(next_handshake_state_ == STATE_NONE); if (result < 0) return result; if (!nss_bufs_) return ERR_UNEXPECTED; bool network_moved; int rv; do { rv = DoPayloadRead(); network_moved = DoTransportIO(); } while (rv == ERR_IO_PENDING && network_moved); LeaveFunction(""); return rv; } int SSLClientSocketNSS::DoWriteLoop(int result) { EnterFunction(""); DCHECK(completed_handshake_); DCHECK(next_handshake_state_ == STATE_NONE); if (result < 0) return result; if (!nss_bufs_) return ERR_UNEXPECTED; bool network_moved; int rv; do { rv = DoPayloadWrite(); network_moved = DoTransportIO(); } while (rv == ERR_IO_PENDING && network_moved); LeaveFunction(""); 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) { // Tell NSS to not verify the certificate. return SECSuccess; } // 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) { #if defined(OS_WIN) // Not implemented. Send no client certificate. PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); return SECFailure; #else SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); that->client_auth_cert_needed_ = !that->ssl_config_.send_client_cert; CERTCertificate* cert = NULL; SECKEYPrivateKey* privkey = NULL; void* wincx = SSL_RevealPinArg(socket); // Second pass: a client certificate should have been selected. if (that->ssl_config_.send_client_cert) { if (that->ssl_config_.client_cert) { cert = CERT_DupCertificate( that->ssl_config_.client_cert->os_cert_handle()); 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; } CERTCertNicknames* names = CERT_GetCertNicknames( CERT_GetDefaultCertDB(), SEC_CERT_NICKNAMES_USER, wincx); if (names) { for (int i = 0; i < names->numnicknames; ++i) { cert = CERT_FindUserCertByUsage( CERT_GetDefaultCertDB(), names->nicknames[i], certUsageSSLClient, PR_FALSE, wincx); if (!cert) continue; // Only check unexpired certs. if (CERT_CheckCertValidTimes(cert, PR_Now(), PR_TRUE) == secCertTimeValid && NSS_CmpCertChainWCANames(cert, ca_names) == SECSuccess) { privkey = PK11_FindKeyByAnyCert(cert, wincx); if (privkey) { X509Certificate* x509_cert = X509Certificate::CreateFromHandle( cert, X509Certificate::SOURCE_LONE_CERT_IMPORT); that->client_certs_.push_back(x509_cert); SECKEY_DestroyPrivateKey(privkey); continue; } } CERT_DestroyCertificate(cert); } CERT_FreeNicknames(names); } return SECFailure; #endif } // static // NSS calls this when handshake is completed. // After the SSL handshake is finished, use CertVerifier to verify // the saved server certificate. void SSLClientSocketNSS::HandshakeCallback(PRFileDesc* socket, void* arg) { SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); that->UpdateServerCert(); } int SSLClientSocketNSS::DoHandshake() { EnterFunction(""); int net_error = net::OK; SECStatus rv = SSL_ForceHandshake(nss_fd_); if (client_auth_cert_needed_) { net_error = ERR_SSL_CLIENT_AUTH_CERT_NEEDED; // 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) { // SSL handshake is completed. Let's verify the certificate. GotoState(STATE_VERIFY_CERT); // Done! } else { PRErrorCode prerr = PR_GetError(); net_error = MapHandshakeError(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; } } LeaveFunction(""); return net_error; } int SSLClientSocketNSS::DoVerifyCert(int result) { DCHECK(server_cert_); GotoState(STATE_VERIFY_CERT_COMPLETE); 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 CertVerifier); return verifier_->Verify(server_cert_, hostname_, flags, &server_cert_verify_result_, &handshake_io_callback_); } // Derived from AuthCertificateCallback() in // mozilla/source/security/manager/ssl/src/nsNSSCallbacks.cpp. int SSLClientSocketNSS::DoVerifyCertComplete(int result) { DCHECK(verifier_.get()); verifier_.reset(); if (result == OK) { // Remember the intermediate CA certs if the server sends them to us. // // 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. CERTCertList* cert_list = CERT_GetCertChainFromCert( server_cert_nss_, PR_Now(), certUsageSSLCA); if (cert_list) { for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list); !CERT_LIST_END(node, cert_list); node = CERT_LIST_NEXT(node)) { if (node->cert->slot || node->cert->isRoot || node->cert->isperm || node->cert == server_cert_nss_) { // Some certs we don't want to remember are: // - found on a token. // - the root cert. // - already stored in perm db. // - the server cert itself. continue; } // We have found a CA cert that we want to remember. // TODO(wtc): Remember the intermediate CA certs in a std::set // temporarily (http://crbug.com/15630). } CERT_DestroyCertList(cert_list); } } // 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_)) { LOG(INFO) << "accepting bad SSL certificate, as user told us to"; result = OK; } completed_handshake_ = true; // TODO(ukai): we may not need this call because it is now harmless to have an // session with a bad cert. InvalidateSessionIfBadCertificate(); // 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(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(""); return ERR_SSL_CLIENT_AUTH_CERT_NEEDED; } 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(""); return MapNSPRError(prerr); } 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) { return ERR_IO_PENDING; } LeaveFunction(""); return MapNSPRError(prerr); } } // namespace net