Index: net/base/ssl_client_socket_nss.cc |
=================================================================== |
--- net/base/ssl_client_socket_nss.cc (revision 12708) |
+++ net/base/ssl_client_socket_nss.cc (working copy) |
@@ -15,6 +15,7 @@ |
#include <pk11pub.h> |
#undef Lock |
+#include "base/compiler_specific.h" |
#include "base/logging.h" |
#include "base/nss_init.h" |
#include "base/string_util.h" |
@@ -129,6 +130,34 @@ |
return OK; |
} |
+// As part of Connect(), the SSLClientSocketNSS object performs an SSL |
+// handshake. This requires network IO, which in turn calls |
+// BufferRecvComplete() with a non-zero byte count. This byte count eventually |
+// winds its way through the state machine and ends up being passed to the |
+// callback. For Read() and Write(), that's what we want. But for Connect(), |
+// the caller expects OK (i.e. 0) for success. |
+// |
+// The ConnectCallbackWrapper object changes the argument that gets passed |
+// to the callback function. Any positive value gets turned into OK. |
+class ConnectCallbackWrapper : |
+ public CompletionCallbackImpl<ConnectCallbackWrapper> { |
+ public: |
+ ConnectCallbackWrapper(CompletionCallback* user_callback) |
+ : ALLOW_THIS_IN_INITIALIZER_LIST( |
+ CompletionCallbackImpl<ConnectCallbackWrapper>(this, |
+ &ConnectCallbackWrapper::ReturnValueWrapper)), |
+ user_callback_(user_callback) { |
+ } |
+ |
+ private: |
+ void ReturnValueWrapper(int rv) { |
+ user_callback_->Run(rv > OK ? OK : rv); |
+ delete this; |
+ } |
+ |
+ CompletionCallback* user_callback_; |
+}; |
+ |
int SSLClientSocketNSS::Connect(CompletionCallback* callback) { |
EnterFunction(""); |
DCHECK(transport_.get()); |
@@ -138,28 +167,38 @@ |
GotoState(STATE_CONNECT); |
int rv = DoLoop(OK); |
if (rv == ERR_IO_PENDING) |
- user_callback_ = callback; |
+ user_callback_ = new ConnectCallbackWrapper(callback); |
LeaveFunction(""); |
- return rv; |
+ return rv > OK ? OK : rv; |
} |
-int SSLClientSocketNSS::ReconnectIgnoringLastError( |
- CompletionCallback* callback) { |
- EnterFunction(""); |
- // TODO(darin): implement me! |
- LeaveFunction(""); |
- return ERR_FAILED; |
+void SSLClientSocketNSS::InvalidateSessionIfBadCertificate() { |
+ if (UpdateServerCert() != NULL && |
+ ssl_config_.allowed_bad_certs_.count(server_cert_)) { |
+ SSL_InvalidateSession(nss_fd_); |
+ } |
} |
void SSLClientSocketNSS::Disconnect() { |
EnterFunction(""); |
+ |
+ // Reset object state |
+ transport_send_busy_ = false; |
+ transport_recv_busy_ = false; |
+ user_buf_ = NULL; |
+ user_buf_len_ = 0; |
+ server_cert_error_ = OK; |
+ completed_handshake_ = false; |
+ nss_bufs_ = NULL; |
+ |
// TODO(wtc): Send SSL close_notify alert. |
if (nss_fd_ != NULL) { |
+ InvalidateSessionIfBadCertificate(); |
PR_Close(nss_fd_); |
nss_fd_ = NULL; |
} |
- completed_handshake_ = false; |
+ |
transport_->Disconnect(); |
LeaveFunction(""); |
} |
@@ -229,6 +268,20 @@ |
return rv; |
} |
+X509Certificate *SSLClientSocketNSS::UpdateServerCert() { |
+ // We set the server_cert_ from OwnAuthCertHandler(), but this handler |
+ // does not necessarily get called if we are continuing a cached SSL |
+ // session. |
+ if (server_cert_ == NULL) { |
+ X509Certificate::OSCertHandle nss_cert = SSL_PeerCertificate(nss_fd_); |
+ if (nss_cert) { |
+ server_cert_ = X509Certificate::CreateFromHandle( |
+ nss_cert, X509Certificate::SOURCE_FROM_NETWORK); |
+ } |
+ } |
+ return server_cert_; |
+} |
+ |
void SSLClientSocketNSS::GetSSLInfo(SSLInfo* ssl_info) { |
EnterFunction(""); |
ssl_info->Reset(); |
@@ -248,13 +301,12 @@ |
LOG(DFATAL) << "SSL_GetCipherSuiteInfo returned " << PR_GetError() |
<< " for cipherSuite " << channel_info.cipherSuite; |
} |
+ UpdateServerCert(); |
} |
if (server_cert_error_ != net::OK) |
ssl_info->SetCertError(server_cert_error_); |
- X509Certificate::OSCertHandle nss_cert = SSL_PeerCertificate(nss_fd_); |
- if (nss_cert) |
- ssl_info->cert = X509Certificate::CreateFromHandle(nss_cert, |
- X509Certificate::SOURCE_FROM_NETWORK); |
+ DCHECK(server_cert_ != NULL); |
+ ssl_info->cert = server_cert_; |
LeaveFunction(""); |
} |
@@ -355,7 +407,6 @@ |
LeaveFunction(""); |
} |
- |
int SSLClientSocketNSS::DoLoop(int last_io_result) { |
EnterFunction(last_io_result); |
bool network_moved; |
@@ -409,20 +460,49 @@ |
int SSLClientSocketNSS::DoConnect() { |
EnterFunction(""); |
GotoState(STATE_CONNECT_COMPLETE); |
- return transport_->Connect(&io_callback_); |
+ |
+ // The caller has to make sure that the transport socket is connected. If |
+ // it isn't, we will eventually fail when trying to negotiate an SSL session. |
+ // But we cannot call transport_->Connect(), as we do not know if there is |
+ // any proxy negotiation that needs to be performed prior to establishing |
+ // the SSL session. |
+ return OK; |
} |
// static |
+// NSS calls this if an incoming certificate needs to be verified. |
+SECStatus SSLClientSocketNSS::OwnAuthCertHandler(void* arg, |
+ PRFileDesc* socket, |
+ PRBool checksig, |
+ PRBool is_server) { |
+ SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); |
+ |
+ // Remember the certificate as it will no longer be accessible if the |
+ // handshake fails. |
+ that->UpdateServerCert(); |
+ |
+ return SSL_AuthCertificate(CERT_GetDefaultCertDB(), socket, checksig, |
+ is_server); |
+} |
+ |
+// static |
// NSS calls this if an incoming certificate is invalid. |
-SECStatus SSLClientSocketNSS::OwnBadCertHandler(void* arg, PRFileDesc* socket) { |
+SECStatus SSLClientSocketNSS::OwnBadCertHandler(void* arg, |
+ PRFileDesc* socket) { |
SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); |
+ |
+ if (that->server_cert_ && |
+ that->ssl_config_.allowed_bad_certs_.count(that->server_cert_)) { |
+ LOG(INFO) << "accepting bad SSL certificate, as user told us to"; |
+ |
+ return SECSuccess; |
+ } |
PRErrorCode prerr = PR_GetError(); |
that->server_cert_error_ = NetErrorFromNSPRError(prerr); |
LOG(INFO) << "server certificate is invalid; NSS error code " << prerr |
<< ", net error " << that->server_cert_error_; |
- // Return SECSuccess to override the problem. |
- // Chromium wants it to succeed here, and may abort the connection later. |
- return SECSuccess; |
+ |
+ return SECFailure; |
} |
int SSLClientSocketNSS::DoConnectComplete(int result) { |
@@ -503,6 +583,10 @@ |
if (rv != SECSuccess) |
return ERR_UNEXPECTED; |
+ rv = SSL_AuthCertificateHook(nss_fd_, OwnAuthCertHandler, this); |
+ if (rv != SECSuccess) |
+ return ERR_UNEXPECTED; |
+ |
rv = SSL_BadCertHook(nss_fd_, OwnBadCertHandler, this); |
if (rv != SECSuccess) |
return ERR_UNEXPECTED; |
@@ -520,11 +604,14 @@ |
int SSLClientSocketNSS::DoHandshakeRead() { |
EnterFunction(""); |
- int net_error; |
+ int net_error = net::OK; |
int rv = SSL_ForceHandshake(nss_fd_); |
if (rv == SECSuccess) { |
- net_error = server_cert_error_; |
+ DCHECK(server_cert_error_ == net::OK); |
+ |
+ InvalidateSessionIfBadCertificate(); |
+ |
// there's a callback for this, too |
completed_handshake_ = true; |
// Done! |