summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--net/socket/ssl_client_socket_mac.cc411
-rw-r--r--net/socket/ssl_client_socket_mac.h33
2 files changed, 199 insertions, 245 deletions
diff --git a/net/socket/ssl_client_socket_mac.cc b/net/socket/ssl_client_socket_mac.cc
index 284937a..2df29e7 100644
--- a/net/socket/ssl_client_socket_mac.cc
+++ b/net/socket/ssl_client_socket_mac.cc
@@ -39,7 +39,7 @@
// 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.
+// in the DoHandshakeLoop() 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
@@ -108,18 +108,6 @@ const unsigned int kWriteSizeResumeLimit = 1 * 1024 * 1024;
#define SSL_LOG LOG(INFO) << "SSL: "
#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5
-// Declarations needed to call the 10.5.7 and later SSLSetSessionOption()
-// function when building with the 10.5.0 SDK.
-typedef enum {
- kSSLSessionOptionBreakOnServerAuth,
- kSSLSessionOptionBreakOnCertRequested,
-} SSLSessionOption;
-
-enum {
- errSSLServerAuthCompleted = -9841,
- errSSLClientCertRequested = -9842,
-};
-
// When compiled against the Mac OS X 10.5 SDK, define symbolic constants for
// cipher suites added in Mac OS X 10.6.
enum {
@@ -152,9 +140,6 @@ enum {
};
#endif
-typedef OSStatus (*SSLSetSessionOptionFuncPtr)(SSLContextRef,
- SSLSessionOption,
- Boolean);
// For an explanation of the Mac OS X error codes, please refer to:
// http://developer.apple.com/mac/library/documentation/Security/Reference/secureTransportRef/Reference/reference.html
int NetErrorFromOSStatus(OSStatus status) {
@@ -515,8 +500,7 @@ SSLClientSocketMac::SSLClientSocketMac(ClientSocketHandle* transport_socket,
user_read_buf_len_(0),
user_write_buf_len_(0),
next_handshake_state_(STATE_NONE),
- completed_handshake_(false),
- handshake_interrupted_(false),
+ renegotiating_(false),
client_cert_requested_(false),
ssl_context_(NULL),
pending_send_error_(OK),
@@ -540,7 +524,7 @@ int SSLClientSocketMac::Connect(CompletionCallback* callback) {
return rv;
}
- next_handshake_state_ = STATE_HANDSHAKE_START;
+ next_handshake_state_ = STATE_HANDSHAKE;
rv = DoHandshakeLoop(OK);
if (rv == ERR_IO_PENDING) {
user_connect_callback_ = callback;
@@ -551,7 +535,7 @@ int SSLClientSocketMac::Connect(CompletionCallback* callback) {
}
void SSLClientSocketMac::Disconnect() {
- completed_handshake_ = false;
+ next_handshake_state_ = STATE_NONE;
if (ssl_context_) {
SSLClose(ssl_context_);
@@ -572,7 +556,7 @@ bool SSLClientSocketMac::IsConnected() const {
// 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_->socket()->IsConnected();
+ return completed_handshake() && transport_->socket()->IsConnected();
}
bool SSLClientSocketMac::IsConnectedAndIdle() const {
@@ -584,7 +568,7 @@ bool SSLClientSocketMac::IsConnectedAndIdle() const {
// bytes to the transport layer below, so
// transport_->socket()->IsConnectedAndIdle() returns the desired false
// when we receive close_notify.
- return completed_handshake_ && transport_->socket()->IsConnectedAndIdle();
+ return completed_handshake() && transport_->socket()->IsConnectedAndIdle();
}
int SSLClientSocketMac::GetPeerAddress(AddressList* address) const {
@@ -617,7 +601,7 @@ bool SSLClientSocketMac::WasEverUsed() const {
int SSLClientSocketMac::Read(IOBuffer* buf, int buf_len,
CompletionCallback* callback) {
- DCHECK(completed_handshake_);
+ DCHECK(completed_handshake());
DCHECK(!user_read_callback_);
DCHECK(!user_read_buf_);
@@ -636,7 +620,7 @@ int SSLClientSocketMac::Read(IOBuffer* buf, int buf_len,
int SSLClientSocketMac::Write(IOBuffer* buf, int buf_len,
CompletionCallback* callback) {
- DCHECK(completed_handshake_);
+ DCHECK(completed_handshake());
DCHECK(!user_write_callback_);
DCHECK(!user_write_buf_);
@@ -728,34 +712,6 @@ SSLClientSocketMac::GetNextProto(std::string* proto) {
return kNextProtoUnsupported;
}
-OSStatus SSLClientSocketMac::EnableBreakOnAuth(bool enabled) {
- // SSLSetSessionOption() was introduced in Mac OS X 10.5.7. It allows us
- // to perform certificate validation during the handshake, which is
- // required in order to properly enable session resumption.
- //
- // With the kSSLSessionOptionBreakOnServerAuth option set, SSLHandshake()
- // will return errSSLServerAuthCompleted after receiving the server's
- // Certificate during the handshake. That gives us an opportunity to verify
- // the server certificate and then re-enter that handshake (assuming the
- // certificate successfully validated).
- // If the server also requests a client cert, SSLHandshake() will return
- // errSSLClientCertRequested in addition to (or in some cases *instead of*)
- // errSSLServerAuthCompleted.
- static SSLSetSessionOptionFuncPtr ssl_set_session_options =
- LookupFunction<SSLSetSessionOptionFuncPtr>(CFSTR("com.apple.security"),
- CFSTR("SSLSetSessionOption"));
- if (!ssl_set_session_options)
- return unimpErr; // Return this if the API isn't available
- OSStatus err = ssl_set_session_options(ssl_context_,
- kSSLSessionOptionBreakOnServerAuth,
- enabled);
- if (err)
- return err;
- return ssl_set_session_options(ssl_context_,
- kSSLSessionOptionBreakOnCertRequested,
- enabled);
-}
-
int SSLClientSocketMac::InitializeSSLContext() {
SSL_LOG << "----- InitializeSSLContext";
OSStatus status = noErr;
@@ -810,55 +766,32 @@ int SSLClientSocketMac::InitializeSSLContext() {
if (status)
return NetErrorFromOSStatus(status);
- // It is tricky to handle client cert request over renegotiation due to bugs
- // in Secure Transport. From Ken McLeod on apple-cdsa:
- // http://lists.apple.com/archives/apple-cdsa/2010/Feb/msg00058.html
- // A possible workaround would be to set the
- // kSSLSessionOptionBreakOnCertRequested option initially, then if you get
- // that status, ask for a client cert, abort the connection yourself and
- // retry it (this time calling SSLSetCertificate before the handshake
- // starts, and *not* setting the kSSLSessionOptionBreakOnCertRequested
- // option.)
if (ssl_config_.send_client_cert) {
- SSL_LOG << "Setting client cert in advance because send_client_cert is set";
status = SetClientCert();
if (status)
return NetErrorFromOSStatus(status);
return OK;
}
- status = EnableBreakOnAuth(true);
- if (status == noErr) {
- // Only enable session resumption if break-on-auth is available,
- // because without break-on-auth we are verifying the server's certificate
- // after the handshake completes (but before any application data is
- // exchanged). If we were to enable session resumption in this situation,
- // the session would be cached before we verified the certificate, leaving
- // the potential for a session in which the certificate failed to validate
- // to still be able to be resumed.
-
- // Concatenate the hostname and peer address to use as the peer ID. To
- // resume a session, we must connect to the same server on the same port
- // using the same hostname (i.e., localhost and 127.0.0.1 are considered
- // different peers, which puts us through certificate validation again
- // and catches hostname/certificate name mismatches.
- AddressList address;
- int rv = transport_->socket()->GetPeerAddress(&address);
- if (rv != OK)
- return rv;
- const struct addrinfo* ai = address.head();
- std::string peer_id(hostname_);
- peer_id += std::string(reinterpret_cast<char*>(ai->ai_addr),
- ai->ai_addrlen);
-
- // SSLSetPeerID() treats peer_id as a binary blob, and makes its
- // own copy.
- status = SSLSetPeerID(ssl_context_, peer_id.data(), peer_id.length());
- if (status)
- return NetErrorFromOSStatus(status);
- } else if (status != unimpErr) { // it's OK if the API isn't available
+ // Concatenate the hostname and peer address to use as the peer ID. To
+ // resume a session, we must connect to the same server on the same port
+ // using the same hostname (i.e., localhost and 127.0.0.1 are considered
+ // different peers, which puts us through certificate validation again
+ // and catches hostname/certificate name mismatches.
+ AddressList address;
+ int rv = transport_->socket()->GetPeerAddress(&address);
+ if (rv != OK)
+ return rv;
+ const struct addrinfo* ai = address.head();
+ std::string peer_id(hostname_);
+ peer_id += std::string(reinterpret_cast<char*>(ai->ai_addr),
+ ai->ai_addrlen);
+
+ // SSLSetPeerID() treats peer_id as a binary blob, and makes its
+ // own copy.
+ status = SSLSetPeerID(ssl_context_, peer_id.data(), peer_id.length());
+ if (status)
return NetErrorFromOSStatus(status);
- }
return OK;
}
@@ -866,7 +799,6 @@ int SSLClientSocketMac::InitializeSSLContext() {
void SSLClientSocketMac::DoConnectCallback(int rv) {
DCHECK(rv != ERR_IO_PENDING);
DCHECK(user_connect_callback_);
- DCHECK(next_handshake_state_ == STATE_NONE);
CompletionCallback* c = user_connect_callback_;
user_connect_callback_ = NULL;
@@ -900,9 +832,16 @@ void SSLClientSocketMac::DoWriteCallback(int rv) {
}
void SSLClientSocketMac::OnHandshakeIOComplete(int result) {
- DCHECK(next_handshake_state_ != STATE_NONE);
int rv = DoHandshakeLoop(result);
if (rv != ERR_IO_PENDING) {
+ // If there is no connect callback available to call, we are
+ // renegotiating (which occurs because we are in the middle of a Read
+ // when the renegotiation process starts). So we complete the Read
+ // here.
+ if (!user_connect_callback_) {
+ DoReadCallback(rv);
+ return;
+ }
net_log_.EndEvent(NetLog::TYPE_SSL_CONNECT, NULL);
DoConnectCallback(rv);
}
@@ -916,14 +855,11 @@ void SSLClientSocketMac::OnTransportReadComplete(int result) {
}
read_io_buf_ = NULL;
- if (next_handshake_state_ != STATE_NONE) {
- int rv = DoHandshakeLoop(result);
- if (rv != ERR_IO_PENDING) {
- net_log_.EndEvent(NetLog::TYPE_SSL_CONNECT, NULL);
- DoConnectCallback(rv);
- }
+ if (!completed_handshake()) {
+ OnHandshakeIOComplete(result);
return;
}
+
if (user_read_buf_) {
if (result < 0) {
DoReadCallback(result);
@@ -948,6 +884,11 @@ void SSLClientSocketMac::OnTransportWriteComplete(int result) {
if (!send_buffer_.empty())
SSLWriteCallback(this, NULL, NULL);
+ if (!completed_handshake()) {
+ OnHandshakeIOComplete(result);
+ return;
+ }
+
// If paused because too much data is in flight, try writing again and make
// the promised callback.
if (user_write_buf_ && send_buffer_.size() < kWriteSizeResumeLimit) {
@@ -957,8 +898,6 @@ void SSLClientSocketMac::OnTransportWriteComplete(int result) {
}
}
-// 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::DoHandshakeLoop(int last_io_result) {
DCHECK(next_handshake_state_ != STATE_NONE);
int rv = last_io_result;
@@ -966,22 +905,28 @@ int SSLClientSocketMac::DoHandshakeLoop(int last_io_result) {
State state = next_handshake_state_;
next_handshake_state_ = STATE_NONE;
switch (state) {
- case STATE_HANDSHAKE_START:
- // Do the SSL/TLS handshake, up to the server certificate message.
- rv = DoHandshakeStart();
+ case STATE_HANDSHAKE:
+ // Do the SSL/TLS handshake.
+ rv = DoHandshake();
break;
case STATE_VERIFY_CERT:
// Kick off server certificate validation.
rv = DoVerifyCert();
break;
case STATE_VERIFY_CERT_COMPLETE:
- // Check the results of the server certificate validation.
+ // Check the results of the server certificate validation.
rv = DoVerifyCertComplete(rv);
break;
- case STATE_HANDSHAKE_FINISH:
- // Do the SSL/TLS handshake, after the server certificate message.
- rv = DoHandshakeFinish();
+ case STATE_COMPLETED_RENEGOTIATION:
+ // The renegotiation handshake has completed, and the Read() call
+ // that was interrupted by the renegotiation needs to be resumed in
+ // order to to satisfy the original caller's request.
+ rv = DoCompletedRenegotiation(rv);
break;
+ case STATE_COMPLETED_HANDSHAKE:
+ next_handshake_state_ = STATE_COMPLETED_HANDSHAKE;
+ // This is the end of our state machine, so return.
+ return rv;
default:
rv = ERR_UNEXPECTED;
NOTREACHED() << "unexpected state";
@@ -991,57 +936,49 @@ int SSLClientSocketMac::DoHandshakeLoop(int last_io_result) {
return rv;
}
-int SSLClientSocketMac::DoHandshakeStart() {
+int SSLClientSocketMac::DoHandshake() {
+ client_cert_requested_ = false;
+
OSStatus status = SSLHandshake(ssl_context_);
- switch (status) {
- case errSSLWouldBlock:
- next_handshake_state_ = STATE_HANDSHAKE_START;
- break;
+ SSLClientCertificateState client_cert_state;
+ if (SSLGetClientCertificateState(ssl_context_, &client_cert_state) != noErr)
+ client_cert_state = kSSLClientCertNone;
+ if (client_cert_state > kSSLClientCertNone)
+ client_cert_requested_ = true;
+ switch (status) {
case noErr:
- // TODO(hawk): we verify the certificate chain even on resumed sessions
- // so that we have the certificate status (valid, expired but overridden
- // by the user, EV, etc.) available. Eliminate this step once we have
- // a certificate validation result cache. (Also applies to the
- // errSSLServerAuthCompleted case below.)
- SSL_LOG << "Handshake completed (DoHandshakeStart), next verify cert";
- next_handshake_state_ = STATE_VERIFY_CERT;
- HandshakeFinished();
- break;
-
- case errSSLServerAuthCompleted:
- // Override errSSLServerAuthCompleted as it's not actually an error,
- // but rather an indication that we're only half way through the
- // handshake.
- SSL_LOG << "Server auth completed (DoHandshakeStart)";
- next_handshake_state_ = STATE_VERIFY_CERT;
- handshake_interrupted_ = true;
- status = noErr;
- break;
-
- case errSSLClientCertRequested:
- SSL_LOG << "Received client cert request in DoHandshakeStart";
- // If we get this instead of errSSLServerAuthCompleted, the latter is
- // implicit, and we should begin verification as well.
- next_handshake_state_ = STATE_VERIFY_CERT;
- handshake_interrupted_ = true;
- status = noErr;
- // We don't want to send a client cert now, because we haven't
- // verified the server's cert yet. Remember it for later.
- client_cert_requested_ = true;
+ return DidCompleteHandshake();
+ case errSSLWouldBlock:
+ next_handshake_state_ = STATE_HANDSHAKE;
break;
-
case errSSLClosedGraceful:
// The server unexpectedly closed on us.
return ERR_SSL_PROTOCOL_ERROR;
+ case errSSLClosedAbort:
+ case errSSLPeerHandshakeFail:
+ if (client_cert_requested_) {
+ // See if the server aborted due to client cert checking.
+ if (!ssl_config_.send_client_cert) {
+ SSL_LOG << "Server requested SSL cert during handshake";
+ return ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
+ }
+ LOG(WARNING) << "Server aborted SSL handshake";
+ return ERR_BAD_SSL_CLIENT_AUTH_CERT;
+ }
+ break;
}
int net_error = NetErrorFromOSStatus(status);
- if (status == noErr || IsCertificateError(net_error)) {
- server_cert_ = GetServerCert(ssl_context_);
- if (!server_cert_)
- return ERR_UNEXPECTED;
+ DCHECK(!IsCertificateError(net_error));
+
+ if (!ssl_config_.send_client_cert &&
+ (client_cert_state == kSSLClientCertRejected ||
+ net_error == ERR_BAD_SSL_CLIENT_AUTH_CERT)) {
+ // The server unexpectedly sent a peer certificate error alert when no
+ // certificate had been sent.
+ net_error = ERR_SSL_PROTOCOL_ERROR;
}
return net_error;
}
@@ -1049,8 +986,7 @@ int SSLClientSocketMac::DoHandshakeStart() {
int SSLClientSocketMac::DoVerifyCert() {
next_handshake_state_ = STATE_VERIFY_CERT_COMPLETE;
- if (!server_cert_)
- return ERR_UNEXPECTED;
+ DCHECK(server_cert_);
SSL_LOG << "DoVerifyCert...";
int flags = 0;
@@ -1072,30 +1008,22 @@ int SSLClientSocketMac::DoVerifyCertComplete(int result) {
if (IsCertificateError(result) && ssl_config_.IsAllowedBadCert(server_cert_))
result = OK;
- if (result == OK && client_cert_requested_) {
- if (!ssl_config_.send_client_cert) {
- // Caller hasn't specified a client cert, so let it know the server's
- // asking for one, and abort the connection.
- return ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
- }
- // (We already called SetClientCert during InitializeSSLContext;
- // no need to do so again.)
+ if (result == OK && client_cert_requested_ &&
+ !ssl_config_.send_client_cert) {
+ // Caller hasn't specified a client cert, so let it know the server is
+ // asking for one, and abort the connection.
+ return ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
}
+ SSL_LOG << "Handshake finished! (DoVerifyCertComplete)";
- if (handshake_interrupted_) {
- // With session resumption enabled the full handshake (i.e., the handshake
- // in a non-resumed session) occurs in two steps. Continue on to the second
- // step if the certificate is OK.
- if (result == OK)
- next_handshake_state_ = STATE_HANDSHAKE_FINISH;
- } else {
- // If the session was resumed or session resumption was disabled, we're
- // done with the handshake.
- SSL_LOG << "Handshake finished! (DoVerifyCertComplete)";
- completed_handshake_ = true;
- DCHECK(next_handshake_state_ == STATE_NONE);
+ if (renegotiating_) {
+ DidCompleteRenegotiation();
+ return result;
}
+ // The initial handshake has completed.
+ next_handshake_state_ = STATE_COMPLETED_HANDSHAKE;
+
return result;
}
@@ -1112,67 +1040,24 @@ int SSLClientSocketMac::SetClientCert() {
return result;
}
-int SSLClientSocketMac::DoHandshakeFinish() {
- OSStatus status = SSLHandshake(ssl_context_);
-
- switch (status) {
- case errSSLWouldBlock:
- next_handshake_state_ = STATE_HANDSHAKE_FINISH;
- break;
- case errSSLClientCertRequested:
- SSL_LOG << "Server requested client cert (DoHandshakeFinish)";
- DCHECK(!ssl_config_.send_client_cert);
- return ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
- break;
- case errSSLClosedGraceful:
- return ERR_SSL_PROTOCOL_ERROR;
- case errSSLClosedAbort:
- case errSSLPeerHandshakeFail: {
- // See if the server aborted due to client cert checking.
- SSLClientCertificateState client_state;
- if (SSLGetClientCertificateState(ssl_context_, &client_state) == noErr &&
- client_state > kSSLClientCertNone) {
- if (client_state == kSSLClientCertRequested &&
- !ssl_config_.send_client_cert) {
- SSL_LOG << "Server requested SSL cert during handshake";
- return ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
- }
- LOG(WARNING) << "Server aborted SSL handshake; client_state="
- << client_state;
- return ERR_BAD_SSL_CLIENT_AUTH_CERT;
- }
- break;
- }
- case noErr:
- SSL_LOG << "Handshake finished! (DoHandshakeFinish)";
- HandshakeFinished();
- completed_handshake_ = true;
- DCHECK(next_handshake_state_ == STATE_NONE);
- break;
- default:
- break;
- }
-
- return NetErrorFromOSStatus(status);
-}
-
-void SSLClientSocketMac::HandshakeFinished() {
- SSL_LOG << "HandshakeFinished()";
-}
-
int SSLClientSocketMac::DoPayloadRead() {
size_t processed = 0;
- OSStatus status = SSLRead(ssl_context_,
- user_read_buf_->data(),
- user_read_buf_len_,
- &processed);
-
+ OSStatus status = SSLRead(ssl_context_, user_read_buf_->data(),
+ user_read_buf_len_, &processed);
+ if (status == errSSLWouldBlock && renegotiating_) {
+ CHECK_EQ(static_cast<size_t>(0), processed);
+ next_handshake_state_ = STATE_HANDSHAKE;
+ return DoHandshakeLoop(OK);
+ }
// 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.
-
+ // have data, let's just return it. This is further complicated by the fact
+ // that errSSLWouldBlock is also used to short-circuit SSLRead()'s
+ // transparent renegotiation, so that we can update our state machine above,
+ // which otherwise would get out of sync with the SSLContextRef's internal
+ // state machine.
if (processed > 0)
return processed;
@@ -1183,15 +1068,6 @@ int SSLClientSocketMac::DoPayloadRead() {
// uncleanly, a potential truncation attack. See http://crbug.com/18586.
return OK;
- case errSSLServerAuthCompleted:
- case errSSLClientCertRequested:
- // Server wants to renegotiate, probably to ask for a client cert,
- // but SecureTransport doesn't support renegotiation so we have to close.
- DCHECK(!ssl_config_.send_client_cert);
- // Tell my caller the server wants a client cert so it can reconnect.
- SSL_LOG << "Server renegotiating; assuming it wants a client cert...";
- return ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
-
default:
return NetErrorFromOSStatus(status);
}
@@ -1214,6 +1090,43 @@ int SSLClientSocketMac::DoPayloadWrite() {
return NetErrorFromOSStatus(status);
}
+int SSLClientSocketMac::DoCompletedRenegotiation(int result) {
+ // The user had a read in progress, which was usurped by the renegotiation.
+ // Restart the read sequence.
+ next_handshake_state_ = STATE_COMPLETED_HANDSHAKE;
+ if (result != OK)
+ return result;
+ return DoPayloadRead();
+}
+
+void SSLClientSocketMac::DidCompleteRenegotiation() {
+ DCHECK(!user_connect_callback_);
+ renegotiating_ = false;
+ next_handshake_state_ = STATE_COMPLETED_RENEGOTIATION;
+}
+
+int SSLClientSocketMac::DidCompleteHandshake() {
+ DCHECK(!server_cert_ || renegotiating_);
+ SSL_LOG << "Handshake completed, next verify cert";
+
+ scoped_refptr<X509Certificate> new_server_cert =
+ GetServerCert(ssl_context_);
+ if (!new_server_cert)
+ return ERR_UNEXPECTED;
+
+ if (renegotiating_ &&
+ X509Certificate::IsSameOSCert(server_cert_->os_cert_handle(),
+ new_server_cert->os_cert_handle())) {
+ // We already verified the server certificate. Either it is good or the
+ // user has accepted the certificate error.
+ DidCompleteRenegotiation();
+ } else {
+ server_cert_ = new_server_cert;
+ next_handshake_state_ = STATE_VERIFY_CERT;
+ }
+ return OK;
+}
+
// static
OSStatus SSLClientSocketMac::SSLReadCallback(SSLConnectionRef connection,
void* data,
@@ -1230,6 +1143,32 @@ OSStatus SSLClientSocketMac::SSLReadCallback(SSLConnectionRef connection,
*data_length = 0;
return errSSLWouldBlock;
}
+ if (us->completed_handshake()) {
+ // The state machine for SSLRead, located in libsecurity_ssl's
+ // sslTransport.c, will attempt to fully complete the renegotiation
+ // transparently in SSLRead once it reads the server's HelloRequest
+ // message. In order to make sure that the server certificate is
+ // (re-)verified and that any other parameters are logged (eg:
+ // certificate request state), we try to detect that the
+ // SSLClientSocketMac's state machine is out of sync with the
+ // SSLContext's. When that happens, we break out by faking
+ // errSSLWouldBlock, and set a flag so that DoPayloadRead() knows that
+ // it's not actually blocked. DoPayloadRead() will then restart the
+ // handshake state machine, and finally resume the original Read()
+ // once it successfully completes, similar to the behaviour of
+ // SSLClientSocketWin's DoDecryptPayload() and DoLoop() behave.
+ SSLSessionState state;
+ OSStatus status = SSLGetSessionState(us->ssl_context_, &state);
+ if (status) {
+ *data_length = 0;
+ return status;
+ }
+ if (state == kSSLHandshake) {
+ *data_length = 0;
+ us->renegotiating_ = true;
+ return errSSLWouldBlock;
+ }
+ }
size_t total_read = us->recv_buffer_.size();
diff --git a/net/socket/ssl_client_socket_mac.h b/net/socket/ssl_client_socket_mac.h
index 05b9735..00438fc 100644
--- a/net/socket/ssl_client_socket_mac.h
+++ b/net/socket/ssl_client_socket_mac.h
@@ -58,11 +58,12 @@ class SSLClientSocketMac : public SSLClientSocket {
virtual bool SetSendBufferSize(int32 size);
private:
+ bool completed_handshake() const {
+ return next_handshake_state_ == STATE_COMPLETED_HANDSHAKE;
+ }
// Initializes the SSLContext. Returns a net error code.
int InitializeSSLContext();
- OSStatus EnableBreakOnAuth(bool enabled);
-
void DoConnectCallback(int result);
void DoReadCallback(int result);
void DoWriteCallback(int result);
@@ -74,11 +75,13 @@ class SSLClientSocketMac : public SSLClientSocket {
int DoPayloadRead();
int DoPayloadWrite();
- int DoHandshakeStart();
+ int DoHandshake();
int DoVerifyCert();
int DoVerifyCertComplete(int result);
- int DoHandshakeFinish();
- void HandshakeFinished();
+ int DoCompletedRenegotiation(int result);
+
+ void DidCompleteRenegotiation();
+ int DidCompleteHandshake();
int SetClientCert();
@@ -111,10 +114,21 @@ class SSLClientSocketMac : public SSLClientSocket {
enum State {
STATE_NONE,
- STATE_HANDSHAKE_START,
+ STATE_HANDSHAKE,
STATE_VERIFY_CERT,
STATE_VERIFY_CERT_COMPLETE,
- STATE_HANDSHAKE_FINISH,
+ STATE_COMPLETED_RENEGOTIATION,
+ STATE_COMPLETED_HANDSHAKE,
+ // After the handshake, the socket remains in the
+ // STATE_COMPLETED_HANDSHAKE state until renegotiation is requested by
+ // the server. When renegotiation is requested, the state machine
+ // restarts at STATE_HANDSHAKE, advances through to
+ // STATE_VERIFY_CERT_COMPLETE, and then continues to
+ // STATE_COMPLETED_RENEGOTIATION. After STATE_COMPLETED_RENEGOTIATION
+ // has been processed, it goes back to STATE_COMPLETED_HANDSHAKE and
+ // will remain there until the server requests renegotiation again.
+ // During the initial handshake, STATE_COMPLETED_RENEGOTIATION is
+ // skipped.
};
State next_handshake_state_;
@@ -122,8 +136,9 @@ class SSLClientSocketMac : public SSLClientSocket {
scoped_ptr<CertVerifier> verifier_;
CertVerifyResult server_cert_verify_result_;
- bool completed_handshake_;
- bool handshake_interrupted_;
+ // The initial handshake has already completed, and the current handshake
+ // is server-initiated renegotiation.
+ bool renegotiating_;
bool client_cert_requested_;
SSLContextRef ssl_context_;