| Index: net/socket/ssl_client_socket_nss.cc
|
| ===================================================================
|
| --- net/socket/ssl_client_socket_nss.cc (revision 29069)
|
| +++ net/socket/ssl_client_socket_nss.cc (working copy)
|
| @@ -51,6 +51,7 @@
|
| #include "net/socket/ssl_client_socket_nss.h"
|
|
|
| #include <certdb.h>
|
| +#include <keyhi.h>
|
| #include <nspr.h>
|
| #include <nss.h>
|
| #include <secerr.h>
|
| @@ -69,6 +70,7 @@
|
| #include "net/base/cert_verifier.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"
|
| #include "net/ocsp/nss_ocsp.h"
|
|
|
| @@ -164,6 +166,8 @@
|
| case SEC_ERROR_UNTRUSTED_CERT:
|
| case SEC_ERROR_UNTRUSTED_ISSUER:
|
| return ERR_CERT_AUTHORITY_INVALID;
|
| + case SSL_ERROR_HANDSHAKE_FAILURE_ALERT:
|
| + return ERR_SSL_PROTOCOL_ERROR;
|
|
|
| default: {
|
| if (IS_SSL_ERROR(err)) {
|
| @@ -205,6 +209,8 @@
|
| user_write_callback_(NULL),
|
| user_read_buf_len_(0),
|
| user_write_buf_len_(0),
|
| + client_auth_ca_names_(NULL),
|
| + client_auth_cert_needed_(false),
|
| completed_handshake_(false),
|
| next_handshake_state_(STATE_NONE),
|
| nss_fd_(NULL),
|
| @@ -318,6 +324,10 @@
|
| if (rv != SECSuccess)
|
| return ERR_UNEXPECTED;
|
|
|
| + rv = SSL_GetClientAuthDataHook(nss_fd_, ClientAuthHandler, this);
|
| + if (rv != SECSuccess)
|
| + return ERR_UNEXPECTED;
|
| +
|
| rv = SSL_HandshakeCallback(nss_fd_, HandshakeCallback, this);
|
| if (rv != SECSuccess)
|
| return ERR_UNEXPECTED;
|
| @@ -373,6 +383,11 @@
|
| server_cert_verify_result_.Reset();
|
| completed_handshake_ = false;
|
| nss_bufs_ = NULL;
|
| + if (client_auth_ca_names_) {
|
| + CERT_FreeDistNames(client_auth_ca_names_);
|
| + client_auth_ca_names_ = NULL;
|
| + }
|
| + client_auth_cert_needed_ = false;
|
|
|
| LeaveFunction("");
|
| }
|
| @@ -508,7 +523,41 @@
|
|
|
| void SSLClientSocketNSS::GetSSLCertRequestInfo(
|
| SSLCertRequestInfo* cert_request_info) {
|
| - // TODO(wtc): implement this.
|
| + EnterFunction("");
|
| + cert_request_info->host_and_port = hostname_;
|
| + cert_request_info->client_certs.clear();
|
| +
|
| + void* wincx = SSL_RevealPinArg(nss_fd_);
|
| +
|
| + CERTCertNicknames* names = CERT_GetCertNicknames(
|
| + CERT_GetDefaultCertDB(), SEC_CERT_NICKNAMES_USER, wincx);
|
| +
|
| + if (names) {
|
| + for (int i = 0; i < names->numnicknames; ++i) {
|
| + CERTCertificate* cert = CERT_FindUserCertByUsage(
|
| + CERT_GetDefaultCertDB(), names->nicknames[i],
|
| + certUsageSSLClient, PR_FALSE, wincx);
|
| + if (!cert)
|
| + continue;
|
| + // Only check unexpired certs.
|
| + if (CERT_CheckCertValidTimes(cert, PR_Now(), PR_TRUE) ==
|
| + secCertTimeValid &&
|
| + NSS_CmpCertChainWCANames(cert, client_auth_ca_names_) ==
|
| + SECSuccess) {
|
| + SECKEYPrivateKey* privkey = PK11_FindKeyByAnyCert(cert, wincx);
|
| + if (privkey) {
|
| + X509Certificate* x509_cert = X509Certificate::CreateFromHandle(
|
| + cert, X509Certificate::SOURCE_LONE_CERT_IMPORT);
|
| + cert_request_info->client_certs.push_back(x509_cert);
|
| + SECKEY_DestroyPrivateKey(privkey);
|
| + continue;
|
| + }
|
| + }
|
| + CERT_DestroyCertificate(cert);
|
| + }
|
| + CERT_FreeNicknames(names);
|
| + }
|
| + LeaveFunction(cert_request_info->client_certs.size());
|
| }
|
|
|
| void SSLClientSocketNSS::DoReadCallback(int rv) {
|
| @@ -821,6 +870,55 @@
|
| }
|
|
|
| // 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;
|
| +
|
| + // Second pass: a client certificate should have been selected.
|
| + if (that->ssl_config_.send_client_cert) {
|
| + if (that->ssl_config_.client_cert) {
|
| + void* wincx = SSL_RevealPinArg(socket);
|
| + CERTCertificate* cert = CERT_DupCertificate(
|
| + that->ssl_config_.client_cert->os_cert_handle());
|
| + SECKEYPrivateKey* privkey = PK11_FindKeyByAnyCert(cert, wincx);
|
| + if (privkey) {
|
| + // TODO(jsorianopastor): We should wait for server certificate
|
| + // verification before sending our credentials. See
|
| + // http://crbug.com/13934.
|
| + *result_certificate = cert;
|
| + *result_private_key = privkey;
|
| + return SECSuccess;
|
| + }
|
| + LOG(WARNING) << "Client cert found without private key";
|
| + }
|
| + // Send no client certificate.
|
| + return SECFailure;
|
| + }
|
| +
|
| + PRArenaPool* arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
| + CERTDistNames* ca_names_copy = PORT_ArenaZNew(arena, CERTDistNames);
|
| +
|
| + ca_names_copy->arena = arena;
|
| + ca_names_copy->head = NULL;
|
| + ca_names_copy->nnames = ca_names->nnames;
|
| + ca_names_copy->names = PORT_ArenaZNewArray(arena, SECItem,
|
| + ca_names->nnames);
|
| + for (int i = 0; i < ca_names->nnames; ++i)
|
| + SECITEM_CopyItem(arena, &ca_names_copy->names[i], &ca_names->names[i]);
|
| +
|
| + that->client_auth_ca_names_ = ca_names_copy;
|
| + return SECFailure;
|
| +}
|
| +
|
| +// static
|
| // NSS calls this when handshake is completed.
|
| // After the SSL handshake is finished, use CertVerifier to verify
|
| // the saved server certificate.
|
| @@ -834,9 +932,19 @@
|
| int SSLClientSocketNSS::DoHandshake() {
|
| EnterFunction("");
|
| int net_error = net::OK;
|
| - int rv = SSL_ForceHandshake(nss_fd_);
|
| + SECStatus rv = SSL_ForceHandshake(nss_fd_);
|
|
|
| - if (rv == SECSuccess) {
|
| + if (client_auth_cert_needed_) {
|
| + net_error = ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
|
| + // If the handshake already succeeded (because the server requests but
|
| + // doesn't require a client cert), we need to invalidate the SSL session
|
| + // so that we won't try to resume the non-client-authenticated session in
|
| + // the next handshake. This will cause the server to ask for a client
|
| + // cert again.
|
| + if (rv == SECSuccess && SSL_InvalidateSession(nss_fd_) != SECSuccess) {
|
| + LOG(WARNING) << "Couldn't invalidate SSL session: " << PR_GetError();
|
| + }
|
| + } else if (rv == SECSuccess) {
|
| // SSL handshake is completed. Let's verify the certificate.
|
| GotoState(STATE_VERIFY_CERT);
|
| // Done!
|
| @@ -946,6 +1054,12 @@
|
| DCHECK(user_read_buf_);
|
| DCHECK(user_read_buf_len_ > 0);
|
| int rv = PR_Read(nss_fd_, user_read_buf_->data(), user_read_buf_len_);
|
| + if (client_auth_cert_needed_) {
|
| + // We don't need to invalidate the non-client-authenticated SSL session
|
| + // because the server will renegotiate anyway.
|
| + LeaveFunction("");
|
| + return ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
|
| + }
|
| if (rv >= 0) {
|
| LogData(user_read_buf_->data(), rv);
|
| LeaveFunction("");
|
|
|