Index: net/socket/ssl_client_socket_nss.cc |
diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc |
index df2ac6aa87a71e38dd6bda6e95344f2542482f74..a26e77d43adc6db5877138bcaf3229ceba587653 100644 |
--- a/net/socket/ssl_client_socket_nss.cc |
+++ b/net/socket/ssl_client_socket_nss.cc |
@@ -50,6 +50,9 @@ |
#if defined(USE_SYSTEM_SSL) |
#include <dlfcn.h> |
#endif |
+#if defined(OS_MACOSX) |
+#include <Security/Security.h> |
+#endif |
#include <certdb.h> |
#include <hasht.h> |
#include <keyhi.h> |
@@ -214,6 +217,8 @@ int MapNSPRError(PRErrorCode err) { |
return ERR_INVALID_ARGUMENT; |
case PR_END_OF_FILE_ERROR: |
return ERR_CONNECTION_CLOSED; |
+ case PR_NOT_IMPLEMENTED_ERROR: |
+ return ERR_NOT_IMPLEMENTED; |
case SEC_ERROR_INVALID_ARGS: |
return ERR_INVALID_ARGUMENT; |
@@ -800,7 +805,12 @@ int SSLClientSocketNSS::InitializeSSLOptions() { |
return ERR_UNEXPECTED; |
} |
+#if defined(NSS_PLATFORM_CLIENT_AUTH) |
+ rv = SSL_GetPlatformClientAuthDataHook(nss_fd_, PlatformClientAuthHandler, |
+ this); |
+#else |
rv = SSL_GetClientAuthDataHook(nss_fd_, ClientAuthHandler, this); |
+#endif |
if (rv != SECSuccess) { |
LogFailedNSSFunction(net_log_, "SSL_GetClientAuthDataHook", ""); |
return ERR_UNEXPECTED; |
@@ -1364,6 +1374,8 @@ static PRErrorCode MapErrorToNSS(int result) { |
case ERR_NETWORK_ACCESS_DENIED: |
// For connect, this could be mapped to PR_ADDRESS_NOT_SUPPORTED_ERROR. |
return PR_NO_ACCESS_RIGHTS_ERROR; |
+ case ERR_NOT_IMPLEMENTED: |
+ return PR_NOT_IMPLEMENTED_ERROR; |
case ERR_INTERNET_DISCONNECTED: // Equivalent to ENETDOWN. |
return PR_NETWORK_UNREACHABLE_ERROR; // Best approximation. |
case ERR_CONNECTION_TIMED_OUT: |
@@ -1650,29 +1662,69 @@ SECStatus SSLClientSocketNSS::OwnAuthCertHandler(void* arg, |
return SECSuccess; |
} |
+#if defined(NSS_PLATFORM_CLIENT_AUTH) |
// static |
// NSS calls this if a client certificate is needed. |
-// Based on Mozilla's NSS_GetClientAuthData. |
-SECStatus SSLClientSocketNSS::ClientAuthHandler( |
+SECStatus SSLClientSocketNSS::PlatformClientAuthHandler( |
void* arg, |
PRFileDesc* socket, |
CERTDistNames* ca_names, |
- CERTCertificate** result_certificate, |
- SECKEYPrivateKey** result_private_key) { |
- // NSS passes a null ca_names if SSL 2.0 is used. Just fail rather than |
- // trying to make this work, as we plan to remove SSL 2.0 support soon. |
- if (!ca_names) |
- return SECFailure; |
- |
+ CERTCertList** result_certs, |
+ void** result_private_key) { |
SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); |
that->client_auth_cert_needed_ = !that->ssl_config_.send_client_cert; |
- |
#if defined(OS_WIN) |
if (that->ssl_config_.send_client_cert) { |
- // TODO(wtc): SSLClientSocketNSS can't do SSL client authentication using |
- // CryptoAPI yet (http://crbug.com/37560), so client_cert must be NULL. |
- DCHECK(!that->ssl_config_.client_cert); |
+ if (that->ssl_config_.client_cert) { |
+ PCCERT_CONTEXT cert_context = |
+ that->ssl_config_.client_cert->os_cert_handle(); |
+ HCRYPTPROV provider = NULL; |
+ DWORD key_spec = AT_KEYEXCHANGE; |
+ BOOL must_free = FALSE; |
+ BOOL acquired_key = CryptAcquireCertificatePrivateKey( |
+ cert_context, |
+ CRYPT_ACQUIRE_CACHE_FLAG | CRYPT_ACQUIRE_COMPARE_KEY_FLAG, |
+ NULL, &provider, &key_spec, &must_free); |
+ if (acquired_key && provider) { |
+ DCHECK_NE(key_spec, CERT_NCRYPT_KEY_SPEC); |
+ |
+ // The certificate cache may have been updated/used, in which case, |
+ // duplicate the existing handle, since NSS will free it when no |
+ // longer in use. |
+ if (!must_free) |
+ CryptContextAddRef(provider, NULL, 0); |
+ |
+ SECItem der_cert; |
+ der_cert.type = siDERCertBuffer; |
+ der_cert.data = cert_context->pbCertEncoded; |
+ der_cert.len = cert_context->cbCertEncoded; |
+ |
+ // TODO(rsleevi): Error checking for NSS allocation errors. |
+ *result_certs = CERT_NewCertList(); |
+ CERTCertDBHandle* db_handle = CERT_GetDefaultCertDB(); |
+ CERTCertificate* user_cert = CERT_NewTempCertificate( |
+ db_handle, &der_cert, NULL, PR_FALSE, PR_TRUE); |
+ CERT_AddCertToListTail(*result_certs, user_cert); |
+ |
+ // Add the intermediates. |
+ X509Certificate::OSCertHandles intermediates = |
+ that->ssl_config_.client_cert->GetIntermediateCertificates(); |
+ for (X509Certificate::OSCertHandles::const_iterator it = |
+ intermediates.begin(); it != intermediates.end(); ++it) { |
+ der_cert.data = (*it)->pbCertEncoded; |
+ der_cert.len = (*it)->cbCertEncoded; |
+ |
+ CERTCertificate* intermediate = CERT_NewTempCertificate( |
+ db_handle, &der_cert, NULL, PR_FALSE, PR_TRUE); |
+ CERT_AddCertToListTail(*result_certs, intermediate); |
+ } |
+ // TODO(wtc): |key_spec| should be passed along with |provider|. |
+ *result_private_key = reinterpret_cast<void*>(provider); |
+ return SECSuccess; |
+ } |
+ LOG(WARNING) << "Client cert found without private key"; |
+ } |
// Send no client certificate. |
return SECFailure; |
} |
@@ -1733,11 +1785,34 @@ SECStatus SSLClientSocketNSS::ClientAuthHandler( |
NOTREACHED(); |
continue; |
} |
+ |
+ // Copy the rest of the chain to our own store as well. Copying the chain |
+ // stops gracefully if an error is encountered, with the partial chain |
+ // being used as the intermediates, rather than failing to consider the |
+ // client certificate. |
+ net::X509Certificate::OSCertHandles intermediates; |
+ for (DWORD i = 1; i < chain_context->rgpChain[0]->cElement; i++) { |
+ PCCERT_CONTEXT intermediate_copy; |
+ ok = CertAddCertificateContextToStore(X509Certificate::cert_store(), |
+ chain_context->rgpChain[0]->rgpElement[i]->pCertContext, |
+ CERT_STORE_ADD_USE_EXISTING, &intermediate_copy); |
+ if (!ok) { |
+ NOTREACHED(); |
+ break; |
+ } |
+ intermediates.push_back(intermediate_copy); |
+ } |
+ |
scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromHandle( |
cert_context2, X509Certificate::SOURCE_LONE_CERT_IMPORT, |
- X509Certificate::OSCertHandles()); |
- X509Certificate::FreeOSCertHandle(cert_context2); |
+ intermediates); |
that->client_certs_.push_back(cert); |
+ |
+ X509Certificate::FreeOSCertHandle(cert_context2); |
+ for (net::X509Certificate::OSCertHandles::iterator it = |
+ intermediates.begin(); it != intermediates.end(); ++it) { |
+ net::X509Certificate::FreeOSCertHandle(*it); |
+ } |
} |
BOOL ok = CertCloseStore(my_cert_store, CERT_CLOSE_STORE_CHECK_FLAG); |
@@ -1748,9 +1823,63 @@ SECStatus SSLClientSocketNSS::ClientAuthHandler( |
return SECWouldBlock; |
#elif defined(OS_MACOSX) |
if (that->ssl_config_.send_client_cert) { |
- // TODO(wtc): SSLClientSocketNSS can't do SSL client authentication using |
- // CDSA/CSSM yet (http://crbug.com/45369), so client_cert must be NULL. |
- DCHECK(!that->ssl_config_.client_cert); |
+ if (that->ssl_config_.client_cert) { |
+ OSStatus os_error = noErr; |
+ SecIdentityRef identity = NULL; |
+ SecKeyRef private_key = NULL; |
+ CFArrayRef chain = |
+ that->ssl_config_.client_cert->CreateClientCertificateChain(); |
+ if (chain) { |
+ identity = reinterpret_cast<SecIdentityRef>( |
+ const_cast<void*>(CFArrayGetValueAtIndex(chain, 0))); |
+ } |
+ if (identity) |
+ os_error = SecIdentityCopyPrivateKey(identity, &private_key); |
+ |
+ if (chain && identity && os_error == noErr) { |
+ // TODO(rsleevi): Error checking for NSS allocation errors. |
+ *result_certs = CERT_NewCertList(); |
+ *result_private_key = reinterpret_cast<void*>(private_key); |
+ |
+ for (CFIndex i = 0; i < CFArrayGetCount(chain); ++i) { |
+ CSSM_DATA cert_data; |
+ SecCertificateRef cert_ref; |
+ if (i == 0) { |
+ cert_ref = that->ssl_config_.client_cert->os_cert_handle(); |
+ } else { |
+ cert_ref = reinterpret_cast<SecCertificateRef>( |
+ const_cast<void*>(CFArrayGetValueAtIndex(chain, i))); |
+ } |
+ os_error = SecCertificateGetData(cert_ref, &cert_data); |
+ if (os_error != noErr) |
+ break; |
+ |
+ SECItem der_cert; |
+ der_cert.type = siDERCertBuffer; |
+ der_cert.data = cert_data.Data; |
+ der_cert.len = cert_data.Length; |
+ CERTCertificate* nss_cert = CERT_NewTempCertificate( |
+ CERT_GetDefaultCertDB(), &der_cert, NULL, PR_FALSE, PR_TRUE); |
+ CERT_AddCertToListTail(*result_certs, nss_cert); |
+ } |
+ } |
+ if (os_error == noErr) { |
+ CFRelease(chain); |
+ return SECSuccess; |
+ } |
+ LOG(WARNING) << "Client cert found, but could not be used: " |
+ << os_error; |
+ if (*result_certs) { |
+ CERT_DestroyCertList(*result_certs); |
+ *result_certs = NULL; |
+ } |
+ if (*result_private_key) |
+ *result_private_key = NULL; |
+ if (private_key) |
+ CFRelease(private_key); |
+ if (chain) |
+ CFRelease(chain); |
+ } |
// Send no client certificate. |
return SECFailure; |
} |
@@ -1778,6 +1907,24 @@ SECStatus SSLClientSocketNSS::ClientAuthHandler( |
// handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED. |
return SECWouldBlock; |
#else |
+ return SECFailure; |
+#endif |
+} |
+ |
+#else // NSS_PLATFORM_CLIENT_AUTH |
+ |
+// static |
+// NSS calls this if a client certificate is needed. |
+// Based on Mozilla's NSS_GetClientAuthData. |
+SECStatus SSLClientSocketNSS::ClientAuthHandler( |
+ void* arg, |
+ PRFileDesc* socket, |
+ CERTDistNames* ca_names, |
+ CERTCertificate** result_certificate, |
+ SECKEYPrivateKey** result_private_key) { |
+ SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); |
+ |
+ that->client_auth_cert_needed_ = !that->ssl_config_.send_client_cert; |
void* wincx = SSL_RevealPinArg(socket); |
// Second pass: a client certificate should have been selected. |
@@ -1831,8 +1978,8 @@ SECStatus SSLClientSocketNSS::ClientAuthHandler( |
// Tell NSS to suspend the client authentication. We will then abort the |
// handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED. |
return SECWouldBlock; |
-#endif |
} |
+#endif // NSS_PLATFORM_CLIENT_AUTH |
// static |
// NSS calls this when handshake is completed. |