diff options
author | avi@google.com <avi@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-10-17 14:49:07 +0000 |
---|---|---|
committer | avi@google.com <avi@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-10-17 14:49:07 +0000 |
commit | b75523f5a51e3684d5be00fbcfd73ddcb610d5ad (patch) | |
tree | c233b949f183ddb81b523b93e21044fb04bfff40 /net | |
parent | c8e5341e7e90be48182c0435462c9afd7780d898 (diff) | |
download | chromium_src-b75523f5a51e3684d5be00fbcfd73ddcb610d5ad.zip chromium_src-b75523f5a51e3684d5be00fbcfd73ddcb610d5ad.tar.gz chromium_src-b75523f5a51e3684d5be00fbcfd73ddcb610d5ad.tar.bz2 |
Initial Mac implementation of SSL. Requires the full-duplex socket introduced with the Linux/NSS implementation of SSL.
Review URL: http://codereview.chromium.org/7090
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@3525 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/base/client_socket_factory.cc | 5 | ||||
-rw-r--r-- | net/base/ssl_client_socket_mac.cc | 634 | ||||
-rw-r--r-- | net/base/ssl_client_socket_mac.h | 106 | ||||
-rw-r--r-- | net/base/ssl_client_socket_unittest.cc | 40 | ||||
-rw-r--r-- | net/net.xcodeproj/project.pbxproj | 10 |
5 files changed, 774 insertions, 21 deletions
diff --git a/net/base/client_socket_factory.cc b/net/base/client_socket_factory.cc index 10f24df..2c6d715 100644 --- a/net/base/client_socket_factory.cc +++ b/net/base/client_socket_factory.cc @@ -8,6 +8,8 @@ #include "build/build_config.h" #if defined(OS_WIN) #include "net/base/ssl_client_socket_win.h" +#elif defined(OS_MACOSX) +#include "net/base/ssl_client_socket_mac.h" #endif #include "net/base/tcp_client_socket.h" @@ -26,8 +28,9 @@ class DefaultClientSocketFactory : public ClientSocketFactory { const SSLConfig& ssl_config) { #if defined(OS_WIN) return new SSLClientSocketWin(transport_socket, hostname, ssl_config); +#elif defined(OS_MACOSX) + return new SSLClientSocketMac(transport_socket, hostname, ssl_config); #else - // TODO(pinkerton): turn on when we port SSL socket from win32 NOTIMPLEMENTED(); return NULL; #endif diff --git a/net/base/ssl_client_socket_mac.cc b/net/base/ssl_client_socket_mac.cc new file mode 100644 index 0000000..e668de3 --- /dev/null +++ b/net/base/ssl_client_socket_mac.cc @@ -0,0 +1,634 @@ +// Copyright (c) 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_mac.h" + +#include "base/singleton.h" +#include "base/string_util.h" +#include "net/base/net_errors.h" +#include "net/base/ssl_info.h" + +// Welcome to Mac SSL. We've been waiting for you. +// +// The Mac SSL implementation is, like the Windows and NSS implementations, a +// giant state machine. This design constraint is due to the asynchronous nature +// of our underlying transport mechanism. We can call down to read/write on the +// network, but what happens is that either it completes immediately or returns +// saying that we'll get a callback sometime in the future. In that case, we +// have to return to our caller but pick up where we left off when we +// resume. Thus the fun. +// +// On Windows, we use Security Contexts, which are driven by us. We fetch data +// from the network, we call the context to decrypt the data, and so on. On the +// Mac, however, we provide Secure Transport with callbacks to get data from the +// network, and it calls us back to fetch the data from the network for +// it. Therefore, there are different sets of states in our respective state +// machines, fewer on the Mac because Secure Transport keeps a lot of its own +// state. The discussion about what each of the states means lives in comments +// in the DoLoop() function. +// +// Secure Transport is designed for use by either blocking or non-blocking +// network I/O. If, for example, you called SSLRead() to fetch data, Secure +// Transport will, unless it has some cached data, issue a read to your network +// callback read function to fetch it some more encrypted data. It's expecting +// one of two things. If your function is hooked up to a blocking source, then +// it'll block pending receipt of the data from the other end. That's fine, as +// when you return with the data, Secure Transport will do its thing. On the +// other hand, suppose that your socket is non-blocking and tells your function +// that it would block. Then you let Secure Transport know, and it'll tell the +// original caller that it would have blocked and that they need to call it +// "later." +// +// When's "later," though? We have fully-asynchronous networking, so we get a +// callback when our data's ready. But Secure Transport has no way for us to +// tell it that data has arrived, so we must re-execute the call that triggered +// the I/O (we rely on our state machine to do this). When we do so Secure +// Transport will ask once again for the data. Chances are that it'll be the +// same request as the previous time, but that's not actually guaranteed. But as +// long as we buffer what we have and keep track of where we were, it works +// quite well. +// +// Except for network writes. They shoot this plan straight to hell. +// +// Faking a blocking connection with an asynchronous connection (theoretically +// more powerful) simply doesn't work for writing. Suppose that Secure Transport +// requests a write of data to the network. With blocking I/O, we'd just block +// until the write completed, and with non-blocking I/O we'd know how many bytes +// we wrote before we would have blocked. But with the asynchronous I/O, the +// transport underneath us can tell us that it'll let us know sometime "later" +// whether or not things succeeded, and how many bytes were written. What do we +// return to Secure Transport? We can't return a byte count, but we can't return +// "later" as we're not guaranteed to be called in the future with the same data +// to write. +// +// So, like in any good relationship, we're forced to lie. Whenever Secure +// Transport asks for data to be written, we take it all and lie about it always +// being written. We spin in a loop (see SSLWriteCallback() and +// OnWriteComplete()) independent of the main state machine writing the data to +// the network, and get the data out. The main consequence of this independence +// from the state machine is that we require a full-duplex transport underneath +// us since we can't use it to keep our reading and writing +// straight. Fortunately, the NSS implementation also has this issue to deal +// with, so we share the same Libevent-based full-duplex TCP socket. +// +// A side comment on return values might be in order. Those who haven't taken +// the time to read the documentation (ahem, header comments) in our various +// files might be a bit surprised to see result values being treated as both +// lengths and errors. Like Shimmer, they are both. In both the case of +// immediate results as well as results returned in callbacks, a negative return +// value indicates an error, a zero return value indicates end-of-stream (for +// reads), and a positive return value indicates the number of bytes read or +// written. Thus, many functions start off with |if (result < 0) return +// result;|. That gets the error condition out of the way, and from that point +// forward the result can be treated as a length. + +namespace net { + +namespace { + +int NetErrorFromOSStatus(OSStatus status) { + switch (status) { + case errSSLWouldBlock: + return ERR_IO_PENDING; + case errSSLIllegalParam: + case errSSLBadCipherSuite: + case errSSLBadConfiguration: + return ERR_INVALID_ARGUMENT; + case errSSLClosedNoNotify: + return ERR_CONNECTION_RESET; + case errSSLConnectionRefused: + return ERR_CONNECTION_REFUSED; + case errSSLClosedAbort: + return ERR_CONNECTION_ABORTED; + case errSSLInternal: + case errSSLCrypto: + case errSSLFatalAlert: + case errSSLProtocol: + return ERR_SSL_PROTOCOL_ERROR; + case errSSLHostNameMismatch: + return ERR_CERT_COMMON_NAME_INVALID; + case errSSLCertExpired: + case errSSLCertNotYetValid: + return ERR_CERT_DATE_INVALID; + case errSSLNoRootCert: + case errSSLUnknownRootCert: + return ERR_CERT_AUTHORITY_INVALID; + case errSSLXCertChainInvalid: + case errSSLBadCert: + return ERR_CERT_INVALID; + case errSSLPeerCertRevoked: + return ERR_CERT_REVOKED; + + case errSSLClosedGraceful: + case noErr: + return OK; + + case errSSLBadRecordMac: + case errSSLBufferOverflow: + case errSSLDecryptionFail: + case errSSLModuleAttach: + case errSSLNegotiation: + case errSSLRecordOverflow: + case errSSLSessionNotFound: + default: + LOG(WARNING) << "Unknown error " << status << + " mapped to net::ERR_FAILED"; + return ERR_FAILED; + } +} + +OSStatus OSStatusFromNetError(int net_error) { + switch (net_error) { + case ERR_IO_PENDING: + return errSSLWouldBlock; + case ERR_INTERNET_DISCONNECTED: + case ERR_TIMED_OUT: + case ERR_CONNECTION_ABORTED: + case ERR_CONNECTION_RESET: + case ERR_CONNECTION_REFUSED: + case ERR_ADDRESS_UNREACHABLE: + case ERR_ADDRESS_INVALID: + return errSSLClosedAbort; + case OK: + return noErr; + default: + LOG(WARNING) << "Unknown error " << net_error << + " mapped to errSSLIllegalParam"; + return errSSLIllegalParam; + } +} + +} // namespace + +//----------------------------------------------------------------------------- + +SSLClientSocketMac::SSLClientSocketMac(ClientSocket* transport_socket, + const std::string& hostname, + const SSLConfig& ssl_config) + : io_callback_(this, &SSLClientSocketMac::OnIOComplete), + write_callback_(this, &SSLClientSocketMac::OnWriteComplete), + transport_(transport_socket), + hostname_(hostname), + ssl_config_(ssl_config), + user_callback_(NULL), + next_state_(STATE_NONE), + next_io_state_(STATE_NONE), + completed_handshake_(false), + ssl_context_(NULL), + pending_send_error_(OK), + recv_buffer_head_slop_(0), + recv_buffer_tail_slop_(0) { +} + +SSLClientSocketMac::~SSLClientSocketMac() { + Disconnect(); +} + +int SSLClientSocketMac::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 SSLClientSocketMac::ReconnectIgnoringLastError( + CompletionCallback* callback) { + // TODO(darin): implement me! + return ERR_FAILED; +} + +void SSLClientSocketMac::Disconnect() { + completed_handshake_ = false; + + if (ssl_context_) { + SSLClose(ssl_context_); + SSLDisposeContext(ssl_context_); + ssl_context_ = NULL; + } + + transport_->Disconnect(); +} + +bool SSLClientSocketMac::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. + return completed_handshake_ && transport_->IsConnected(); +} + +int SSLClientSocketMac::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 SSLClientSocketMac::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 SSLClientSocketMac::GetSSLInfo(SSLInfo* ssl_info) { + // TODO(port): implement! + memset(ssl_info, 0, sizeof(SSLInfo)); +} + +void SSLClientSocketMac::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 SSLClientSocketMac::OnIOComplete(int result) { + if (next_io_state_ != STATE_NONE) { + State next_state = next_state_; + next_state_ = next_io_state_; + next_io_state_ = STATE_NONE; + result = DoLoop(result); + next_state_ = next_state; + } + if (next_state_ != STATE_NONE) { + int rv = DoLoop(result); + if (rv != ERR_IO_PENDING) + DoCallback(rv); + } +} + +// This is the main loop driving the state machine. Most calls coming from the +// outside just set up a few variables and jump into here. +int SSLClientSocketMac::DoLoop(int last_io_result) { + DCHECK(next_state_ != STATE_NONE); + int rv = last_io_result; + do { + State state = next_state_; + next_state_ = STATE_NONE; + switch (state) { + case STATE_CONNECT: + // We must establish a connection to the other side using our + // lower-level transport. + rv = DoConnect(); + break; + case STATE_CONNECT_COMPLETE: + // We have a connection to the other side; initialize our SSL engine. + rv = DoConnectComplete(rv); + break; + case STATE_HANDSHAKE: + // Do the SSL/TLS handshake. + rv = DoHandshake(); + break; + case STATE_READ_COMPLETE: + // A read off the network is complete; do the paperwork. + rv = DoReadComplete(rv); + break; + case STATE_PAYLOAD_READ: + // Do a read of data from the network. + rv = DoPayloadRead(); + break; + case STATE_PAYLOAD_WRITE: + // Do a write of data to the network. + rv = DoPayloadWrite(); + break; + default: + rv = ERR_UNEXPECTED; + NOTREACHED() << "unexpected state"; + break; + } + } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); + return rv; +} + +int SSLClientSocketMac::DoConnect() { + next_state_ = STATE_CONNECT_COMPLETE; + return transport_->Connect(&io_callback_); +} + +int SSLClientSocketMac::DoConnectComplete(int result) { + if (result < 0) + return result; + + OSStatus status = noErr; + + status = SSLNewContext(false, &ssl_context_); + if (status) + return NetErrorFromOSStatus(status); + + status = SSLSetProtocolVersionEnabled(ssl_context_, + kSSLProtocol2, + ssl_config_.ssl2_enabled); + if (status) + return NetErrorFromOSStatus(status); + + status = SSLSetProtocolVersionEnabled(ssl_context_, + kSSLProtocol3, + ssl_config_.ssl3_enabled); + if (status) + return NetErrorFromOSStatus(status); + + status = SSLSetProtocolVersionEnabled(ssl_context_, + kTLSProtocol1, + ssl_config_.tls1_enabled); + if (status) + return NetErrorFromOSStatus(status); + + status = SSLSetIOFuncs(ssl_context_, SSLReadCallback, SSLWriteCallback); + if (status) + return NetErrorFromOSStatus(status); + + status = SSLSetConnection(ssl_context_, this); + if (status) + return NetErrorFromOSStatus(status); + + status = SSLSetPeerDomainName(ssl_context_, hostname_.c_str(), + hostname_.length()); + if (status) + return NetErrorFromOSStatus(status); + + next_state_ = STATE_HANDSHAKE; + return OK; +} + +int SSLClientSocketMac::DoHandshake() { + OSStatus status = SSLHandshake(ssl_context_); + + if (status == errSSLWouldBlock) + next_state_ = STATE_HANDSHAKE; + + if (status == noErr) + completed_handshake_ = true; + + return NetErrorFromOSStatus(status); +} + +int SSLClientSocketMac::DoReadComplete(int result) { + if (result < 0) + return result; + + recv_buffer_tail_slop_ -= result; + + return result; +} + +void SSLClientSocketMac::OnWriteComplete(int result) { + if (result < 0) { + pending_send_error_ = result; + return; + } + + send_buffer_.erase(send_buffer_.begin(), + send_buffer_.begin() + result); + + if (!send_buffer_.empty()) + SSLWriteCallback(this, NULL, NULL); +} + +int SSLClientSocketMac::DoPayloadRead() { + size_t processed; + OSStatus status = SSLRead(ssl_context_, + user_buf_, + user_buf_len_, + &processed); + + // There's a subtle difference here in semantics of the "would block" errors. + // In our code, ERR_IO_PENDING means the whole operation is async, while + // errSSLWouldBlock means that the stream isn't ending (and is often returned + // along with partial data). So even though "would block" is returned, if we + // have data, let's just return it. + + if (processed > 0) { + next_state_ = STATE_NONE; + return processed; + } + + if (status == errSSLWouldBlock) { + next_state_ = STATE_PAYLOAD_READ; + } + + return NetErrorFromOSStatus(status); +} + +int SSLClientSocketMac::DoPayloadWrite() { + size_t processed; + OSStatus status = SSLWrite(ssl_context_, + user_buf_, + user_buf_len_, + &processed); + + if (processed > 0) + return processed; + + return NetErrorFromOSStatus(status); +} + +// Handling the reading from the network is one of those things that should be +// simpler than it is. Ideally, we'd have some kind of ring buffer. For now, a +// std::vector<char> will have to do. +// +// The need for a buffer at all comes from the difference between an +// asynchronous connection (which is what we have) and a non-blocking connection +// (which is what we fake for Secure Transport). When Secure Transport calls us +// to read data, we call our underlying transport, which will likely tell us +// that it'll do a callback. When that happens, we need to tell Secure Transport +// that we've "blocked". When the callback happens, we have a chunk of data that +// we need to feed to Secure Transport, but it's not interested. It'll ask for +// it again when we call it again, so we need to hold on to the data. +// +// Why keep our own buffer? Well, when we execute a read and the underlying +// transport says that it'll do a callback, it keeps the pointer to the +// buffer. We can't pass it the buffer that Secure Transport gave us to fill, as +// we can't guarantee its lifetime. +// +// The basic idea, then, is this: we have a buffer filled with the data that +// we've read from the network but haven't given to Secure Transport +// yet. Whenever we read from the network the first thing we do is ensure we +// have enough room in the buffer for the read. We enlarge the buffer to be big +// enough to hold both our existing data and the new data, and then we mark the +// extra space at the end as "tail slop." Slop is just space at the ends of the +// buffer that's going to be used for data but isn't (yet). A diagram: +// +// +--------------------------------------+--------------------------------+ +// | existing good data ~~~~~~~~~~~~~~~~~ | tail slop area ~~~~~~~~~~~~~~~ | +// +--------------------------------------+--------------------------------+ +// +// When executing a read, we pass a pointer to the beginning of the tail slop +// area (guaranteed to be contiguous space because it's a vector, unlike, say, a +// deque (sigh)) and the size of the tail slop. When we get data (either here in +// SSLReadCallback() or above in DoReadComplete()) we subtract the number of +// bytes received from the tail slop value. That moves those bytes +// (conceptually, not physically) from the tail slop area to the area containing +// real data. +// +// The idea is still pretty simple. We enlarge the tail slop, call our +// underlying network, get data, shrink the slop area to match, copy requested +// data back into our caller's buffer, and delete the data from the head of the +// vector. +// +// Except for a nasty little problem. Asynchronous I/O calls keep the buffer +// pointer. +// +// This leads to the following scenario: we have a few bytes of good data in our +// buffer. But our caller requests more than that. We oblige by enlarging the +// tail slop, and calling our underlying provider, but the provider says that +// it'll call us back later. So we shrug our shoulders, copy what we do have +// into our caller's buffer and... +// +// Wait. We can't delete the data from the head of our vector. That would +// invalidate the pointer that we just gave to our provider. So instead, in that +// case we keep track of where the good data starts by keeping a "head slop" +// value, which just notes what data we've already sent and that is useless to +// us but that we can't delete because we have I/O in flight depending on us +// leaving the buffer alone. +// +// I hear what you're saying. "We need to use a ring buffer!" You write it, +// then, and I'll use it. Here are the features it needs. First, it needs to be +// able to have contiguous segments of arbitrary length attached to it to create +// read buffers. Second, each of those segments must have a "used" length +// indicator, so if it was half-filled by a previous data read, but the next +// data read is for more than there's space left, a new segment can be created +// for the new read without leaving an internal gap. +// +// Get to it. +// +// (sigh) Who am I kidding? TODO(avi): write the aforementioned ring buffer + +// static +OSStatus SSLClientSocketMac::SSLReadCallback(SSLConnectionRef connection, + void* data, + size_t* data_length) { + DCHECK(data); + DCHECK(data_length); + SSLClientSocketMac* us = + const_cast<SSLClientSocketMac*>( + static_cast<const SSLClientSocketMac*>(connection)); + + // If we have I/O in flight, promise we'll get back to them and use the + // existing callback to do so + + if (us->next_io_state_ == STATE_READ_COMPLETE) { + *data_length = 0; + return errSSLWouldBlock; + } + + // Start with what's in the buffer + + size_t total_read = us->recv_buffer_.size() - us->recv_buffer_head_slop_ - + us->recv_buffer_tail_slop_; + + // Resize the buffer if needed + + if (us->recv_buffer_.size() - us->recv_buffer_head_slop_ < *data_length) { + us->recv_buffer_.resize(us->recv_buffer_head_slop_ + *data_length); + us->recv_buffer_tail_slop_ = *data_length - total_read; + } + + int rv = 1; // any old value to spin the loop below + while (rv > 0 && total_read < *data_length) { + rv = us->transport_->Read(&us->recv_buffer_[us->recv_buffer_head_slop_ + + total_read], + us->recv_buffer_tail_slop_, + &us->io_callback_); + + if (rv > 0) { + total_read += rv; + us->recv_buffer_tail_slop_ -= rv; + } + } + + *data_length = total_read; + if (total_read) { + memcpy(data, &us->recv_buffer_[us->recv_buffer_head_slop_], total_read); + if (rv == ERR_IO_PENDING) { + // We have I/O in flight which is going to land in our buffer. We can't + // shuffle things around, so we need to just fiddle with pointers. + us->recv_buffer_head_slop_ += total_read; + } else { + us->recv_buffer_.erase(us->recv_buffer_.begin(), + us->recv_buffer_.begin() + + total_read + + us->recv_buffer_head_slop_); + us->recv_buffer_head_slop_ = 0; + } + } + + if (rv == ERR_IO_PENDING) { + us->next_io_state_ = STATE_READ_COMPLETE; + } + + if (rv < 0) + return OSStatusFromNetError(rv); + + return noErr; +} + +// static +OSStatus SSLClientSocketMac::SSLWriteCallback(SSLConnectionRef connection, + const void* data, + size_t* data_length) { + SSLClientSocketMac* us = + const_cast<SSLClientSocketMac*>( + static_cast<const SSLClientSocketMac*>(connection)); + + if (us->pending_send_error_ != OK) { + OSStatus status = OSStatusFromNetError(us->pending_send_error_); + us->pending_send_error_ = OK; + return status; + } + + if (data) + us->send_buffer_.insert(us->send_buffer_.end(), + static_cast<const char*>(data), + static_cast<const char*>(data) + *data_length); + int rv; + do { + rv = us->transport_->Write(&us->send_buffer_[0], + us->send_buffer_.size(), + &us->write_callback_); + if (rv > 0) { + us->send_buffer_.erase(us->send_buffer_.begin(), + us->send_buffer_.begin() + rv); + + } + } while (rv > 0 && !us->send_buffer_.empty()); + + if (rv < 0 && rv != ERR_IO_PENDING) { + return OSStatusFromNetError(rv); + } + + // always lie to our caller + return noErr; +} + +} // namespace net + diff --git a/net/base/ssl_client_socket_mac.h b/net/base/ssl_client_socket_mac.h new file mode 100644 index 0000000..21eb769 --- /dev/null +++ b/net/base/ssl_client_socket_mac.h @@ -0,0 +1,106 @@ +// 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. + +#ifndef NET_BASE_SSL_CLIENT_SOCKET_MAC_H_ +#define NET_BASE_SSL_CLIENT_SOCKET_MAC_H_ + +#include <Security/Security.h> + +#include <string> +#include <vector> + +#include "base/scoped_ptr.h" +#include "net/base/completion_callback.h" +#include "net/base/ssl_client_socket.h" +#include "net/base/ssl_config_service.h" + +namespace net { + +// An SSL client socket implemented with Secure Transport. +class SSLClientSocketMac : public SSLClientSocket { + public: + // Takes ownership of the transport_socket, which may already be connected. + // The given hostname will be compared with the name(s) in the server's + // certificate during the SSL handshake. ssl_config specifies the SSL + // settings. + SSLClientSocketMac(ClientSocket* transport_socket, + const std::string& hostname, + const SSLConfig& ssl_config); + ~SSLClientSocketMac(); + + // SSLClientSocket methods: + virtual void GetSSLInfo(SSLInfo* ssl_info); + + // ClientSocket methods: + virtual int Connect(CompletionCallback* callback); + virtual int ReconnectIgnoringLastError(CompletionCallback* callback); + virtual void Disconnect(); + virtual bool IsConnected() const; + + // Socket methods: + virtual int Read(char* buf, int buf_len, CompletionCallback* callback); + virtual int Write(const char* buf, int buf_len, CompletionCallback* callback); + + private: + void DoCallback(int result); + void OnIOComplete(int result); + + int DoLoop(int last_io_result); + int DoConnect(); + int DoConnectComplete(int result); + int DoPayloadRead(); + int DoPayloadWrite(); + int DoHandshake(); + int DoReadComplete(int result); + void OnWriteComplete(int result); + + static OSStatus SSLReadCallback(SSLConnectionRef connection, + void* data, + size_t* data_length); + static OSStatus SSLWriteCallback(SSLConnectionRef connection, + const void* data, + size_t* data_length); + + CompletionCallbackImpl<SSLClientSocketMac> io_callback_; + CompletionCallbackImpl<SSLClientSocketMac> write_callback_; + + scoped_ptr<ClientSocket> transport_; + std::string hostname_; + SSLConfig ssl_config_; + + CompletionCallback* user_callback_; + + // Used by both Read and Write functions. + char* user_buf_; + int user_buf_len_; + + enum State { + STATE_NONE, + STATE_CONNECT, + STATE_CONNECT_COMPLETE, + STATE_PAYLOAD_READ, + STATE_PAYLOAD_WRITE, + STATE_HANDSHAKE, + STATE_READ_COMPLETE, + }; + State next_state_; + State next_io_state_; + + bool completed_handshake_; + SSLContextRef ssl_context_; + + // These are buffers for holding data during I/O. The "slop" is the amount of + // space at the ends of the receive buffer that are allocated for holding data + // but don't (yet). + std::vector<char> send_buffer_; + int pending_send_error_; + std::vector<char> recv_buffer_; + int recv_buffer_head_slop_; + int recv_buffer_tail_slop_; +}; + +} // namespace net + +#endif // NET_BASE_SSL_CLIENT_SOCKET_MAC_H_ + diff --git a/net/base/ssl_client_socket_unittest.cc b/net/base/ssl_client_socket_unittest.cc index d1f1f82..032ed4a 100644 --- a/net/base/ssl_client_socket_unittest.cc +++ b/net/base/ssl_client_socket_unittest.cc @@ -2,10 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/platform_test.h" #include "net/base/address_list.h" #include "net/base/client_socket_factory.h" #include "net/base/host_resolver.h" #include "net/base/net_errors.h" +#include "net/base/scoped_host_mapper.h" #include "net/base/ssl_client_socket.h" #include "net/base/ssl_config_service.h" #include "net/base/tcp_client_socket.h" @@ -14,22 +16,22 @@ //----------------------------------------------------------------------------- -namespace { - const net::SSLConfig kDefaultSSLConfig; -class SSLClientSocketTest : public testing::Test { +class SSLClientSocketTest : public PlatformTest { public: SSLClientSocketTest() : socket_factory_(net::ClientSocketFactory::GetDefaultFactory()) { + // TODO(darin): kill this exception once we have a way to test out the + // TCPClientSocket class using loopback connections. + host_mapper_.AddRule("bugs.webkit.org", "bugs.webkit.org"); } - + protected: + net::ScopedHostMapper host_mapper_; net::ClientSocketFactory* socket_factory_; }; -} // namespace - //----------------------------------------------------------------------------- // bug 1354783 @@ -70,10 +72,10 @@ TEST_F(SSLClientSocketTest, DISABLED_Read) { std::string hostname = "bugs.webkit.org"; int rv = resolver.Resolve(hostname, 443, &addr, &callback); - EXPECT_EQ(rv, net::ERR_IO_PENDING); + EXPECT_EQ(net::ERR_IO_PENDING, rv); rv = callback.WaitForResult(); - EXPECT_EQ(rv, net::OK); + EXPECT_EQ(net::OK, rv); scoped_ptr<net::SSLClientSocket> sock( socket_factory_->CreateSSLClientSocket(new net::TCPClientSocket(addr), @@ -81,10 +83,10 @@ TEST_F(SSLClientSocketTest, DISABLED_Read) { rv = sock->Connect(&callback); if (rv != net::OK) { - ASSERT_EQ(rv, net::ERR_IO_PENDING); + ASSERT_EQ(net::ERR_IO_PENDING, rv); rv = callback.WaitForResult(); - EXPECT_EQ(rv, net::OK); + EXPECT_EQ(net::OK, rv); } const char request_text[] = "GET / HTTP/1.0\r\n\r\n"; @@ -93,7 +95,7 @@ TEST_F(SSLClientSocketTest, DISABLED_Read) { if (rv == net::ERR_IO_PENDING) { rv = callback.WaitForResult(); - EXPECT_EQ(rv, arraysize(request_text) - 1); + EXPECT_EQ(static_cast<int>(arraysize(request_text) - 1), rv); } char buf[4096]; @@ -118,7 +120,7 @@ TEST_F(SSLClientSocketTest, DISABLED_Read_SmallChunks) { std::string hostname = "bugs.webkit.org"; int rv = resolver.Resolve(hostname, 443, &addr, NULL); - EXPECT_EQ(rv, net::OK); + EXPECT_EQ(net::OK, rv); scoped_ptr<net::SSLClientSocket> sock( socket_factory_->CreateSSLClientSocket(new net::TCPClientSocket(addr), @@ -126,10 +128,10 @@ TEST_F(SSLClientSocketTest, DISABLED_Read_SmallChunks) { rv = sock->Connect(&callback); if (rv != net::OK) { - ASSERT_EQ(rv, net::ERR_IO_PENDING); + ASSERT_EQ(net::ERR_IO_PENDING, rv); rv = callback.WaitForResult(); - EXPECT_EQ(rv, net::OK); + EXPECT_EQ(net::OK, rv); } const char request_text[] = "GET / HTTP/1.0\r\n\r\n"; @@ -138,7 +140,7 @@ TEST_F(SSLClientSocketTest, DISABLED_Read_SmallChunks) { if (rv == net::ERR_IO_PENDING) { rv = callback.WaitForResult(); - EXPECT_EQ(rv, arraysize(request_text) - 1); + EXPECT_EQ(static_cast<int>(arraysize(request_text) - 1), rv); } char buf[1]; @@ -163,7 +165,7 @@ TEST_F(SSLClientSocketTest, DISABLED_Read_Interrupted) { std::string hostname = "bugs.webkit.org"; int rv = resolver.Resolve(hostname, 443, &addr, NULL); - EXPECT_EQ(rv, net::OK); + EXPECT_EQ(net::OK, rv); scoped_ptr<net::SSLClientSocket> sock( socket_factory_->CreateSSLClientSocket(new net::TCPClientSocket(addr), @@ -171,10 +173,10 @@ TEST_F(SSLClientSocketTest, DISABLED_Read_Interrupted) { rv = sock->Connect(&callback); if (rv != net::OK) { - ASSERT_EQ(rv, net::ERR_IO_PENDING); + ASSERT_EQ(net::ERR_IO_PENDING, rv); rv = callback.WaitForResult(); - EXPECT_EQ(rv, net::OK); + EXPECT_EQ(net::OK, rv); } const char request_text[] = "GET / HTTP/1.0\r\n\r\n"; @@ -183,7 +185,7 @@ TEST_F(SSLClientSocketTest, DISABLED_Read_Interrupted) { if (rv == net::ERR_IO_PENDING) { rv = callback.WaitForResult(); - EXPECT_EQ(rv, arraysize(request_text) - 1); + EXPECT_EQ(static_cast<int>(arraysize(request_text) - 1), rv); } // Do a partial read and then exit. This test should not crash! diff --git a/net/net.xcodeproj/project.pbxproj b/net/net.xcodeproj/project.pbxproj index a594020..99f8673 100644 --- a/net/net.xcodeproj/project.pbxproj +++ b/net/net.xcodeproj/project.pbxproj @@ -142,6 +142,8 @@ 821F23C30E5E105E003C7E38 /* url_request_about_job.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BED33A20E5A198600A747DB /* url_request_about_job.cc */; }; 821F23CA0E5E106B003C7E38 /* url_request_job.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BED33980E5A198600A747DB /* url_request_job.cc */; }; 821F23CC0E5E106D003C7E38 /* url_request_simple_job.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BED339C0E5A198600A747DB /* url_request_simple_job.cc */; }; + 8220FABD0E914ACA008170A9 /* ssl_client_socket_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BED32950E5A181C00A747DB /* ssl_client_socket_unittest.cc */; }; + 8220FAFC0E915561008170A9 /* ssl_client_socket_mac.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BED32970E5A181C00A747DB /* ssl_client_socket_mac.cc */; }; 825C2FCC0E5C968B00FDEAB7 /* ev_root_ca_metadata.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BED32BE0E5A181C00A747DB /* ev_root_ca_metadata.cc */; }; 827E139D0E81611D00183614 /* x509_certificate_mac.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BED32800E5A181C00A747DB /* x509_certificate_mac.cc */; }; 82ECB3090E5B651D00A913E3 /* mime_sniffer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7BED32AD0E5A181C00A747DB /* mime_sniffer.cc */; }; @@ -491,6 +493,7 @@ 7BED32940E5A181C00A747DB /* ssl_config_service.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ssl_config_service.cc; sourceTree = "<group>"; }; 7BED32950E5A181C00A747DB /* ssl_client_socket_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ssl_client_socket_unittest.cc; sourceTree = "<group>"; }; 7BED32960E5A181C00A747DB /* ssl_client_socket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ssl_client_socket.h; sourceTree = "<group>"; }; + 7BED32970E5A181C00A747DB /* ssl_client_socket_mac.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ssl_client_socket_mac.cc; sourceTree = "<group>"; }; 7BED32980E5A181C00A747DB /* socket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = socket.h; sourceTree = "<group>"; }; 7BED32990E5A181C00A747DB /* registry_controlled_domain_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = registry_controlled_domain_unittest.cc; sourceTree = "<group>"; }; 7BED329A0E5A181C00A747DB /* registry_controlled_domain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = registry_controlled_domain.h; sourceTree = "<group>"; }; @@ -653,6 +656,7 @@ 82113A1C0E8434EE00E3848F /* x509_certificate_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = x509_certificate_unittest.cc; sourceTree = "<group>"; }; 82113A270E84360200E3848F /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = "<group>"; }; 82113BBC0E892E5800E3848F /* x509_certificate.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = x509_certificate.cc; sourceTree = "<group>"; }; + 8249C4920EA786B100A4A54B /* ssl_client_socket_mac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ssl_client_socket_mac.h; sourceTree = "<group>"; }; 936882DC0E9154E200043405 /* file_input_stream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = file_input_stream.h; sourceTree = "<group>"; }; 93D11DCD0E91463000C36437 /* file_input_stream_posix.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = file_input_stream_posix.cc; sourceTree = "<group>"; }; DFEE18250E882E3600666107 /* stats_histogram.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = stats_histogram.cc; sourceTree = "<group>"; }; @@ -898,8 +902,8 @@ 7BED32BF0E5A181C00A747DB /* escape_unittest.cc */, 7BED32BE0E5A181C00A747DB /* ev_root_ca_metadata.cc */, 7BED32BD0E5A181C00A747DB /* ev_root_ca_metadata.h */, - 93D11DCD0E91463000C36437 /* file_input_stream_posix.cc */, 936882DC0E9154E200043405 /* file_input_stream.h */, + 93D11DCD0E91463000C36437 /* file_input_stream_posix.cc */, 7BED32BC0E5A181C00A747DB /* filter.cc */, 7BED32BB0E5A181C00A747DB /* filter.h */, 7BED32BA0E5A181C00A747DB /* gzip_filter.cc */, @@ -946,6 +950,8 @@ E49DD2E90E892F8C003C7A87 /* sdch_manager.h */, 7BED32980E5A181C00A747DB /* socket.h */, 7BED32960E5A181C00A747DB /* ssl_client_socket.h */, + 7BED32970E5A181C00A747DB /* ssl_client_socket_mac.cc */, + 8249C4920EA786B100A4A54B /* ssl_client_socket_mac.h */, 7BED32950E5A181C00A747DB /* ssl_client_socket_unittest.cc */, 7BED32940E5A181C00A747DB /* ssl_config_service.cc */, 7BED32930E5A181C00A747DB /* ssl_config_service.h */, @@ -1463,6 +1469,7 @@ 7B8B5B560E5CEADE002F9A97 /* registry_controlled_domain.cc in Sources */, 7BA362B60E8C3D020023C8B9 /* sdch_filter.cc in Sources */, E49DD2EA0E892F8C003C7A87 /* sdch_manager.cc in Sources */, + 8220FAFC0E915561008170A9 /* ssl_client_socket_mac.cc in Sources */, 7B8504410E5B2E9600730B43 /* stats.cc in Sources */, DFEE18270E882E3600666107 /* stats_histogram.cc in Sources */, E47E933F0E8924DC00CA613E /* tcp_client_socket_libevent.cc in Sources */, @@ -1524,6 +1531,7 @@ 7B8B5B9E0E5D188E002F9A97 /* registry_controlled_domain_unittest.cc in Sources */, E4AFA6430E5241B400201347 /* run_all_unittests.cc in Sources */, 7BA362B70E8C3D040023C8B9 /* sdch_filter_unitest.cc in Sources */, + 8220FABD0E914ACA008170A9 /* ssl_client_socket_unittest.cc in Sources */, 7BD8F7110E65DCF500034DE9 /* storage_block_unittest.cc in Sources */, E47E93430E8924EE00CA613E /* tcp_client_socket_unittest.cc in Sources */, 7BA361450E8C341F0023C8B9 /* test_completion_callback_unittest.cc in Sources */, |