summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoravi@google.com <avi@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-10-17 14:49:07 +0000
committeravi@google.com <avi@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-10-17 14:49:07 +0000
commitb75523f5a51e3684d5be00fbcfd73ddcb610d5ad (patch)
treec233b949f183ddb81b523b93e21044fb04bfff40
parentc8e5341e7e90be48182c0435462c9afd7780d898 (diff)
downloadchromium_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
-rw-r--r--net/base/client_socket_factory.cc5
-rw-r--r--net/base/ssl_client_socket_mac.cc634
-rw-r--r--net/base/ssl_client_socket_mac.h106
-rw-r--r--net/base/ssl_client_socket_unittest.cc40
-rw-r--r--net/net.xcodeproj/project.pbxproj10
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 */,