summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authoragl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-10-01 16:25:54 +0000
committeragl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-10-01 16:25:54 +0000
commit7a8de307caef3ed87448fde283201273f063f024 (patch)
tree7660501ff318592e9577e571446139726a33f5a0 /net
parent5a41c4c0b8cdb997062b8bf197e2d0cf65bd508c (diff)
downloadchromium_src-7a8de307caef3ed87448fde283201273f063f024.zip
chromium_src-7a8de307caef3ed87448fde283201273f063f024.tar.gz
chromium_src-7a8de307caef3ed87448fde283201273f063f024.tar.bz2
net: add Snap Start support to NSS sockets.
(This doesn't actually enable any functional changes yet.) BUG=none TEST=none (yet) http://codereview.chromium.org/3454021 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@61181 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r--net/base/net_error_list.h6
-rw-r--r--net/base/ssl_config_service.cc18
-rw-r--r--net/base/ssl_config_service.h13
-rw-r--r--net/socket/ssl_client_socket.h1
-rw-r--r--net/socket/ssl_client_socket_nss.cc434
-rw-r--r--net/socket/ssl_client_socket_nss.h21
6 files changed, 468 insertions, 25 deletions
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
index 0370874..9e4ec6a 100644
--- a/net/base/net_error_list.h
+++ b/net/base/net_error_list.h
@@ -177,6 +177,12 @@ NET_ERROR(SSL_WEAK_SERVER_EPHEMERAL_DH_KEY, -129)
// of an HTTP proxy.
NET_ERROR(PROXY_CONNECTION_FAILED, -130)
+// This means that we tried a Snap Start connection and sent a request,
+// predicting the server's NPN protocol support. However, after doing the
+// actual handshake, our prediction turned out to be incorrect so we sent a
+// request in the wrong protocol.
+NET_ERROR(SSL_SNAP_START_NPN_MISPREDICTION, -131)
+
// Certificate error codes
//
// The values of certificate error codes must be consecutive.
diff --git a/net/base/ssl_config_service.cc b/net/base/ssl_config_service.cc
index c20284f..fc35f5d 100644
--- a/net/base/ssl_config_service.cc
+++ b/net/base/ssl_config_service.cc
@@ -17,9 +17,9 @@ namespace net {
SSLConfig::SSLConfig()
: rev_checking_enabled(true), ssl2_enabled(false), ssl3_enabled(true),
- tls1_enabled(true), dnssec_enabled(false), mitm_proxies_allowed(false),
- false_start_enabled(true), send_client_cert(false),
- verify_ev_cert(false), ssl3_fallback(false) {
+ tls1_enabled(true), dnssec_enabled(false), snap_start_enabled(false),
+ mitm_proxies_allowed(false), false_start_enabled(true),
+ send_client_cert(false), verify_ev_cert(false), ssl3_fallback(false) {
}
SSLConfig::~SSLConfig() {
@@ -90,12 +90,14 @@ bool SSLConfigService::IsKnownFalseStartIncompatibleServer(
static bool g_dnssec_enabled = false;
static bool g_false_start_enabled = true;
static bool g_mitm_proxies_allowed = false;
+static bool g_snap_start_enabled = false;
// static
void SSLConfigService::SetSSLConfigFlags(SSLConfig* ssl_config) {
ssl_config->dnssec_enabled = g_dnssec_enabled;
ssl_config->false_start_enabled = g_false_start_enabled;
ssl_config->mitm_proxies_allowed = g_mitm_proxies_allowed;
+ ssl_config->snap_start_enabled = g_snap_start_enabled;
}
// static
@@ -109,6 +111,16 @@ bool SSLConfigService::dnssec_enabled() {
}
// static
+void SSLConfigService::EnableSnapStart() {
+ g_snap_start_enabled = true;
+}
+
+// static
+bool SSLConfigService::snap_start_enabled() {
+ return g_snap_start_enabled;
+}
+
+// static
void SSLConfigService::DisableFalseStart() {
g_false_start_enabled = false;
}
diff --git a/net/base/ssl_config_service.h b/net/base/ssl_config_service.h
index f78e3df..5eb2397 100644
--- a/net/base/ssl_config_service.h
+++ b/net/base/ssl_config_service.h
@@ -10,6 +10,7 @@
#include "base/observer_list.h"
#include "base/ref_counted.h"
+#include "net/base/ssl_non_sensitive_host_info.h"
#include "net/base/x509_certificate.h"
namespace net {
@@ -27,6 +28,7 @@ struct SSLConfig {
bool ssl3_enabled; // True if SSL 3.0 is enabled.
bool tls1_enabled; // True if TLS 1.0 is enabled.
bool dnssec_enabled; // True if we'll accept DNSSEC chains in certificates.
+ bool snap_start_enabled; // True if we'll try Snap Start handshakes.
// True if we allow this connection to be MITM attacked. This sounds a little
// worse than it is: large networks sometimes MITM attack all SSL connections
@@ -72,6 +74,12 @@ struct SSLConfig {
std::string next_protos;
scoped_refptr<X509Certificate> client_cert;
+
+ // ssl_host_info contains an optional context that is needed for Snap Start.
+ // If provided, the SSL socket will assume that the application protocol is
+ // client-speaks-first. Also needs SSLConfigService::EnableSnapStart to
+ // have been called.
+ scoped_refptr<SSLNonSensitiveHostInfo> ssl_host_info;
};
// The interface for retrieving the SSL configuration. This interface
@@ -125,6 +133,11 @@ class SSLConfigService : public base::RefCountedThreadSafe<SSLConfigService> {
static void EnableDNSSEC();
static bool dnssec_enabled();
+ // Enables Snap Start, an experiemental SSL/TLS extension for zero round
+ // trip handshakes.
+ static void EnableSnapStart();
+ static bool snap_start_enabled();
+
// Sets a global flag which allows SSL connections to be MITM attacked. See
// the comment about this flag in |SSLConfig|.
static void AllowMITMProxies();
diff --git a/net/socket/ssl_client_socket.h b/net/socket/ssl_client_socket.h
index 064dd26..a6a0a91 100644
--- a/net/socket/ssl_client_socket.h
+++ b/net/socket/ssl_client_socket.h
@@ -17,6 +17,7 @@ namespace net {
class SSLCertRequestInfo;
class SSLInfo;
+class SSLNonSensitiveHostInfo;
struct RRResponse;
// DNSSECProvider is an interface to an object that can return DNSSEC data.
diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc
index fb5668a..88106f9 100644
--- a/net/socket/ssl_client_socket_nss.cc
+++ b/net/socket/ssl_client_socket_nss.cc
@@ -61,6 +61,8 @@
#include <ssl.h>
#include <sslerr.h>
+#include <limits>
+
#include "base/compiler_specific.h"
#include "base/histogram.h"
#include "base/logging.h"
@@ -81,6 +83,7 @@
#include "net/base/ssl_cert_request_info.h"
#include "net/base/ssl_connection_status_flags.h"
#include "net/base/ssl_info.h"
+#include "net/base/ssl_non_sensitive_host_info.h"
#include "net/base/sys_addrinfo.h"
#include "net/ocsp/nss_ocsp.h"
#include "net/socket/client_socket_handle.h"
@@ -353,11 +356,14 @@ SSLClientSocketNSS::SSLClientSocketNSS(ClientSocketHandle* transport_socket,
client_auth_cert_needed_(false),
handshake_callback_called_(false),
completed_handshake_(false),
+ pseudo_connected_(false),
dnssec_provider_(NULL),
next_handshake_state_(STATE_NONE),
nss_fd_(NULL),
nss_bufs_(NULL),
- net_log_(transport_socket->socket()->NetLog()) {
+ net_log_(transport_socket->socket()->NetLog()),
+ predicted_npn_status_(kNextProtoUnsupported),
+ predicted_npn_proto_used_(false) {
EnterFunction("");
}
@@ -385,6 +391,248 @@ int SSLClientSocketNSS::Init() {
return OK;
}
+// This is a version number of the Snap Start information saved by
+// |SaveSnapStartInfo| and loaded by |LoadSnapStartInfo|. Since the information
+// can be saved on disk we might have version skew in the future. Any data with
+// a different version is ignored by |LoadSnapStartInfo|.
+static const uint8 kSnapStartInfoVersion = 0;
+
+// SaveSnapStartInfo serialises the information needed to perform a Snap Start
+// with this server in the future (if any) and tells
+// |ssl_config_.ssl_host_info| to preserve it.
+void SSLClientSocketNSS::SaveSnapStartInfo() {
+ if (!ssl_config_.ssl_host_info.get())
+ return;
+
+ SECStatus rv;
+ SSLSnapStartResult snap_start_type;
+ rv = SSL_GetSnapStartResult(nss_fd_, &snap_start_type);
+ if (rv != SECSuccess) {
+ NOTREACHED();
+ return;
+ }
+ if (snap_start_type == SSL_SNAP_START_FULL ||
+ snap_start_type == SSL_SNAP_START_RESUME) {
+ // If we did a successful Snap Start then our information was correct and
+ // there's no point saving it again.
+ return;
+ }
+
+ const unsigned char* hello_data;
+ unsigned hello_data_len;
+ rv = SSL_GetPredictedServerHelloData(nss_fd_, &hello_data, &hello_data_len);
+ if (rv != SECSuccess) {
+ NOTREACHED();
+ return;
+ }
+ // If the server doesn't support Snap Start then |hello_data_len| is zero.
+ if (!hello_data_len)
+ return;
+ if (hello_data_len > std::numeric_limits<uint16>::max())
+ return;
+
+ // The format of the saved info looks like:
+ // struct Cert {
+ // uint16 length
+ // opaque certificate[length];
+ // }
+ //
+ // uint8 version (kSnapStartInfoVersion)
+ // uint8 npn_status
+ // uint8 npn_proto_len
+ // uint8 npn_proto[npn_proto_len]
+ // uint16 hello_data_len
+ // opaque hello_data[hello_data_len]
+ // uint8 num_certs;
+ // Cert[num_certs];
+
+ std::string npn_proto;
+ NextProtoStatus npn_status = GetNextProto(&npn_proto);
+
+ unsigned num_certs = 0;
+ unsigned len = 3;
+ DCHECK_LT(npn_proto.size(), 256u);
+ len += npn_proto.size();
+ len += 2; // for hello_data_len
+ len += hello_data_len;
+ len++; // for |num_certs|
+
+ // TODO(wtc): CERT_GetCertChainFromCert might not return the same cert chain
+ // that the Certificate message actually contained. http://crbug.com/48854
+ CERTCertList* cert_list = CERT_GetCertChainFromCert(
+ server_cert_nss_, PR_Now(), certUsageSSLCA);
+ if (!cert_list)
+ return;
+
+ unsigned last_cert_len = 0;
+ bool last_cert_is_root = false;
+ for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list);
+ !CERT_LIST_END(node, cert_list);
+ node = CERT_LIST_NEXT(node)) {
+ num_certs++;
+ if (node->cert->derCert.len > std::numeric_limits<uint16>::max()) {
+ CERT_DestroyCertList(cert_list);
+ return;
+ }
+ last_cert_len = node->cert->derCert.len;
+ len += 2 + last_cert_len;
+ last_cert_is_root = node->cert->isRoot == PR_TRUE;
+ }
+
+ if (num_certs == 0 || num_certs > std::numeric_limits<uint8>::max()) {
+ CERT_DestroyCertList(cert_list);
+ return;
+ }
+
+ if (num_certs > 1 && last_cert_is_root) {
+ // The cert list included the root certificate, which we don't want to
+ // save. (Since we need to predict the server's certificates we don't want
+ // to predict the root cert because the server won't send it to us. We
+ // could implement this logic either here, or in the code which loads the
+ // certificates. But, by doing it here, we save a little disk space).
+ //
+ // Note that, when the TODO above (http://crbug.com/48854) is handled, this
+ // point will be moot.
+ len -= 2 + last_cert_len;
+ num_certs--;
+ }
+
+ std::vector<uint8> data(len);
+ unsigned j = 0;
+ data[j++] = kSnapStartInfoVersion;
+ data[j++] = static_cast<uint8>(npn_status);
+ data[j++] = static_cast<uint8>(npn_proto.size());
+ memcpy(&data[j], npn_proto.data(), npn_proto.size());
+ j += npn_proto.size();
+ data[j++] = hello_data_len >> 8;
+ data[j++] = hello_data_len;
+ memcpy(&data[j], hello_data, hello_data_len);
+ j += hello_data_len;
+ data[j++] = num_certs;
+
+ unsigned i = 0;
+ for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list);
+ i < num_certs;
+ node = CERT_LIST_NEXT(node), i++) {
+ data[j++] = node->cert->derCert.len >> 8;
+ data[j++] = node->cert->derCert.len;
+ memcpy(&data[j], node->cert->derCert.data, node->cert->derCert.len);
+ j += node->cert->derCert.len;
+ }
+
+ DCHECK_EQ(j, len);
+
+ ssl_config_.ssl_host_info->Set(std::string(
+ reinterpret_cast<const char *>(&data[0]), len));
+
+ CERT_DestroyCertList(cert_list);
+}
+
+static void DestroyCertificates(CERTCertificate** certs, unsigned len) {
+ for (unsigned i = 0; i < len; i++)
+ CERT_DestroyCertificate(certs[i]);
+}
+
+// LoadSnapStartInfo parses |info|, which contains data previously serialised
+// by |SaveSnapStartInfo|, and sets the predicted certificates and ServerHello
+// data on the NSS socket. Returns true on success. If this function returns
+// false, the caller should try a normal TLS handshake.
+bool SSLClientSocketNSS::LoadSnapStartInfo(const std::string& info) {
+ const unsigned char* data =
+ reinterpret_cast<const unsigned char*>(info.data());
+ SECStatus rv;
+
+ // See the comment in |SaveSnapStartInfo| for the format of the data.
+ if (info.size() < 3 ||
+ data[0] != kSnapStartInfoVersion) {
+ return false;
+ }
+
+ unsigned j = 1;
+ const uint8 npn_status = data[j++];
+ const uint8 npn_proto_len = data[j++];
+ if (static_cast<unsigned>(npn_proto_len) + j > info.size()) {
+ NOTREACHED();
+ return false;
+ }
+ const std::string npn_proto(info.substr(j, npn_proto_len));
+ j += npn_proto_len;
+
+ if (j + 2 > info.size()) {
+ NOTREACHED();
+ return false;
+ }
+ uint16 hello_data_len = static_cast<uint16>(data[j]) << 8 |
+ static_cast<uint16>(data[j+1]);
+ j += 2;
+ if (static_cast<unsigned>(hello_data_len) + j > info.size()) {
+ NOTREACHED();
+ return false;
+ }
+
+ rv = SSL_SetPredictedServerHelloData(nss_fd_, &data[j], hello_data_len);
+ DCHECK_EQ(SECSuccess, rv);
+ j += hello_data_len;
+
+ if (j + 1 > info.size()) {
+ NOTREACHED();
+ return false;
+ }
+ unsigned num_certs = data[j++];
+ scoped_array<CERTCertificate*> certs(new CERTCertificate*[num_certs]);
+
+ for (unsigned i = 0; i < num_certs; i++) {
+ if (j + 2 > info.size()) {
+ DestroyCertificates(&certs[0], i);
+ NOTREACHED();
+ // It's harmless to call only SSL_SetPredictedServerHelloData.
+ return false;
+ }
+ uint16 cert_len = static_cast<uint16>(data[j]) << 8 |
+ static_cast<uint16>(data[j+1]);
+ j += 2;
+ if (static_cast<unsigned>(cert_len) + j > info.size()) {
+ DestroyCertificates(&certs[0], i);
+ NOTREACHED();
+ return false;
+ }
+ SECItem derCert;
+ derCert.data = const_cast<uint8*>(data + j);
+ derCert.len = cert_len;
+ j += cert_len;
+ certs[i] = CERT_NewTempCertificate(
+ CERT_GetDefaultCertDB(), &derCert, NULL /* no nickname given */,
+ PR_FALSE /* not permanent */, PR_TRUE /* copy DER data */);
+ if (!certs[i]) {
+ DestroyCertificates(&certs[0], i);
+ NOTREACHED();
+ return false;
+ }
+ }
+
+ rv = SSL_SetPredictedPeerCertificates(nss_fd_, certs.get(), num_certs);
+ DestroyCertificates(&certs[0], num_certs);
+ DCHECK_EQ(SECSuccess, rv);
+
+ predicted_npn_status_ = static_cast<NextProtoStatus>(npn_status);
+ predicted_npn_proto_ = npn_proto;
+
+ // We ignore any trailing data that might be in |info|.
+ if (j != info.size())
+ LOG(WARNING) << "Trailing data found in SSLNonSensitiveHostInfo";
+
+ return true;
+}
+
+bool SSLClientSocketNSS::IsNPNProtocolMispredicted() {
+ DCHECK(handshake_callback_called_);
+ if (!predicted_npn_proto_used_)
+ return false;
+ std::string npn_proto;
+ GetNextProto(&npn_proto);
+ return predicted_npn_proto_ != npn_proto;
+}
+
int SSLClientSocketNSS::Connect(CompletionCallback* callback) {
EnterFunction("");
DCHECK(transport_.get());
@@ -394,6 +642,7 @@ int SSLClientSocketNSS::Connect(CompletionCallback* callback) {
DCHECK(!user_connect_callback_);
DCHECK(!user_read_buf_);
DCHECK(!user_write_buf_);
+ DCHECK(!pseudo_connected_);
net_log_.BeginEvent(NetLog::TYPE_SSL_CONNECT, NULL);
@@ -409,10 +658,20 @@ int SSLClientSocketNSS::Connect(CompletionCallback* callback) {
return rv;
}
- GotoState(STATE_HANDSHAKE);
+ if (ssl_config_.snap_start_enabled && ssl_config_.ssl_host_info.get()) {
+ GotoState(STATE_SNAP_START_LOAD_INFO);
+ } else {
+ GotoState(STATE_HANDSHAKE);
+ }
+
rv = DoHandshakeLoop(OK);
if (rv == ERR_IO_PENDING) {
- user_connect_callback_ = callback;
+ if (pseudo_connected_) {
+ net_log_.EndEvent(NetLog::TYPE_SSL_CONNECT, NULL);
+ rv = OK;
+ } else {
+ user_connect_callback_ = callback;
+ }
} else {
net_log_.EndEvent(NetLog::TYPE_SSL_CONNECT, NULL);
}
@@ -518,6 +777,14 @@ int SSLClientSocketNSS::InitializeSSLOptions() {
LOG(INFO) << "SSL_ENABLE_FALSE_START failed. Old system nss?";
#endif
+#ifdef SSL_ENABLE_SNAP_START
+ // TODO(agl): check that SSL_ENABLE_SNAP_START actually does something in the
+ // current NSS code.
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SNAP_START, PR_TRUE);
+ if (rv != SECSuccess)
+ LOG(INFO) << "SSL_ENABLE_SNAP_START failed. Old system nss?";
+#endif
+
#ifdef SSL_ENABLE_RENEGOTIATION
// Deliberately disable this check for now: http://crbug.com/55410
if (false &&
@@ -627,6 +894,7 @@ void SSLClientSocketNSS::Disconnect() {
}
server_cert_verify_result_.Reset();
completed_handshake_ = false;
+ pseudo_connected_ = false;
nss_bufs_ = NULL;
client_certs_.clear();
client_auth_cert_needed_ = false;
@@ -642,7 +910,8 @@ bool SSLClientSocketNSS::IsConnected() const {
// closed by the server when we send a request anyway, a false positive in
// exchange for simpler code is a good trade-off.
EnterFunction("");
- bool ret = completed_handshake_ && transport_->socket()->IsConnected();
+ bool ret = (pseudo_connected_ || completed_handshake_) &&
+ transport_->socket()->IsConnected();
LeaveFunction("");
return ret;
}
@@ -657,7 +926,8 @@ bool SSLClientSocketNSS::IsConnectedAndIdle() const {
// transport_->socket()->IsConnectedAndIdle() returns the desired false
// when we receive close_notify.
EnterFunction("");
- bool ret = completed_handshake_ && transport_->socket()->IsConnectedAndIdle();
+ bool ret = (pseudo_connected_ || completed_handshake_) &&
+ transport_->socket()->IsConnectedAndIdle();
LeaveFunction("");
return ret;
}
@@ -693,8 +963,6 @@ bool SSLClientSocketNSS::WasEverUsed() const {
int SSLClientSocketNSS::Read(IOBuffer* buf, int buf_len,
CompletionCallback* callback) {
EnterFunction(buf_len);
- DCHECK(completed_handshake_);
- DCHECK(next_handshake_state_ == STATE_NONE);
DCHECK(!user_read_callback_);
DCHECK(!user_connect_callback_);
DCHECK(!user_read_buf_);
@@ -703,6 +971,17 @@ int SSLClientSocketNSS::Read(IOBuffer* buf, int buf_len,
user_read_buf_ = buf;
user_read_buf_len_ = buf_len;
+ if (!completed_handshake_) {
+ // In this case we have lied about being connected in order to merge the
+ // first Write into a Snap Start handshake. We'll leave the read hanging
+ // until the handshake has completed.
+ DCHECK(pseudo_connected_);
+
+ user_read_callback_ = callback;
+ LeaveFunction(ERR_IO_PENDING);
+ return ERR_IO_PENDING;
+ }
+
int rv = DoReadLoop(OK);
if (rv == ERR_IO_PENDING) {
@@ -718,16 +997,34 @@ int SSLClientSocketNSS::Read(IOBuffer* buf, int buf_len,
int SSLClientSocketNSS::Write(IOBuffer* buf, int buf_len,
CompletionCallback* callback) {
EnterFunction(buf_len);
- DCHECK(completed_handshake_);
- DCHECK(next_handshake_state_ == STATE_NONE);
+ if (!pseudo_connected_) {
+ DCHECK(completed_handshake_);
+ DCHECK(next_handshake_state_ == STATE_NONE);
+ DCHECK(!user_connect_callback_);
+ }
DCHECK(!user_write_callback_);
- DCHECK(!user_connect_callback_);
DCHECK(!user_write_buf_);
DCHECK(nss_bufs_);
user_write_buf_ = buf;
user_write_buf_len_ = buf_len;
+ if (next_handshake_state_ == STATE_SNAP_START_WAIT_FOR_WRITE) {
+ // We lied about being connected and we have been waiting for this write in
+ // order to merge it into the Snap Start handshake. We'll leave the write
+ // pending until the handshake completes.
+ DCHECK(pseudo_connected_);
+ int rv = DoHandshakeLoop(OK);
+ if (rv == ERR_IO_PENDING) {
+ user_write_callback_ = callback;
+ } else {
+ user_write_buf_ = NULL;
+ user_write_buf_len_ = 0;
+ }
+ if (rv != OK)
+ return rv;
+ }
+
corked_ = false;
int rv = DoWriteLoop(OK);
@@ -773,7 +1070,7 @@ X509Certificate::OSCertHandle SSLClientSocketNSS::CreateOSCert(
#endif
X509Certificate *SSLClientSocketNSS::UpdateServerCert() {
- // We set the server_cert_ from OwnAuthCertHandler(), but this handler
+ // We set the server_cert_ from HandshakeCallback(), but this handler
// does not necessarily get called if we are continuing a cached SSL
// session.
if (server_cert_ == NULL) {
@@ -850,8 +1147,11 @@ void SSLClientSocketNSS::CheckSecureRenegotiation() const {
void SSLClientSocketNSS::GetSSLInfo(SSLInfo* ssl_info) {
EnterFunction("");
ssl_info->Reset();
- if (!server_cert_)
+
+ if (!server_cert_) {
+ LOG(DFATAL) << "!server_cert_";
return;
+ }
SSLChannelInfo channel_info;
SECStatus ok = SSL_GetChannelInfo(nss_fd_,
@@ -911,6 +1211,13 @@ void SSLClientSocketNSS::GetSSLCertRequestInfo(
SSLClientSocket::NextProtoStatus
SSLClientSocketNSS::GetNextProto(std::string* proto) {
#if defined(SSL_NEXT_PROTO_NEGOTIATED)
+ if (!handshake_callback_called_) {
+ DCHECK(pseudo_connected_);
+ predicted_npn_proto_used_ = true;
+ *proto = predicted_npn_proto_;
+ return predicted_npn_status_;
+ }
+
unsigned char buf[255];
int state;
unsigned len;
@@ -988,7 +1295,6 @@ void SSLClientSocketNSS::DoWriteCallback(int rv) {
void SSLClientSocketNSS::DoConnectCallback(int rv) {
EnterFunction(rv);
DCHECK_NE(rv, ERR_IO_PENDING);
- DCHECK(user_connect_callback_);
CompletionCallback* c = user_connect_callback_;
user_connect_callback_ = NULL;
@@ -1001,7 +1307,10 @@ void SSLClientSocketNSS::OnHandshakeIOComplete(int result) {
int rv = DoHandshakeLoop(result);
if (rv != ERR_IO_PENDING) {
net_log_.EndEvent(net::NetLog::TYPE_SSL_CONNECT, NULL);
- DoConnectCallback(rv);
+ // If we pseudo connected for Snap Start, then we won't have a connect
+ // callback.
+ if (user_connect_callback_)
+ DoConnectCallback(rv);
}
LeaveFunction("");
}
@@ -1048,8 +1357,8 @@ void SSLClientSocketNSS::OnRecvComplete(int result) {
}
// Network layer received some data, check if client requested to read
- // decrypted data.
- if (!user_read_buf_) {
+ // decrypted data or if we're waiting for the first write for Snap Start.
+ if (!user_read_buf_ || !completed_handshake_) {
LeaveFunction("");
return;
}
@@ -1211,6 +1520,12 @@ int SSLClientSocketNSS::DoHandshakeLoop(int last_io_result) {
case STATE_NONE:
// we're just pumping data between the buffer and the network
break;
+ case STATE_SNAP_START_LOAD_INFO:
+ rv = DoSnapStartLoadInfo();
+ break;
+ case STATE_SNAP_START_WAIT_FOR_WRITE:
+ rv = DoSnapStartWaitForWrite();
+ break;
case STATE_HANDSHAKE:
rv = DoHandshake();
break;
@@ -1236,7 +1551,7 @@ int SSLClientSocketNSS::DoHandshakeLoop(int last_io_result) {
// Do the actual network I/O
network_moved = DoTransportIO();
} while ((rv != ERR_IO_PENDING || network_moved) &&
- next_handshake_state_ != STATE_NONE);
+ next_handshake_state_ != STATE_NONE);
LeaveFunction("");
return rv;
}
@@ -1510,13 +1825,73 @@ void SSLClientSocketNSS::HandshakeCallback(PRFileDesc* socket,
void* arg) {
SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg);
- that->set_handshake_callback_called();
+ that->handshake_callback_called_ = true;
that->UpdateServerCert();
that->CheckSecureRenegotiation();
}
+int SSLClientSocketNSS::DoSnapStartLoadInfo() {
+ EnterFunction("");
+ int rv = ssl_config_.ssl_host_info->WaitForDataReady(&handshake_io_callback_);
+
+ if (rv == OK) {
+ if (LoadSnapStartInfo(ssl_config_.ssl_host_info->data())) {
+ pseudo_connected_ = true;
+ GotoState(STATE_SNAP_START_WAIT_FOR_WRITE);
+ if (user_connect_callback_)
+ DoConnectCallback(OK);
+ } else {
+ GotoState(STATE_HANDSHAKE);
+ }
+ } else {
+ DCHECK_EQ(ERR_IO_PENDING, rv);
+ GotoState(STATE_SNAP_START_LOAD_INFO);
+ }
+
+ LeaveFunction("");
+ return rv;
+}
+
+int SSLClientSocketNSS::DoSnapStartWaitForWrite() {
+ EnterFunction("");
+ // In this state, we're waiting for the first Write call so that we can merge
+ // it into the Snap Start handshake.
+ if (!user_write_buf_) {
+ // We'll lie and say that we're connected in order that someone will call
+ // Write.
+ GotoState(STATE_SNAP_START_WAIT_FOR_WRITE);
+ DCHECK(!user_connect_callback_);
+ LeaveFunction("");
+ return ERR_IO_PENDING;
+ }
+
+ // This is the largest Snap Start application data payload that we'll try to
+ // use. A TCP client can only send three frames of data without an ACK and,
+ // at 2048 bytes, this leaves some space for the rest of the ClientHello
+ // (including possible session ticket).
+ static const int kMaxSnapStartPayloadSize = 2048;
+
+ if (user_write_buf_len_ > kMaxSnapStartPayloadSize) {
+ user_write_buf_len_ = kMaxSnapStartPayloadSize;
+ // When we complete the handshake and call user_write_callback_ we'll say
+ // that we only wrote |kMaxSnapStartPayloadSize| bytes. That way the rest
+ // of the payload will be presented to |Write| again and transmitted as
+ // normal application data.
+ }
+
+ SECStatus rv = SSL_SetSnapStartApplicationData(
+ nss_fd_, reinterpret_cast<const unsigned char*>(user_write_buf_->data()),
+ user_write_buf_len_);
+ DCHECK_EQ(SECSuccess, rv);
+ user_write_buf_ = NULL;
+
+ GotoState(STATE_HANDSHAKE);
+ LeaveFunction("");
+ return OK;
+}
+
int SSLClientSocketNSS::DoHandshake() {
EnterFunction("");
int net_error = net::OK;
@@ -1534,8 +1909,19 @@ int SSLClientSocketNSS::DoHandshake() {
}
} else if (rv == SECSuccess) {
if (handshake_callback_called_) {
- // SSL handshake is completed. Let's verify the certificate.
- GotoState(STATE_VERIFY_DNSSEC);
+ SaveSnapStartInfo();
+ // SSL handshake is completed. It's possible that we mispredicted the NPN
+ // agreed protocol. In this case, we've just sent a request in the wrong
+ // protocol! The higher levels of this network stack aren't prepared for
+ // switching the protocol like that so we make up an error and rely on
+ // the fact that the request will be retried.
+ if (IsNPNProtocolMispredicted()) {
+ LOG(WARNING) << "Mispredicted NPN protocol for " << hostname_;
+ net_error = ERR_SSL_SNAP_START_NPN_MISPREDICTION;
+ } else {
+ // Let's verify the certificate.
+ GotoState(STATE_VERIFY_DNSSEC);
+ }
// Done!
} else {
// SSL_ForceHandshake returned SECSuccess prematurely.
@@ -1848,6 +2234,14 @@ int SSLClientSocketNSS::DoVerifyCertComplete(int result) {
// TODO(ukai): we may not need this call because it is now harmless to have a
// session with a bad cert.
InvalidateSessionIfBadCertificate();
+
+ // Likewise, if we merged a Write call into the handshake we need to make the
+ // callback now.
+ if (user_write_callback_) {
+ corked_ = false;
+ DoWriteCallback(user_write_buf_len_);
+ }
+
// Exit DoHandshakeLoop and return the result to the caller to Connect.
DCHECK(next_handshake_state_ == STATE_NONE);
return result;
diff --git a/net/socket/ssl_client_socket_nss.h b/net/socket/ssl_client_socket_nss.h
index 3796826..7f5ee94 100644
--- a/net/socket/ssl_client_socket_nss.h
+++ b/net/socket/ssl_client_socket_nss.h
@@ -66,8 +66,6 @@ class SSLClientSocketNSS : public SSLClientSocket {
virtual bool SetReceiveBufferSize(int32 size);
virtual bool SetSendBufferSize(int32 size);
- void set_handshake_callback_called() { handshake_callback_called_ = true; }
-
private:
// Initializes NSS SSL options. Returns a net error code.
int InitializeSSLOptions();
@@ -90,6 +88,8 @@ class SSLClientSocketNSS : public SSLClientSocket {
int DoReadLoop(int result);
int DoWriteLoop(int result);
+ int DoSnapStartLoadInfo();
+ int DoSnapStartWaitForWrite();
int DoHandshake();
int DoVerifyDNSSEC(int result);
@@ -99,6 +99,9 @@ class SSLClientSocketNSS : public SSLClientSocket {
int DoPayloadRead();
int DoPayloadWrite();
int Init();
+ void SaveSnapStartInfo();
+ bool LoadSnapStartInfo(const std::string& info);
+ bool IsNPNProtocolMispredicted();
bool DoTransportIO();
int BufferSend(void);
@@ -166,6 +169,10 @@ class SSLClientSocketNSS : public SSLClientSocket {
// True if the SSL handshake has been completed.
bool completed_handshake_;
+ // True if we are lying about being connected in order to merge the first
+ // Write call into a Snap Start handshake.
+ bool pseudo_connected_;
+
// This pointer is owned by the caller of UseDNSSEC.
DNSSECProvider* dnssec_provider_;
// The time when we started waiting for DNSSEC records.
@@ -173,6 +180,8 @@ class SSLClientSocketNSS : public SSLClientSocket {
enum State {
STATE_NONE,
+ STATE_SNAP_START_LOAD_INFO,
+ STATE_SNAP_START_WAIT_FOR_WRITE,
STATE_HANDSHAKE,
STATE_VERIFY_DNSSEC,
STATE_VERIFY_DNSSEC_COMPLETE,
@@ -189,6 +198,14 @@ class SSLClientSocketNSS : public SSLClientSocket {
BoundNetLog net_log_;
+ // When performing Snap Start we need to predict the NPN protocol which the
+ // server is going to speak before we actually perform the handshake. Thus
+ // the last NPN protocol used is serialised in |ssl_config.ssl_host_info|
+ // and kept in these fields:
+ SSLClientSocket::NextProtoStatus predicted_npn_status_;
+ std::string predicted_npn_proto_;
+ bool predicted_npn_proto_used_;
+
#if defined(OS_WIN)
// A CryptoAPI in-memory certificate store. We use it for two purposes:
// 1. Import server certificates into this store so that we can verify and