summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorwtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-19 19:57:01 +0000
committerwtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-19 19:57:01 +0000
commit5e363960aa45fc4a906e51bfe7964db86f1fa656 (patch)
treef54f261bd8c07234b46f98ba2127fa526ea89f5e /net
parentb3c6b7f71bb588570107770b42c00480abe221b6 (diff)
downloadchromium_src-5e363960aa45fc4a906e51bfe7964db86f1fa656.zip
chromium_src-5e363960aa45fc4a906e51bfe7964db86f1fa656.tar.gz
chromium_src-5e363960aa45fc4a906e51bfe7964db86f1fa656.tar.bz2
Implement the backend of SSL client authentication for
Windows. Create Schannel SSPI CredHandles with certificates for SSL client authentication. Remember the client certificates that the user selected so that we don't ask the user again and again. R=rvargas,eroman BUG=http://crbug.com/318 TEST=none Review URL: http://codereview.chromium.org/131086 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@18841 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r--net/base/ssl_cert_request_info.h7
-rw-r--r--net/base/ssl_client_socket_win.cc194
-rw-r--r--net/http/http_network_transaction.cc51
-rw-r--r--net/http/http_network_transaction.h2
4 files changed, 220 insertions, 34 deletions
diff --git a/net/base/ssl_cert_request_info.h b/net/base/ssl_cert_request_info.h
index 2529d87..913a219 100644
--- a/net/base/ssl_cert_request_info.h
+++ b/net/base/ssl_cert_request_info.h
@@ -25,6 +25,13 @@ class SSLCertRequestInfo
// A list of client certificates that match the server's criteria in the
// SSL CertificateRequest message. In TLS 1.0, the CertificateRequest
// message is defined as:
+ // enum {
+ // rsa_sign(1), dss_sign(2), rsa_fixed_dh(3), dss_fixed_dh(4),
+ // (255)
+ // } ClientCertificateType;
+ //
+ // opaque DistinguishedName<1..2^16-1>;
+ //
// struct {
// ClientCertificateType certificate_types<1..2^8-1>;
// DistinguishedName certificate_authorities<3..2^16-1>;
diff --git a/net/base/ssl_client_socket_win.cc b/net/base/ssl_client_socket_win.cc
index 43641e3..3517adc 100644
--- a/net/base/ssl_client_socket_win.cc
+++ b/net/base/ssl_client_socket_win.cc
@@ -12,6 +12,7 @@
#include "net/base/connection_type_histograms.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
+#include "net/base/ssl_cert_request_info.h"
#include "net/base/ssl_info.h"
#pragma comment(lib, "secur32.lib")
@@ -62,8 +63,9 @@ static int MapSecurityError(SECURITY_STATUS err) {
// Returns true if the two CERT_CONTEXTs contain the same certificate.
bool SameCert(PCCERT_CONTEXT a, PCCERT_CONTEXT b) {
- return a->cbCertEncoded == b->cbCertEncoded &&
- memcmp(a->pbCertEncoded, b->pbCertEncoded, b->cbCertEncoded) == 0;
+ return a == b ||
+ (a->cbCertEncoded == b->cbCertEncoded &&
+ memcmp(a->pbCertEncoded, b->pbCertEncoded, b->cbCertEncoded) == 0);
}
//-----------------------------------------------------------------------------
@@ -77,44 +79,80 @@ enum {
SSL_VERSION_MASKS = 1 << 3 // The number of SSL version bitmasks.
};
-// A table of CredHandles for all possible combinations of SSL versions.
-class CredHandleTable {
+// CredHandleClass simply gives a default constructor and a destructor to
+// SSPI's CredHandle type (a C struct). The default constuctor is required
+// by STL containers.
+class CredHandleClass : public CredHandle {
public:
- CredHandleTable() {
- memset(creds_, 0, sizeof(creds_));
+ CredHandleClass() {
+ dwLower = 0;
+ dwUpper = 0;
}
- // Frees the CredHandles.
- ~CredHandleTable() {
- for (int i = 0; i < arraysize(creds_); ++i) {
- if (creds_[i].dwLower || creds_[i].dwUpper)
- FreeCredentialsHandle(&creds_[i]);
+ ~CredHandleClass() {
+ if (dwLower || dwUpper) {
+ SECURITY_STATUS status = FreeCredentialsHandle(this);
+ DCHECK(status == SEC_E_OK);
}
}
+};
- CredHandle* GetHandle(int ssl_version_mask) {
- DCHECK(0 < ssl_version_mask && ssl_version_mask < arraysize(creds_));
- CredHandle* handle = &creds_[ssl_version_mask];
- {
- AutoLock lock(lock_);
- if (!handle->dwLower && !handle->dwUpper)
- InitializeHandle(handle, ssl_version_mask);
+// A table of CredHandles.
+class CredHandleTable {
+ public:
+ CredHandleTable() {}
+
+ ~CredHandleTable() {}
+
+ CredHandle* GetHandle(PCCERT_CONTEXT client_cert, int ssl_version_mask) {
+ DCHECK(0 < ssl_version_mask &&
+ ssl_version_mask < arraysize(anonymous_creds_));
+ CredHandle* handle;
+ AutoLock lock(lock_);
+ if (client_cert) {
+ handle = &client_cert_creds_[
+ std::make_pair(client_cert, ssl_version_mask)];
+ } else {
+ handle = &anonymous_creds_[ssl_version_mask];
}
+ if (!handle->dwLower && !handle->dwUpper)
+ InitializeHandle(handle, client_cert, ssl_version_mask);
return handle;
}
private:
- static void InitializeHandle(CredHandle* handle, int ssl_version_mask);
+ // CredHandleMapKey is a std::pair consisting of these two components:
+ // PCCERT_CONTEXT client_cert
+ // int ssl_version_mask
+ typedef std::pair<PCCERT_CONTEXT, int> CredHandleMapKey;
+
+ typedef std::map<CredHandleMapKey, CredHandleClass> CredHandleMap;
+
+ static void InitializeHandle(CredHandle* handle,
+ PCCERT_CONTEXT client_cert,
+ int ssl_version_mask);
Lock lock_;
- CredHandle creds_[SSL_VERSION_MASKS];
+
+ // Anonymous (no client certificate) CredHandles for all possible
+ // combinations of SSL versions. Defined as an array for fast lookup.
+ CredHandleClass anonymous_creds_[SSL_VERSION_MASKS];
+
+ // CredHandles that use a client certificate.
+ CredHandleMap client_cert_creds_;
};
// static
void CredHandleTable::InitializeHandle(CredHandle* handle,
+ PCCERT_CONTEXT client_cert,
int ssl_version_mask) {
SCHANNEL_CRED schannel_cred = {0};
schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;
+ if (client_cert) {
+ schannel_cred.cCreds = 1;
+ schannel_cred.paCred = &client_cert;
+ // Schannel will make its own copy of client_cert.
+ }
// The global system registry settings take precedence over the value of
// schannel_cred.grbitEnabledProtocols.
@@ -180,14 +218,9 @@ void CredHandleTable::InitializeHandle(CredHandle* handle,
// versions (for example, SSL3 | TLS1 for normal use, plus SSL3 when visiting
// TLS-intolerant servers). These CredHandles are initialized only when
// needed.
-//
-// NOTE: Since the client authentication certificate is also a property of the
-// CredHandle, SSL sockets won't be able to use the shared CredHandles when we
-// support SSL client authentication. So we will need to refine the way we
-// share SSL sessions. For now the simple solution of using shared
-// CredHandles is good enough.
-static CredHandle* GetCredHandle(int ssl_version_mask) {
+static CredHandle* GetCredHandle(PCCERT_CONTEXT client_cert,
+ int ssl_version_mask) {
// It doesn't matter whether GetCredHandle returns NULL or a pointer to an
// uninitialized CredHandle on failure. Both of them cause
// InitializeSecurityContext to fail with SEC_E_INVALID_HANDLE.
@@ -195,11 +228,43 @@ static CredHandle* GetCredHandle(int ssl_version_mask) {
NOTREACHED();
return NULL;
}
- return Singleton<CredHandleTable>::get()->GetHandle(ssl_version_mask);
+ return Singleton<CredHandleTable>::get()->GetHandle(client_cert,
+ ssl_version_mask);
}
//-----------------------------------------------------------------------------
+// A memory certificate store for client certificates. This allows us to
+// close the "MY" system certificate store when we finish searching for
+// client certificates.
+class ClientCertStore {
+ public:
+ ClientCertStore() {
+ store_ = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, 0, NULL);
+ }
+
+ ~ClientCertStore() {
+ if (store_) {
+ BOOL ok = CertCloseStore(store_, CERT_CLOSE_STORE_CHECK_FLAG);
+ DCHECK(ok);
+ }
+ }
+
+ PCCERT_CONTEXT CopyCertContext(PCCERT_CONTEXT client_cert) {
+ PCCERT_CONTEXT copy;
+ BOOL ok = CertAddCertificateContextToStore(store_, client_cert,
+ CERT_STORE_ADD_USE_EXISTING,
+ &copy);
+ DCHECK(ok);
+ return ok ? copy : NULL;
+ }
+
+ private:
+ HCERTSTORE store_;
+};
+
+//-----------------------------------------------------------------------------
+
// Size of recv_buffer_
//
// Ciphertext is decrypted one SSL record at a time, so recv_buffer_ needs to
@@ -262,7 +327,73 @@ void SSLClientSocketWin::GetSSLInfo(SSLInfo* ssl_info) {
void SSLClientSocketWin::GetSSLCertRequestInfo(
SSLCertRequestInfo* cert_request_info) {
- // TODO(wtc): implement this.
+ cert_request_info->host_and_port = hostname_; // TODO(wtc): no port!
+ cert_request_info->client_certs.clear();
+
+ // Get the certificate_authorities field of the CertificateRequest message.
+ // Schannel doesn't return the certificate_types field of the
+ // CertificateRequest message to us, so we can't filter the client
+ // certificates properly. :-(
+ SecPkgContext_IssuerListInfoEx issuer_list;
+ SECURITY_STATUS status = QueryContextAttributes(
+ &ctxt_, SECPKG_ATTR_ISSUER_LIST_EX, &issuer_list);
+ if (status != SEC_E_OK) {
+ DLOG(ERROR) << "QueryContextAttributes (issuer list) failed: " << status;
+ return;
+ }
+
+ // Client certificates of the user are in the "MY" system certificate store.
+ HCERTSTORE my_cert_store = CertOpenSystemStore(NULL, L"MY");
+ if (!my_cert_store) {
+ FreeContextBuffer(issuer_list.aIssuers);
+ return;
+ }
+
+ // Enumerate the client certificates.
+ CERT_CHAIN_FIND_BY_ISSUER_PARA find_by_issuer_para;
+ memset(&find_by_issuer_para, 0, sizeof(find_by_issuer_para));
+ find_by_issuer_para.cbSize = sizeof(find_by_issuer_para);
+ find_by_issuer_para.pszUsageIdentifier = szOID_PKIX_KP_CLIENT_AUTH;
+ find_by_issuer_para.cIssuer = issuer_list.cIssuers;
+ find_by_issuer_para.rgIssuer = issuer_list.aIssuers;
+
+ PCCERT_CHAIN_CONTEXT chain_context = NULL;
+
+ for (;;) {
+ // Find a certificate chain.
+ chain_context = CertFindChainInStore(my_cert_store,
+ X509_ASN_ENCODING,
+ 0,
+ CERT_CHAIN_FIND_BY_ISSUER,
+ &find_by_issuer_para,
+ chain_context);
+ if (!chain_context) {
+ DWORD err = GetLastError();
+ if (err != CRYPT_E_NOT_FOUND)
+ DLOG(ERROR) << "CertFindChainInStore failed: " << err;
+ break;
+ }
+
+ // Get the leaf certificate.
+ PCCERT_CONTEXT cert_context =
+ chain_context->rgpChain[0]->rgpElement[0]->pCertContext;
+ // Copy it to our own certificate store, so that we can close the "MY"
+ // certificate store before returning from this function.
+ PCCERT_CONTEXT cert_context2 =
+ Singleton<ClientCertStore>::get()->CopyCertContext(cert_context);
+ if (!cert_context2) {
+ NOTREACHED();
+ continue;
+ }
+ scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromHandle(
+ cert_context2, X509Certificate::SOURCE_LONE_CERT_IMPORT);
+ cert_request_info->client_certs.push_back(cert);
+ }
+
+ FreeContextBuffer(issuer_list.aIssuers);
+
+ BOOL ok = CertCloseStore(my_cert_store, CERT_CLOSE_STORE_CHECK_FLAG);
+ DCHECK(ok);
}
int SSLClientSocketWin::Connect(CompletionCallback* callback) {
@@ -281,7 +412,10 @@ int SSLClientSocketWin::Connect(CompletionCallback* callback) {
// rather than enabling no protocols. So we have to fail here.
if (ssl_version_mask == 0)
return ERR_NO_SSL_VERSIONS_ENABLED;
- creds_ = GetCredHandle(ssl_version_mask);
+ PCCERT_CONTEXT cert_context = NULL;
+ if (ssl_config_.client_cert)
+ cert_context = ssl_config_.client_cert->os_cert_handle();
+ creds_ = GetCredHandle(cert_context, ssl_version_mask);
memset(&ctxt_, 0, sizeof(ctxt_));
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index 2383c38..5a52f2a 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -193,6 +193,10 @@ int HttpNetworkTransaction::RestartWithCertificate(
X509Certificate* client_cert,
CompletionCallback* callback) {
ssl_config_.client_cert = client_cert;
+ if (client_cert) {
+ session_->ssl_client_auth_cache()->Add(GetHostAndPort(request_->url),
+ client_cert);
+ }
ssl_config_.send_client_cert = true;
next_state_ = STATE_INIT_CONNECTION;
// Reset the other member variables.
@@ -634,7 +638,7 @@ int HttpNetworkTransaction::DoSSLConnectComplete(int result) {
if (result == OK) {
next_state_ = STATE_WRITE_HEADERS;
} else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
- HandleCertificateRequest();
+ result = HandleCertificateRequest(result);
} else {
result = HandleSSLHandshakeError(result);
}
@@ -789,7 +793,9 @@ int HttpNetworkTransaction::DoReadHeadersComplete(int result) {
<< " during SSL renegotiation";
result = ERR_CERT_ERROR_IN_SSL_RENEGOTIATION;
} else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
- HandleCertificateRequest();
+ result = HandleCertificateRequest(result);
+ if (result == OK)
+ return result;
}
}
@@ -1255,7 +1261,17 @@ int HttpNetworkTransaction::HandleCertificateError(int error) {
return error;
}
-void HttpNetworkTransaction::HandleCertificateRequest() {
+int HttpNetworkTransaction::HandleCertificateRequest(int error) {
+ // Assert that the socket did not send a client certificate.
+ // Note: If we got a reused socket, it was created with some other
+ // transaction's ssl_config_, so we need to disable this assertion. We can
+ // get a certificate request on a reused socket when the server requested
+ // renegotiation (rehandshake).
+ // TODO(wtc): add a GetSSLParams method to SSLClientSocket so we can query
+ // the SSL parameters it was created with and get rid of the reused_socket_
+ // test.
+ DCHECK(reused_socket_ || !ssl_config_.send_client_cert);
+
response_.cert_request_info = new SSLCertRequestInfo;
SSLClientSocket* ssl_socket =
reinterpret_cast<SSLClientSocket*>(connection_.socket());
@@ -1265,9 +1281,38 @@ void HttpNetworkTransaction::HandleCertificateRequest() {
// to the server.
connection_.socket()->Disconnect();
connection_.Reset();
+
+ // If the user selected one of the certificate in client_certs for this
+ // server before, use it automatically.
+ X509Certificate* client_cert = session_->ssl_client_auth_cache()->
+ Lookup(GetHostAndPort(request_->url));
+ if (client_cert) {
+ const std::vector<scoped_refptr<X509Certificate> >& client_certs =
+ response_.cert_request_info->client_certs;
+ for (size_t i = 0; i < client_certs.size(); ++i) {
+ if (memcmp(&client_cert->fingerprint(),
+ &client_certs[i]->fingerprint(),
+ sizeof(X509Certificate::Fingerprint)) == 0) {
+ ssl_config_.client_cert = client_cert;
+ ssl_config_.send_client_cert = true;
+ next_state_ = STATE_INIT_CONNECTION;
+ // Reset the other member variables.
+ // Note: this is necessary only with SSL renegotiation.
+ ResetStateForRestart();
+ return OK;
+ }
+ }
+ }
+ return error;
}
int HttpNetworkTransaction::HandleSSLHandshakeError(int error) {
+ if (ssl_config_.send_client_cert &&
+ (error == ERR_SSL_PROTOCOL_ERROR ||
+ error == ERR_BAD_SSL_CLIENT_AUTH_CERT)) {
+ session_->ssl_client_auth_cache()->Remove(GetHostAndPort(request_->url));
+ }
+
switch (error) {
case ERR_SSL_PROTOCOL_ERROR:
case ERR_SSL_VERSION_OR_CIPHER_MISMATCH:
diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h
index 13d88b9..6358e6a 100644
--- a/net/http/http_network_transaction.h
+++ b/net/http/http_network_transaction.h
@@ -163,7 +163,7 @@ class HttpNetworkTransaction : public HttpTransaction {
int HandleCertificateError(int error);
// Called to handle a client certificate request.
- void HandleCertificateRequest();
+ int HandleCertificateRequest(int error);
// Called to possibly recover from an SSL handshake error. Sets next_state_
// and returns OK if recovering from the error. Otherwise, the same error