| Index: net/base/ssl_client_socket_nss.cc
|
| ===================================================================
|
| --- net/base/ssl_client_socket_nss.cc (revision 0)
|
| +++ net/base/ssl_client_socket_nss.cc (revision 0)
|
| @@ -0,0 +1,395 @@
|
| +// Copyright (c) 2006-2008 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.
|
| +
|
| +#include "net/base/ssl_client_socket_nss.h"
|
| +
|
| +#include <nspr.h>
|
| +#include <nss.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 <pk11pub.h>
|
| +#undef Lock
|
| +
|
| +#include "base/logging.h"
|
| +#include "base/nss_init.h"
|
| +#include "base/string_util.h"
|
| +#include "net/base/net_errors.h"
|
| +#include "net/base/ssl_info.h"
|
| +
|
| +static const int kRecvBufferSize = 4096;
|
| +
|
| +/*
|
| + * nss calls this if an incoming certificate is invalid.
|
| + * TODO(port): expose to app via GetSSLInfo so it can put up
|
| + * the appropriate GUI and retry with override if desired
|
| + */
|
| +static SECStatus
|
| +ownBadCertHandler(void * arg, PRFileDesc * socket)
|
| +{
|
| + PRErrorCode err = PR_GetError();
|
| + LOG(ERROR) << "server certificate is invalid; NSS error code " << err;
|
| + // Return SECSuccess to override the problem, SECFailure to let the original function fail
|
| + return SECSuccess; /* override, say it's OK. */
|
| +}
|
| +
|
| +
|
| +namespace net {
|
| +
|
| +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_(NULL),
|
| + user_buf_len_(0),
|
| + completed_handshake_(false),
|
| + next_state_(STATE_NONE),
|
| + nss_fd_(NULL),
|
| + nss_bufs_(NULL) {
|
| +}
|
| +
|
| +SSLClientSocketNSS::~SSLClientSocketNSS() {
|
| + Disconnect();
|
| +}
|
| +
|
| +int SSLClientSocketNSS::Init() {
|
| + // Call NSS_NoDB_Init() in a threadsafe way.
|
| + base::EnsureNSSInit();
|
| +
|
| + return OK;
|
| +}
|
| +
|
| +int SSLClientSocketNSS::Connect(CompletionCallback* callback) {
|
| + DCHECK(transport_.get());
|
| + DCHECK(next_state_ == STATE_NONE);
|
| + DCHECK(!user_callback_);
|
| +
|
| + next_state_ = STATE_CONNECT;
|
| + int rv = DoLoop(OK);
|
| + if (rv == ERR_IO_PENDING)
|
| + user_callback_ = callback;
|
| + return rv;
|
| +}
|
| +
|
| +int SSLClientSocketNSS::ReconnectIgnoringLastError(CompletionCallback* callback) {
|
| + // TODO(darin): implement me!
|
| + return ERR_FAILED;
|
| +}
|
| +
|
| +void SSLClientSocketNSS::Disconnect() {
|
| + // TODO(wtc): Send SSL close_notify alert.
|
| + if (nss_fd_ != NULL) {
|
| + PR_Close(nss_fd_);
|
| + nss_fd_ = NULL;
|
| + }
|
| + completed_handshake_ = false;
|
| + transport_->Disconnect();
|
| +}
|
| +
|
| +bool SSLClientSocketNSS::IsConnected() const {
|
| + return completed_handshake_ && transport_->IsConnected();
|
| +}
|
| +
|
| +int SSLClientSocketNSS::Read(char* buf, int buf_len,
|
| + CompletionCallback* callback) {
|
| + DCHECK(completed_handshake_);
|
| + DCHECK(next_state_ == STATE_NONE);
|
| + DCHECK(!user_callback_);
|
| +
|
| + user_buf_ = buf;
|
| + user_buf_len_ = buf_len;
|
| +
|
| + next_state_ = STATE_PAYLOAD_READ;
|
| + int rv = DoLoop(OK);
|
| + if (rv == ERR_IO_PENDING)
|
| + user_callback_ = callback;
|
| + return rv;
|
| +}
|
| +
|
| +int SSLClientSocketNSS::Write(const char* buf, int buf_len,
|
| + CompletionCallback* callback) {
|
| + DCHECK(completed_handshake_);
|
| + DCHECK(next_state_ == STATE_NONE);
|
| + DCHECK(!user_callback_);
|
| +
|
| + user_buf_ = const_cast<char*>(buf);
|
| + user_buf_len_ = buf_len;
|
| +
|
| + next_state_ = STATE_PAYLOAD_WRITE;
|
| + int rv = DoLoop(OK);
|
| + if (rv == ERR_IO_PENDING)
|
| + user_callback_ = callback;
|
| + return rv;
|
| +}
|
| +
|
| +void SSLClientSocketNSS::GetSSLInfo(SSLInfo* ssl_info) {
|
| + // TODO(port): implement!
|
| + ssl_info->Reset();
|
| +}
|
| +
|
| +void SSLClientSocketNSS::DoCallback(int 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;
|
| + c->Run(rv);
|
| +}
|
| +
|
| +void SSLClientSocketNSS::OnIOComplete(int result) {
|
| + int rv = DoLoop(result);
|
| + if (rv != ERR_IO_PENDING)
|
| + DoCallback(rv);
|
| +}
|
| +
|
| +// 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);
|
| +
|
| + int rv;
|
| + if (!nb) {
|
| + rv = OK;
|
| + } else {
|
| + rv = transport_->Write(buf, nb, &buffer_send_callback_);
|
| + if (rv == ERR_IO_PENDING)
|
| + transport_send_busy_ = true;
|
| + else
|
| + memio_PutWriteResult(nss_bufs_, MapErrorToNSS(rv));
|
| + }
|
| +
|
| + return rv;
|
| +}
|
| +
|
| +void SSLClientSocketNSS::BufferSendComplete(int result) {
|
| + memio_PutWriteResult(nss_bufs_, result);
|
| + transport_send_busy_ = false;
|
| + OnIOComplete(result);
|
| +}
|
| +
|
| +
|
| +int SSLClientSocketNSS::BufferRecv(void) {
|
| +
|
| + if (transport_recv_busy_) return ERR_IO_PENDING;
|
| +
|
| + char *buf;
|
| + int nb = memio_GetReadParams(nss_bufs_, &buf);
|
| + int rv;
|
| + if (!nb) {
|
| + // buffer too full to read into, so no I/O possible at moment
|
| + rv = ERR_IO_PENDING;
|
| + } else {
|
| + rv = transport_->Read(buf, nb, &buffer_recv_callback_);
|
| + if (rv == ERR_IO_PENDING)
|
| + transport_recv_busy_ = true;
|
| + else
|
| + memio_PutReadResult(nss_bufs_, MapErrorToNSS(rv));
|
| + }
|
| +
|
| + return rv;
|
| +}
|
| +
|
| +void SSLClientSocketNSS::BufferRecvComplete(int result) {
|
| + memio_PutReadResult(nss_bufs_, result);
|
| + transport_recv_busy_ = false;
|
| + OnIOComplete(result);
|
| +}
|
| +
|
| +
|
| +int SSLClientSocketNSS::DoLoop(int last_io_result) {
|
| + DCHECK(next_state_ != STATE_NONE);
|
| + bool network_moved;
|
| + int rv = last_io_result;
|
| + do {
|
| + network_moved = false;
|
| + State state = next_state_;
|
| + //DLOG(INFO) << "DoLoop state " << state;
|
| + next_state_ = STATE_NONE;
|
| + switch (state) {
|
| + case STATE_CONNECT:
|
| + rv = DoConnect();
|
| + break;
|
| + case STATE_CONNECT_COMPLETE:
|
| + rv = DoConnectComplete(rv);
|
| + break;
|
| + case STATE_HANDSHAKE_READ:
|
| + rv = DoHandshakeRead();
|
| + 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);
|
| + return rv;
|
| +}
|
| +
|
| +int SSLClientSocketNSS::DoConnect() {
|
| + next_state_ = STATE_CONNECT_COMPLETE;
|
| + return transport_->Connect(&io_callback_);
|
| +}
|
| +
|
| +int SSLClientSocketNSS::DoConnectComplete(int result) {
|
| + if (result < 0)
|
| + return result;
|
| +
|
| + 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;
|
| +
|
| + 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_SSL3, ssl_config_.tls1_enabled);
|
| + if (rv != SECSuccess)
|
| + return ERR_UNEXPECTED;
|
| +
|
| + rv = SSL_OptionSet(nss_fd_, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
|
| + if (rv != SECSuccess)
|
| + return ERR_UNEXPECTED;
|
| +
|
| + rv = SSL_BadCertHook(nss_fd_, ownBadCertHandler, NULL);
|
| + 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);
|
| + next_state_ = STATE_HANDSHAKE_READ;
|
| + // Return OK so DoLoop tries handshaking
|
| + return OK;
|
| +}
|
| +
|
| +int SSLClientSocketNSS::DoHandshakeRead() {
|
| + int rv = SSL_ForceHandshake(nss_fd_);
|
| + if (rv == SECSuccess) {
|
| + // there's a callback for this, too
|
| + completed_handshake_ = true;
|
| + // Indicate we're ready to handle I/O. Badly named?
|
| + next_state_ = STATE_NONE;
|
| + return OK;
|
| + }
|
| + PRErrorCode prerr = PR_GetError();
|
| + if (prerr == PR_WOULD_BLOCK_ERROR) {
|
| + // at this point, it should have tried to send some bytes
|
| + next_state_ = STATE_HANDSHAKE_READ;
|
| + return ERR_IO_PENDING;
|
| + }
|
| + // TODO: map rv to net error code properly
|
| + return ERR_SSL_PROTOCOL_ERROR;
|
| +}
|
| +
|
| +int SSLClientSocketNSS::DoPayloadRead() {
|
| + int rv = PR_Read(nss_fd_, user_buf_, user_buf_len_);
|
| + if (rv >= 0)
|
| + return rv;
|
| + PRErrorCode prerr = PR_GetError();
|
| + if (prerr == PR_WOULD_BLOCK_ERROR) {
|
| + next_state_ = STATE_PAYLOAD_READ;
|
| + return ERR_IO_PENDING;
|
| + }
|
| + // TODO: map rv to net error code properly
|
| + return ERR_SSL_PROTOCOL_ERROR;
|
| +}
|
| +
|
| +int SSLClientSocketNSS::DoPayloadWrite() {
|
| + int rv = PR_Write(nss_fd_, user_buf_, user_buf_len_);
|
| + if (rv >= 0)
|
| + return rv;
|
| + PRErrorCode prerr = PR_GetError();
|
| + if (prerr == PR_WOULD_BLOCK_ERROR) {
|
| + next_state_ = STATE_PAYLOAD_WRITE;
|
| + return ERR_IO_PENDING;
|
| + }
|
| + // TODO: map rv to net error code properly
|
| + return ERR_SSL_PROTOCOL_ERROR;
|
| +}
|
| +
|
| +} // namespace net
|
| +
|
|
|