Chromium Code Reviews| Index: chrome/browser/extensions/api/socket/ssl_socket.cc |
| diff --git a/chrome/browser/extensions/api/socket/ssl_socket.cc b/chrome/browser/extensions/api/socket/ssl_socket.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..511e32c945d203f435c212e2c4d983c9ec58c272 |
| --- /dev/null |
| +++ b/chrome/browser/extensions/api/socket/ssl_socket.cc |
| @@ -0,0 +1,325 @@ |
| +// Copyright (c) 2012 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 "chrome/browser/extensions/api/socket/ssl_socket.h" |
| + |
| +#include "chrome/browser/extensions/api/api_resource.h" |
| +#include "chrome/browser/extensions/api/api_resource_event_notifier.h" |
| +#include "net/base/address_list.h" |
| +#include "net/base/cert_verifier.h" |
| +#include "net/base/host_port_pair.h" |
| +#include "net/base/ip_endpoint.h" |
| +#include "net/base/net_errors.h" |
| +#include "net/base/rand_callback.h" |
| +#include "net/base/ssl_config_service.h" |
| +#include "net/socket/client_socket_factory.h" |
| +#include "net/socket/client_socket_handle.h" |
| +#include "net/socket/tcp_client_socket.h" |
| + |
| +// SSLSocket starts out as a TCP socket in SSLSTATE_NONE. When a handshake |
| +// request is made, it transitions to SSLSTATE_WAIT. If there are no pending |
| +// read or write requests on the TCP socket, an SSL client socket is then |
| +// created and asked to perform the handshake. Otherwise, SSLSocket |
| +// continues to attempt to start the handshake after every pending read |
| +// and write request completes. While in SSLSTATE_WAIT, no new read or |
| +// write requests may be issued (and are replied to with an immediate |
| +// net::ERR_IO_PENDING). |
| +// |
| +// If the SSL handshake succeeds, SSLSocket enters SSLSTATE_CONNECTED |
| +// and all read and write requests go over the secure channel. If |
| +// the SSL handshake fails, SSLSocket enters SSLSTATE_ERROR and no |
| +// further read or write requests may be made until the socket is |
| +// closed. |
| + |
| +namespace extensions { |
| + |
| +SSLSocket::SSLSocket(extensions::APIResourceEventNotifier* event_notifier) |
| + : Socket(event_notifier), |
| + ssl_state_(SSLSTATE_NONE) { |
| +} |
| + |
| +// For testing. |
| +SSLSocket::SSLSocket(net::TCPClientSocket* tcp_client_socket, |
| + APIResourceEventNotifier* event_notifier) |
| + : Socket(event_notifier), |
| + socket_(tcp_client_socket), |
| + ssl_state_(SSLSTATE_NONE) { |
| +} |
| + |
| +// static |
| +SSLSocket* SSLSocket::CreateSocketForTesting( |
| + net::TCPClientSocket* tcp_client_socket, |
| + APIResourceEventNotifier* event_notifier) { |
| + return new SSLSocket(tcp_client_socket, event_notifier); |
| +} |
| + |
| +SSLSocket::~SSLSocket() { |
| + if (is_connected_) { |
| + Disconnect(); |
| + } |
| +} |
| + |
| +void SSLSocket::Connect(const std::string& address, |
| + int port, |
| + const CompletionCallback& callback) { |
| + DCHECK(!callback.is_null()); |
| + |
| + if (!connect_callback_.is_null()) { |
| + callback.Run(net::ERR_CONNECTION_FAILED); |
| + return; |
| + } |
| + connect_callback_ = callback; |
| + |
| + int result = net::ERR_CONNECTION_FAILED; |
| + do { |
| + if (is_connected_) |
| + break; |
| + |
| + net::AddressList address_list; |
| + if (!StringAndPortToAddressList(address, port, &address_list)) { |
| + result = net::ERR_ADDRESS_INVALID; |
| + break; |
| + } |
| + |
| + socket_.reset(new net::TCPClientSocket(address_list, NULL, |
| + net::NetLog::Source())); |
| + connect_callback_ = callback; |
| + result = socket_->Connect(base::Bind( |
| + &SSLSocket::OnConnectComplete, base::Unretained(this))); |
| + } while (false); |
| + |
| + if (result != net::ERR_IO_PENDING) |
| + OnConnectComplete(result); |
| +} |
| + |
| +void SSLSocket::PerformSSLHandshake(const std::string& ssl_hostname, |
| + int ssl_port, |
| + const CompletionCallback& callback) { |
| + DCHECK(!callback.is_null()); |
| + |
| + if (ssl_state_ == SSLSTATE_WAIT) { |
| + callback.Run(net::ERR_IO_PENDING); |
| + return; |
| + } else if (ssl_state_ == SSLSTATE_ERROR) { |
| + callback.Run(net::ERR_FAILED); |
| + return; |
| + } else if (ssl_state_ == SSLSTATE_CONNECTED) { |
| + callback.Run(net::ERR_FAILED); |
| + return; |
| + } |
| + DCHECK(ssl_state_ == SSLSTATE_NONE); |
| + |
| + handshake_callback_ = callback; |
| + ssl_state_ = SSLSTATE_WAIT; |
| + ssl_port_ = ssl_port; |
| + ssl_hostname_ = ssl_hostname; |
| + |
| + AttemptSSLHandshake(); |
| +} |
| + |
| +void SSLSocket::AttemptSSLHandshake() { |
| + DCHECK(ssl_state_ == SSLSTATE_WAIT); |
| + |
| + // We have to wait until outstanding I/O requests are |
| + // complete before we perform the SSL handshake. |
| + if (!read_callback_.is_null()) { |
| + VLOG(1) << "AttemptSSLHandshake aborting (pending callback)."; |
| + return; |
| + } |
| + |
| + if (!write_queue_empty()) { |
| + VLOG(1) << "AttemptSSLHandshake aborting (pending writes)."; |
| + return; |
| + } |
| + |
| + if (!socket_.get() || !socket_->IsConnected()) { |
| + OnHandshakeComplete(net::ERR_SOCKET_NOT_CONNECTED); |
| + return; |
| + } |
| + |
| + VLOG(2) << "AttemptSSLHandshake running handshake."; |
| + |
| + // see remoting/jingle_glue/ssl_socket_adapter.cc |
| + ssl_socket_.reset(); |
| + cert_verifier_.reset(net::CertVerifier::CreateDefault()); |
| + |
| + // We'll use the default configuration to avoid issues wrt |
| + // thread-safety and SSLConfigService. |
| + net::SSLConfig ssl_config; |
|
Ryan Sleevi
2012/07/09 20:55:45
This is not a good pattern. You should use the Pro
|
| + net::SSLClientSocketContext context; |
| + context.cert_verifier = cert_verifier_.get(); |
| + |
| + // ClientSocketHandle::set_socket takes ownership of the socket... |
| + net::ClientSocketHandle* socket_handle = new net::ClientSocketHandle(); |
| + socket_handle->set_socket(socket_.release()); |
| + |
| + // ... and the SSL socket takes ownership of the socket handle. |
| + ssl_socket_.reset( |
| + net::ClientSocketFactory::GetDefaultFactory()->CreateSSLClientSocket( |
| + socket_handle, |
| + net::HostPortPair(ssl_hostname_, ssl_port_), |
| + ssl_config, |
| + context)); |
| + |
| + int result = ssl_socket_->Connect(base::Bind( |
| + &SSLSocket::OnHandshakeComplete, base::Unretained(this))); |
| + |
| + if (result != net::ERR_IO_PENDING) { |
| + OnHandshakeComplete(result); |
| + } |
| +} |
| + |
| +void SSLSocket::Disconnect() { |
| + if (ssl_socket_.get()) { |
| + ssl_socket_->Disconnect(); |
| + } else if (socket_.get()) { |
| + socket_->Disconnect(); |
| + } |
| + |
| + ssl_state_ = SSLSTATE_NONE; |
| + is_connected_ = false; |
| +} |
| + |
| +int SSLSocket::Bind(const std::string& address, int port) { |
| + // TODO(penghuang): Supports bind for tcp? |
| + return net::ERR_FAILED; |
| +} |
| + |
| +void SSLSocket::Read(int count, |
| + const ReadCompletionCallback& callback) { |
| + DCHECK(!callback.is_null()); |
| + |
| + if (!read_callback_.is_null() || ssl_state_ == SSLSTATE_WAIT) { |
| + callback.Run(net::ERR_IO_PENDING, NULL); |
| + return; |
| + } else if (ssl_state_ == SSLSTATE_ERROR) { |
| + callback.Run(net::ERR_FAILED, NULL); |
| + return; |
| + } else { |
| + read_callback_ = callback; |
| + } |
| + |
| + int result = net::ERR_FAILED; |
| + scoped_refptr<net::IOBuffer> io_buffer; |
| + do { |
| + if (count < 0) { |
| + result = net::ERR_INVALID_ARGUMENT; |
| + break; |
| + } |
| + |
| + if (ssl_state_ == SSLSTATE_CONNECTED) { |
| + if (!ssl_socket_.get() || !ssl_socket_->IsConnected()) { |
| + result = net::ERR_SOCKET_NOT_CONNECTED; |
| + break; |
| + } |
| + DVLOG(2) << "Reading from SSL socket."; |
| + io_buffer = new net::IOBuffer(count); |
| + result = ssl_socket_->Read(io_buffer.get(), count, |
| + base::Bind(&SSLSocket::OnReadComplete, base::Unretained(this), |
| + io_buffer)); |
| + } else { |
| + if (!socket_.get() || !socket_->IsConnected()) { |
| + result = net::ERR_SOCKET_NOT_CONNECTED; |
| + break; |
| + } |
| + DVLOG(2) << "Reading from normal socket."; |
| + io_buffer = new net::IOBuffer(count); |
| + result = socket_->Read(io_buffer.get(), count, |
| + base::Bind(&SSLSocket::OnReadComplete, base::Unretained(this), |
| + io_buffer)); |
| + } |
| + } while (false); |
| + |
| + if (result != net::ERR_IO_PENDING) |
| + OnReadComplete(io_buffer, result); |
| +} |
| + |
| +void SSLSocket::RecvFrom(int count, |
| + const RecvFromCompletionCallback& callback) { |
| + callback.Run(net::ERR_FAILED, NULL, NULL, 0); |
| +} |
| + |
| +void SSLSocket::SendTo(scoped_refptr<net::IOBuffer> io_buffer, |
| + int byte_count, |
| + const std::string& address, |
| + int port, |
| + const CompletionCallback& callback) { |
| + callback.Run(net::ERR_FAILED); |
| +} |
| + |
| +bool SSLSocket::SetKeepAlive(bool enable, int delay) { |
| + if (!socket_.get()) |
| + return false; |
| + return socket_->SetKeepAlive(enable, delay); |
| +} |
| + |
| +bool SSLSocket::SetNoDelay(bool no_delay) { |
| + if (!socket_.get()) |
| + return false; |
| + return socket_->SetNoDelay(no_delay); |
| +} |
| + |
| +int SSLSocket::WriteImpl(net::IOBuffer* io_buffer, |
| + int io_buffer_size, |
| + const net::CompletionCallback& callback) { |
| + if (ssl_state_ == SSLSTATE_CONNECTED) { |
| + if (!ssl_socket_.get() || !ssl_socket_->IsConnected()) |
| + return net::ERR_SOCKET_NOT_CONNECTED; |
| + DVLOG(2) << "Writing " << io_buffer_size << "B to SSL socket."; |
| + return ssl_socket_->Write(io_buffer, io_buffer_size, callback); |
| + } else if (ssl_state_ == SSLSTATE_NONE) { |
| + if (!socket_.get() || !socket_->IsConnected()) |
| + return net::ERR_SOCKET_NOT_CONNECTED; |
| + DVLOG(2) << "Writing " << io_buffer_size << "B to normal socket."; |
| + return socket_->Write(io_buffer, io_buffer_size, callback); |
| + } else if (ssl_state_ == SSLSTATE_WAIT && socket_.get()) { |
| + // Write dregs out to the insecure socket. We'll complete the handshake |
| + // once the write queue empties. |
| + if (!socket_.get() || !socket_->IsConnected()) |
| + return net::ERR_SOCKET_NOT_CONNECTED; |
| + DVLOG(2) << "Writing " << io_buffer_size |
| + << "B to normal socket (in SSLSTATE_WAIT)."; |
| + return socket_->Write(io_buffer, io_buffer_size, callback); |
| + } else { |
| + VLOG(1) << "Bad state for writing."; |
| + return net::ERR_FAILED; |
| + } |
| +} |
| + |
| +void SSLSocket::OnConnectComplete(int result) { |
| + DCHECK(!connect_callback_.is_null()); |
| + DCHECK(!is_connected_); |
| + is_connected_ = result == net::OK; |
| + connect_callback_.Run(result); |
| + connect_callback_.Reset(); |
| +} |
| + |
| +void SSLSocket::OnHandshakeComplete(int result) { |
| + DCHECK(!handshake_callback_.is_null()); |
| + DCHECK(ssl_state_ == SSLSTATE_WAIT); |
| + ssl_state_ = result == net::OK ? SSLSTATE_CONNECTED : SSLSTATE_ERROR; |
| + handshake_callback_.Run(result); |
| + handshake_callback_.Reset(); |
| +} |
| + |
| +void SSLSocket::OnReadComplete(scoped_refptr<net::IOBuffer> io_buffer, |
| + int result) { |
| + DCHECK(!read_callback_.is_null()); |
| + read_callback_.Run(result, io_buffer); |
| + read_callback_.Reset(); |
| + DVLOG(2) << "OnReadComplete in state " << ssl_state_; |
| + if (ssl_state_ == SSLSTATE_WAIT) { |
| + AttemptSSLHandshake(); |
| + } |
| +} |
| + |
| +void SSLSocket::OnWriteComplete(int result) { |
| + Socket::OnWriteComplete(result); |
| + DVLOG(2) << "OnWriteComplete in state " << ssl_state_; |
| + if (ssl_state_ == SSLSTATE_WAIT) { |
| + AttemptSSLHandshake(); |
| + } |
| +} |
| + |
| +} // namespace extensions |