summaryrefslogtreecommitdiffstats
path: root/net/socket/ssl_client_socket_nss.cc
diff options
context:
space:
mode:
authorwillchan@chromium.org <willchan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-22 23:26:44 +0000
committerwillchan@chromium.org <willchan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-22 23:26:44 +0000
commitf7984fc67f3c88b6ff1c738700a8229f387d732d (patch)
tree094f6be7633d60b0413370462bf6bd04b906ac00 /net/socket/ssl_client_socket_nss.cc
parent8c1be4e0311d52f07fe16fc091862957757dc002 (diff)
downloadchromium_src-f7984fc67f3c88b6ff1c738700a8229f387d732d.zip
chromium_src-f7984fc67f3c88b6ff1c738700a8229f387d732d.tar.gz
chromium_src-f7984fc67f3c88b6ff1c738700a8229f387d732d.tar.bz2
Move socket related files from net/base to net/socket.
Review URL: http://codereview.chromium.org/144009 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@18985 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/socket/ssl_client_socket_nss.cc')
-rw-r--r--net/socket/ssl_client_socket_nss.cc820
1 files changed, 820 insertions, 0 deletions
diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc
new file mode 100644
index 0000000..786f897
--- /dev/null
+++ b/net/socket/ssl_client_socket_nss.cc
@@ -0,0 +1,820 @@
+// 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 GetDefaultCertNickname(), derived from
+// nsNSSCertificate::defaultServerNickName()
+// in mozilla/security/manager/ssl/src/nsNSSCertificate.cpp
+// and 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"
+
+#include <certdb.h>
+#include <nspr.h>
+#include <nss.h>
+#include <secerr.h>
+// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=455424
+// until NSS 3.12.2 comes out and we update to it.
+#define Lock FOO_NSS_Lock
+#include <ssl.h>
+#include <sslerr.h>
+#include <pk11pub.h>
+#undef Lock
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/nss_init.h"
+#include "base/string_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/ssl_info.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_state_ = s
+#define LogData(s, len)
+#else
+#define EnterFunction(x) LOG(INFO) << (void *)this << " " << __FUNCTION__ << \
+ " enter " << x << "; next_state " << next_state_
+#define LeaveFunction(x) LOG(INFO) << (void *)this << " " << __FUNCTION__ << \
+ " leave " << x << "; next_state " << next_state_
+#define GotoState(s) do { LOG(INFO) << (void *)this << " " << __FUNCTION__ << \
+ " jump to state " << s; next_state_ = s; } while (0)
+#define LogData(s, len) LOG(INFO) << (void *)this << " " << __FUNCTION__ << \
+ " data [" << std::string(s, len) << "]";
+
+#endif
+
+namespace {
+
+// Gets default certificate nickname from cert.
+// Derived from nsNSSCertificate::defaultServerNickname
+// in mozilla/security/manager/ssl/src/nsNSSCertificate.cpp.
+std::string GetDefaultCertNickname(
+ net::X509Certificate::OSCertHandle cert) {
+ if (cert == NULL)
+ return "";
+
+ char* name = CERT_GetCommonName(&cert->subject);
+ if (!name) {
+ // Certs without common names are strange, but they do exist...
+ // Let's try to use another string for the nickname
+ name = CERT_GetOrgUnitName(&cert->subject);
+ if (!name)
+ name = CERT_GetOrgName(&cert->subject);
+ if (!name)
+ name = CERT_GetLocalityName(&cert->subject);
+ if (!name)
+ name = CERT_GetStateName(&cert->subject);
+ if (!name)
+ name = CERT_GetCountryName(&cert->subject);
+ if (!name)
+ return "";
+ }
+ int count = 1;
+ std::string nickname;
+ while (1) {
+ if (count == 1) {
+ nickname = name;
+ } else {
+ nickname = StringPrintf("%s #%d", name, count);
+ }
+ PRBool conflict = SEC_CertNicknameConflict(
+ const_cast<char*>(nickname.c_str()), &cert->derSubject, cert->dbhandle);
+ if (!conflict)
+ break;
+ count++;
+ }
+ PR_FREEIF(name);
+ return nickname;
+}
+
+int NetErrorFromNSPRError(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 SSL_ERROR_NO_CYPHER_OVERLAP:
+ return ERR_SSL_VERSION_OR_CIPHER_MISMATCH;
+ case SSL_ERROR_BAD_CERT_DOMAIN:
+ return ERR_CERT_COMMON_NAME_INVALID;
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ return ERR_CERT_DATE_INVALID;
+ case SEC_ERROR_BAD_SIGNATURE:
+ return ERR_CERT_INVALID;
+ case SSL_ERROR_REVOKED_CERT_ALERT:
+ case SEC_ERROR_REVOKED_CERTIFICATE:
+ case SEC_ERROR_REVOKED_KEY:
+ return ERR_CERT_REVOKED;
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ return ERR_CERT_AUTHORITY_INVALID;
+
+ default: {
+ if (IS_SSL_ERROR(err)) {
+ LOG(WARNING) << "Unknown SSL error " << err <<
+ " mapped to net::ERR_SSL_PROTOCOL_ERROR";
+ return ERR_SSL_PROTOCOL_ERROR;
+ }
+ if (IS_SEC_ERROR(err)) {
+ // TODO(port): Probably not the best mapping
+ LOG(WARNING) << "Unknown SEC error " << err <<
+ " mapped to net::ERR_CERT_INVALID";
+ return ERR_CERT_INVALID;
+ }
+ LOG(WARNING) << "Unknown error " << err <<
+ " mapped to net::ERR_FAILED";
+ return ERR_FAILED;
+ }
+ }
+}
+
+} // namespace
+
+bool SSLClientSocketNSS::nss_options_initialized_ = false;
+
+SSLClientSocketNSS::SSLClientSocketNSS(ClientSocket* transport_socket,
+ const std::string& hostname,
+ const SSLConfig& ssl_config)
+ :
+ buffer_send_callback_(this, &SSLClientSocketNSS::BufferSendComplete),
+ buffer_recv_callback_(this, &SSLClientSocketNSS::BufferRecvComplete),
+ transport_send_busy_(false),
+ transport_recv_busy_(false),
+ io_callback_(this, &SSLClientSocketNSS::OnIOComplete),
+ transport_(transport_socket),
+ hostname_(hostname),
+ ssl_config_(ssl_config),
+ user_callback_(NULL),
+ user_buf_len_(0),
+ completed_handshake_(false),
+ next_state_(STATE_NONE),
+ nss_fd_(NULL),
+ nss_bufs_(NULL) {
+ EnterFunction("");
+}
+
+SSLClientSocketNSS::~SSLClientSocketNSS() {
+ EnterFunction("");
+ Disconnect();
+ LeaveFunction("");
+}
+
+int SSLClientSocketNSS::Init() {
+ EnterFunction("");
+ // Call NSS_NoDB_Init() in a threadsafe way.
+ base::EnsureNSSInit();
+
+ LeaveFunction("");
+ return OK;
+}
+
+// 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.
+//
+// The ConnectCallbackWrapper object changes the argument that gets passed
+// to the callback function. Any positive value gets turned into OK.
+class ConnectCallbackWrapper :
+ public CompletionCallbackImpl<ConnectCallbackWrapper> {
+ public:
+ explicit ConnectCallbackWrapper(CompletionCallback* user_callback)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(
+ CompletionCallbackImpl<ConnectCallbackWrapper>(this,
+ &ConnectCallbackWrapper::ReturnValueWrapper)),
+ user_callback_(user_callback) {
+ }
+
+ private:
+ void ReturnValueWrapper(int rv) {
+ user_callback_->Run(rv > OK ? OK : rv);
+ delete this;
+ }
+
+ CompletionCallback* user_callback_;
+};
+
+int SSLClientSocketNSS::Connect(CompletionCallback* callback) {
+ EnterFunction("");
+ DCHECK(transport_.get());
+ DCHECK(next_state_ == STATE_NONE);
+ DCHECK(!user_callback_);
+
+ if (Init() != OK) {
+ NOTREACHED() << "Couldn't initialize nss";
+ }
+
+ // 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 9999; // TODO(port): real error
+ }
+
+ // 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";
+ return 9999; // TODO(port): real error
+ }
+ 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_SSL_PROTOCOL_ERROR; // TODO(port): real error
+ }
+ // 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
+
+ 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_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());
+
+ // Tell SSL we're a client; needed if not letting NSPR do socket I/O
+ SSL_ResetHandshake(nss_fd_, 0);
+
+ GotoState(STATE_HANDSHAKE_READ);
+ rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = new ConnectCallbackWrapper(callback);
+
+ LeaveFunction("");
+ return rv > OK ? OK : rv;
+}
+
+void SSLClientSocketNSS::InvalidateSessionIfBadCertificate() {
+ if (UpdateServerCert() != NULL &&
+ ssl_config_.allowed_bad_certs_.count(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;
+ }
+
+ transport_->Disconnect();
+
+ // Reset object state
+ transport_send_busy_ = false;
+ transport_recv_busy_ = false;
+ user_buf_ = NULL;
+ user_buf_len_ = 0;
+ server_cert_ = NULL;
+ server_cert_verify_result_.Reset();
+ completed_handshake_ = false;
+ nss_bufs_ = NULL;
+
+ 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::Read(IOBuffer* buf, int buf_len,
+ CompletionCallback* callback) {
+ EnterFunction(buf_len);
+ DCHECK(completed_handshake_);
+ DCHECK(next_state_ == STATE_NONE);
+ DCHECK(!user_callback_);
+ DCHECK(!user_buf_);
+
+ user_buf_ = buf;
+ user_buf_len_ = buf_len;
+
+ GotoState(STATE_PAYLOAD_READ);
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ LeaveFunction(rv);
+ return rv;
+}
+
+int SSLClientSocketNSS::Write(IOBuffer* buf, int buf_len,
+ CompletionCallback* callback) {
+ EnterFunction(buf_len);
+ DCHECK(completed_handshake_);
+ DCHECK(next_state_ == STATE_NONE);
+ DCHECK(!user_callback_);
+ DCHECK(!user_buf_);
+
+ user_buf_ = buf;
+ user_buf_len_ = buf_len;
+
+ GotoState(STATE_PAYLOAD_WRITE);
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ LeaveFunction(rv);
+ return rv;
+}
+
+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) {
+ X509Certificate::OSCertHandle nss_cert = SSL_PeerCertificate(nss_fd_);
+ if (nss_cert) {
+ server_cert_ = X509Certificate::CreateFromHandle(
+ nss_cert, X509Certificate::SOURCE_FROM_NETWORK);
+ }
+ }
+ 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) {
+ // TODO(wtc): implement this.
+}
+
+void SSLClientSocketNSS::DoCallback(int rv) {
+ EnterFunction(rv);
+ DCHECK(rv != ERR_IO_PENDING);
+ DCHECK(user_callback_);
+
+ // since Run may result in Read being called, clear user_callback_ up front.
+ CompletionCallback* c = user_callback_;
+ user_callback_ = NULL;
+ user_buf_ = NULL;
+ c->Run(rv);
+ LeaveFunction("");
+}
+
+void SSLClientSocketNSS::OnIOComplete(int result) {
+ EnterFunction(result);
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING && user_callback_ != NULL)
+ DoCallback(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;
+ // TODO(port): add real table
+ LOG(ERROR) << "MapErrorToNSS " << result;
+ return PR_UNKNOWN_ERROR;
+}
+
+// Do network I/O between the given buffer and the given socket.
+// 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;
+
+ const char *buf;
+ int nb = memio_GetWriteParams(nss_bufs_, &buf);
+ EnterFunction(nb);
+
+ int rv;
+ if (!nb) {
+ rv = OK;
+ } else {
+ scoped_refptr<IOBuffer> send_buffer = new IOBuffer(nb);
+ memcpy(send_buffer->data(), buf, nb);
+ rv = transport_->Write(send_buffer, nb, &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);
+ memio_PutWriteResult(nss_bufs_, result);
+ transport_send_busy_ = false;
+ OnIOComplete(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_, result);
+ transport_recv_busy_ = false;
+ OnIOComplete(result);
+ LeaveFunction("");
+}
+
+int SSLClientSocketNSS::DoLoop(int last_io_result) {
+ EnterFunction(last_io_result);
+ bool network_moved;
+ int rv = last_io_result;
+ do {
+ network_moved = false;
+ // 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_state_;
+ GotoState(STATE_NONE);
+ switch (state) {
+ case STATE_NONE:
+ // we're just pumping data between the buffer and the network
+ break;
+ case STATE_HANDSHAKE_READ:
+ rv = DoHandshakeRead();
+ break;
+ case STATE_VERIFY_CERT:
+ DCHECK(rv == OK);
+ rv = DoVerifyCert(rv);
+ break;
+ case STATE_VERIFY_CERT_COMPLETE:
+ rv = DoVerifyCertComplete(rv);
+ break;
+ case STATE_PAYLOAD_READ:
+ rv = DoPayloadRead();
+ break;
+ case STATE_PAYLOAD_WRITE:
+ rv = DoPayloadWrite();
+ break;
+ default:
+ rv = ERR_UNEXPECTED;
+ NOTREACHED() << "unexpected state";
+ break;
+ }
+
+ // Do the actual network I/O
+ if (nss_bufs_ != NULL) {
+ int nsent = BufferSend();
+ int nreceived = BufferRecv();
+ network_moved = (nsent > 0 || nreceived >= 0);
+ }
+ } while ((rv != ERR_IO_PENDING || network_moved) &&
+ next_state_ != STATE_NONE);
+ 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 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::DoHandshakeRead() {
+ EnterFunction("");
+ int net_error = net::OK;
+ int rv = SSL_ForceHandshake(nss_fd_);
+
+ if (rv == SECSuccess) {
+ // SSL handshake is completed. Let's verify the certificate.
+ GotoState(STATE_VERIFY_CERT);
+ // Done!
+ } else {
+ PRErrorCode prerr = PR_GetError();
+
+ // If the server closed on us, it is a protocol error.
+ // Some TLS-intolerant servers do this when we request TLS.
+ if (prerr == PR_END_OF_FILE_ERROR) {
+ net_error = ERR_SSL_PROTOCOL_ERROR;
+ } else {
+ net_error = NetErrorFromNSPRError(prerr);
+ }
+
+ // If not done, stay in this state
+ if (net_error == ERR_IO_PENDING) {
+ GotoState(STATE_HANDSHAKE_READ);
+ } 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);
+ return verifier_.Verify(server_cert_, hostname_,
+ ssl_config_.rev_checking_enabled,
+ &server_cert_verify_result_, &io_callback_);
+}
+
+// Derived from AuthCertificateCallback() in
+// mozilla/source/security/manager/ssl/src/nsNSSCallbacks.cpp.
+int SSLClientSocketNSS::DoVerifyCertComplete(int result) {
+ if (result == OK) {
+ // Remember the intermediate CA certs if the server sends them to us.
+ CERTCertList* cert_list = CERT_GetCertChainFromCert(
+ server_cert_->os_cert_handle(), 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_->os_cert_handle()) {
+ // 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.
+ std::string nickname(GetDefaultCertNickname(node->cert));
+ if (!nickname.empty()) {
+ PK11SlotInfo* slot = PK11_GetInternalKeySlot();
+ if (slot) {
+ PK11_ImportCert(slot, node->cert, CK_INVALID_HANDLE,
+ const_cast<char*>(nickname.c_str()), PR_FALSE);
+ PK11_FreeSlot(slot);
+ }
+ }
+ }
+ 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 might be able to store the cert's status along with
+ // the cert in the allowed_bad_certs_ set.
+ if (IsCertificateError(result) &&
+ ssl_config_.allowed_bad_certs_.count(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 DoLoop and return the result to the caller to Connect.
+ DCHECK(next_state_ == STATE_NONE);
+ return result;
+}
+
+int SSLClientSocketNSS::DoPayloadRead() {
+ EnterFunction(user_buf_len_);
+ int rv = PR_Read(nss_fd_, user_buf_->data(), user_buf_len_);
+ if (rv >= 0) {
+ LogData(user_buf_->data(), rv);
+ user_buf_ = NULL;
+ LeaveFunction("");
+ return rv;
+ }
+ PRErrorCode prerr = PR_GetError();
+ if (prerr == PR_WOULD_BLOCK_ERROR) {
+ GotoState(STATE_PAYLOAD_READ);
+ LeaveFunction("");
+ return ERR_IO_PENDING;
+ }
+ user_buf_ = NULL;
+ LeaveFunction("");
+ return NetErrorFromNSPRError(prerr);
+}
+
+int SSLClientSocketNSS::DoPayloadWrite() {
+ EnterFunction(user_buf_len_);
+ int rv = PR_Write(nss_fd_, user_buf_->data(), user_buf_len_);
+ if (rv >= 0) {
+ LogData(user_buf_->data(), rv);
+ user_buf_ = NULL;
+ LeaveFunction("");
+ return rv;
+ }
+ PRErrorCode prerr = PR_GetError();
+ if (prerr == PR_WOULD_BLOCK_ERROR) {
+ GotoState(STATE_PAYLOAD_WRITE);
+ return ERR_IO_PENDING;
+ }
+ user_buf_ = NULL;
+ LeaveFunction("");
+ return NetErrorFromNSPRError(prerr);
+}
+
+} // namespace net