mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1040130 - Allow specifying a client cert for sockets. r=keeler, r=mcmanus
This commit is contained in:
parent
2a94f956c6
commit
951ce56ee2
@ -7,6 +7,7 @@
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIInterfaceRequestor;
|
||||
interface nsIX509Cert;
|
||||
|
||||
%{C++
|
||||
template<class T> class nsTArray;
|
||||
@ -14,7 +15,7 @@ class nsCString;
|
||||
%}
|
||||
[ref] native nsCStringTArrayRef(nsTArray<nsCString>);
|
||||
|
||||
[scriptable, builtinclass, uuid(2032ad83-229f-4ddb-818a-59b9ae4ecd4b)]
|
||||
[scriptable, builtinclass, uuid(7836a872-e50c-4e43-8224-fd08a8d09699)]
|
||||
interface nsISSLSocketControl : nsISupports {
|
||||
attribute nsIInterfaceRequestor notificationCallbacks;
|
||||
|
||||
@ -94,5 +95,12 @@ interface nsISSLSocketControl : nsISupports {
|
||||
const short SSL_MAC_AEAD = 6;
|
||||
|
||||
[infallible] readonly attribute short MACAlgorithmUsed;
|
||||
|
||||
/**
|
||||
* If set before the server requests a client cert (assuming it does so at
|
||||
* all), then this cert will be presented to the server, instead of asking
|
||||
* the user or searching the set of rememebered user cert decisions.
|
||||
*/
|
||||
attribute nsIX509Cert clientCert;
|
||||
};
|
||||
|
||||
|
@ -140,7 +140,8 @@ nsNSSSocketInfo::nsNSSSocketInfo(SharedSSLState& aState, uint32_t providerFlags)
|
||||
mMACAlgorithmUsed(nsISSLSocketControl::SSL_MAC_UNKNOWN),
|
||||
mProviderFlags(providerFlags),
|
||||
mSocketCreationTimestamp(TimeStamp::Now()),
|
||||
mPlaintextBytesRead(0)
|
||||
mPlaintextBytesRead(0),
|
||||
mClientCert(nullptr)
|
||||
{
|
||||
mTLSVersionRange.min = 0;
|
||||
mTLSVersionRange.max = 0;
|
||||
@ -203,6 +204,22 @@ nsNSSSocketInfo::GetMACAlgorithmUsed(int16_t* aMac)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNSSSocketInfo::GetClientCert(nsIX509Cert** aClientCert)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aClientCert);
|
||||
*aClientCert = mClientCert;
|
||||
NS_IF_ADDREF(*aClientCert);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNSSSocketInfo::SetClientCert(nsIX509Cert* aClientCert)
|
||||
{
|
||||
mClientCert = aClientCert;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNSSSocketInfo::GetRememberClientAuthCertificate(bool* aRemember)
|
||||
{
|
||||
@ -1908,6 +1925,29 @@ ClientAuthDataRunnable::RunOnTargetThread()
|
||||
void* wincx = mSocketInfo;
|
||||
nsresult rv;
|
||||
|
||||
nsCOMPtr<nsIX509Cert> socketClientCert;
|
||||
mSocketInfo->GetClientCert(getter_AddRefs(socketClientCert));
|
||||
|
||||
// If a client cert preference was set on the socket info, use that and skip
|
||||
// the client cert UI and/or search of the user's past cert decisions.
|
||||
if (socketClientCert) {
|
||||
cert = socketClientCert->GetCert();
|
||||
if (!cert) {
|
||||
goto loser;
|
||||
}
|
||||
|
||||
// Get the private key
|
||||
privKey = PK11_FindKeyByAnyCert(cert.get(), wincx);
|
||||
if (!privKey) {
|
||||
goto loser;
|
||||
}
|
||||
|
||||
*mPRetCert = cert.forget();
|
||||
*mPRetKey = privKey.forget();
|
||||
mRV = SECSuccess;
|
||||
return;
|
||||
}
|
||||
|
||||
// create caNameStrings
|
||||
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
||||
if (!arena) {
|
||||
|
@ -151,6 +151,8 @@ private:
|
||||
uint32_t mProviderFlags;
|
||||
mozilla::TimeStamp mSocketCreationTimestamp;
|
||||
uint64_t mPlaintextBytesRead;
|
||||
|
||||
nsCOMPtr<nsIX509Cert> mClientCert;
|
||||
};
|
||||
|
||||
class nsSSLIOLayerHelpers
|
||||
|
@ -57,6 +57,7 @@ const SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED = SEC_ERROR_BASE + 176;
|
||||
const SEC_ERROR_APPLICATION_CALLBACK_ERROR = SEC_ERROR_BASE + 178;
|
||||
|
||||
const SSL_ERROR_BAD_CERT_DOMAIN = SSL_ERROR_BASE + 12;
|
||||
const SSL_ERROR_BAD_CERT_ALERT = SSL_ERROR_BASE + 17;
|
||||
|
||||
const MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE = MOZILLA_PKIX_ERROR_BASE + 0;
|
||||
|
||||
@ -202,7 +203,8 @@ function run_test() {
|
||||
add_connection_test("<test-name-1>.example.com",
|
||||
getXPCOMStatusFromNSS(SEC_ERROR_xxx),
|
||||
function() { ... },
|
||||
function(aTransportSecurityInfo) { ... });
|
||||
function(aTransportSecurityInfo) { ... },
|
||||
function(aTransport) { ... });
|
||||
[...]
|
||||
add_connection_test("<test-name-n>.example.com", Cr.NS_OK);
|
||||
|
||||
@ -223,8 +225,11 @@ function add_tls_server_setup(serverBinName) {
|
||||
// called before the connection is attempted.
|
||||
// aWithSecurityInfo is a callback function that takes an
|
||||
// nsITransportSecurityInfo, which is called after the TLS handshake succeeds.
|
||||
// aAfterStreamOpen is a callback function that is called with the
|
||||
// nsISocketTransport once the output stream is ready.
|
||||
function add_connection_test(aHost, aExpectedResult,
|
||||
aBeforeConnect, aWithSecurityInfo) {
|
||||
aBeforeConnect, aWithSecurityInfo,
|
||||
aAfterStreamOpen) {
|
||||
const REMOTE_PORT = 8443;
|
||||
|
||||
function Connection(aHost) {
|
||||
@ -268,6 +273,9 @@ function add_connection_test(aHost, aExpectedResult,
|
||||
|
||||
// nsIOutputStreamCallback
|
||||
onOutputStreamReady: function(aStream) {
|
||||
if (aAfterStreamOpen) {
|
||||
aAfterStreamOpen(this.transport);
|
||||
}
|
||||
let sslSocketControl = this.transport.securityInfo
|
||||
.QueryInterface(Ci.nsISSLSocketControl);
|
||||
sslSocketControl.proxyStartSSL();
|
||||
|
68
security/manager/ssl/tests/unit/test_client_cert.js
Normal file
68
security/manager/ssl/tests/unit/test_client_cert.js
Normal file
@ -0,0 +1,68 @@
|
||||
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
"use strict";
|
||||
|
||||
// Tests specifying a particular client cert to use via the nsISSLSocketControl
|
||||
// |clientCert| attribute prior to connecting to the server.
|
||||
|
||||
function run_test() {
|
||||
do_get_profile();
|
||||
|
||||
// Init key token (to prevent password prompt)
|
||||
const tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"]
|
||||
.getService(Ci.nsIPK11TokenDB);
|
||||
let keyToken = tokenDB.getInternalKeyToken();
|
||||
if (keyToken.needsUserInit) {
|
||||
keyToken.initPassword("");
|
||||
}
|
||||
|
||||
// Replace the UI dialog that would prompt for the following PKCS #12 file's
|
||||
// password, as well as an alert that appears after it succeeds.
|
||||
do_load_manifest("test_client_cert/cert_dialog.manifest");
|
||||
|
||||
// Load the user cert and look it up in XPCOM format
|
||||
const certDB = Cc["@mozilla.org/security/x509certdb;1"]
|
||||
.getService(Ci.nsIX509CertDB);
|
||||
let clientCertFile = do_get_file("test_client_cert/client-cert.p12", false);
|
||||
certDB.importPKCS12File(null, clientCertFile);
|
||||
|
||||
// Find the cert by its common name
|
||||
let clientCert;
|
||||
let certs = certDB.getCerts().getEnumerator();
|
||||
while (certs.hasMoreElements()) {
|
||||
let cert = certs.getNext().QueryInterface(Ci.nsIX509Cert);
|
||||
if (cert.certType === Ci.nsIX509Cert.USER_CERT &&
|
||||
cert.commonName === "client-cert") {
|
||||
clientCert = cert;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ok(clientCert, "Client cert found");
|
||||
|
||||
add_tls_server_setup("ClientAuthServer");
|
||||
|
||||
add_connection_test("noclientauth.example.com", Cr.NS_OK);
|
||||
|
||||
add_connection_test("requestclientauth.example.com", Cr.NS_OK);
|
||||
add_connection_test("requestclientauth.example.com", Cr.NS_OK,
|
||||
null, null, transport => {
|
||||
do_print("Setting client cert on transport");
|
||||
let sslSocketControl = transport.securityInfo
|
||||
.QueryInterface(Ci.nsISSLSocketControl);
|
||||
sslSocketControl.clientCert = clientCert;
|
||||
});
|
||||
|
||||
add_connection_test("requireclientauth.example.com",
|
||||
getXPCOMStatusFromNSS(SSL_ERROR_BAD_CERT_ALERT));
|
||||
add_connection_test("requireclientauth.example.com", Cr.NS_OK,
|
||||
null, null, transport => {
|
||||
do_print("Setting client cert on transport");
|
||||
let sslSocketControl =
|
||||
transport.securityInfo.QueryInterface(Ci.nsISSLSocketControl);
|
||||
sslSocketControl.clientCert = clientCert;
|
||||
});
|
||||
|
||||
run_next_test();
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
const { utils: Cu, interfaces: Ci } = Components;
|
||||
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
|
||||
|
||||
function CertDialogService() {}
|
||||
CertDialogService.prototype = {
|
||||
classID: Components.ID("{a70153f2-3590-4317-93e9-73b3e7ffca5d}"),
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsICertificateDialogs]),
|
||||
|
||||
getPKCS12FilePassword: function() {
|
||||
return true; // Simulates entering an empty password
|
||||
}
|
||||
};
|
||||
|
||||
let Prompter = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt]),
|
||||
alert: function() {} // Do nothing when asked to show an alert
|
||||
};
|
||||
|
||||
function WindowWatcherService() {}
|
||||
WindowWatcherService.prototype = {
|
||||
classID: Components.ID("{01ae923c-81bb-45db-b860-d423b0fc4fe1}"),
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowWatcher]),
|
||||
|
||||
getNewPrompter: function() {
|
||||
return Prompter;
|
||||
}
|
||||
};
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([
|
||||
CertDialogService,
|
||||
WindowWatcherService
|
||||
]);
|
@ -0,0 +1,4 @@
|
||||
component {a70153f2-3590-4317-93e9-73b3e7ffca5d} cert_dialog.js
|
||||
contract @mozilla.org/nsCertificateDialogs;1 {a70153f2-3590-4317-93e9-73b3e7ffca5d}
|
||||
component {01ae923c-81bb-45db-b860-d423b0fc4fe1} cert_dialog.js
|
||||
contract @mozilla.org/embedcomp/window-watcher;1 {01ae923c-81bb-45db-b860-d423b0fc4fe1}
|
BIN
security/manager/ssl/tests/unit/test_client_cert/client-cert.p12
Normal file
BIN
security/manager/ssl/tests/unit/test_client_cert/client-cert.p12
Normal file
Binary file not shown.
32
security/manager/ssl/tests/unit/test_client_cert/generate.py
Executable file
32
security/manager/ssl/tests/unit/test_client_cert/generate.py
Executable file
@ -0,0 +1,32 @@
|
||||
#!/usr/bin/python
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python
|
||||
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# After running this file you MUST modify ClientAuthServer.cpp to change the
|
||||
# fingerprint of the client cert
|
||||
|
||||
import tempfile, os, sys, random
|
||||
|
||||
libpath = os.path.abspath("../psm_common_py")
|
||||
sys.path.append(libpath)
|
||||
|
||||
import CertUtils
|
||||
|
||||
dest_dir = os.getcwd()
|
||||
db = tempfile.mkdtemp()
|
||||
|
||||
serial = random.randint(100, 40000000)
|
||||
name = "client-cert"
|
||||
[key, cert] = CertUtils.generate_cert_generic(db, dest_dir, serial, "rsa",
|
||||
name, "")
|
||||
CertUtils.generate_pkcs12(db, dest_dir, cert, key, name)
|
||||
|
||||
# Remove unnecessary .der file
|
||||
os.remove(dest_dir + "/" + name + ".der")
|
||||
|
||||
print ("You now MUST modify ClientAuthServer.cpp to ensure the xpchell debug " +
|
||||
"certificate there matches this newly generated one\n")
|
@ -0,0 +1,116 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 sw=2 tw=80 et: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// This is a standalone server for testing client cert authentication.
|
||||
// The client is expected to connect, initiate an SSL handshake (with SNI
|
||||
// to indicate which "server" to connect to), and verify the certificate.
|
||||
// If all is good, the client then sends one encrypted byte and receives that
|
||||
// same byte back.
|
||||
// This server also has the ability to "call back" another process waiting on
|
||||
// it. That is, when the server is all set up and ready to receive connections,
|
||||
// it will connect to a specified port and issue a simple HTTP request.
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "hasht.h"
|
||||
#include "ScopedNSSTypes.h"
|
||||
#include "ssl.h"
|
||||
#include "TLSServer.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::test;
|
||||
|
||||
struct ClientAuthHost
|
||||
{
|
||||
const char *mHostName;
|
||||
bool mRequestClientAuth;
|
||||
bool mRequireClientAuth;
|
||||
};
|
||||
|
||||
// Hostname, cert nickname pairs.
|
||||
static const ClientAuthHost sClientAuthHosts[] =
|
||||
{
|
||||
{ "noclientauth.example.com", false, false },
|
||||
{ "requestclientauth.example.com", true, false },
|
||||
{ "requireclientauth.example.com", true, true },
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
|
||||
static const unsigned char sClientCertFingerprint[] =
|
||||
{
|
||||
0xD2, 0x2F, 0x00, 0x9A, 0x9E, 0xED, 0x79, 0xDC,
|
||||
0x8D, 0x17, 0x98, 0x8E, 0xEC, 0x76, 0x05, 0x91,
|
||||
0xA5, 0xF6, 0xC9, 0xFA, 0x16, 0x8B, 0xD2, 0x5F,
|
||||
0xE1, 0x52, 0x04, 0x7C, 0xF4, 0x76, 0x42, 0x9D
|
||||
};
|
||||
|
||||
SECStatus
|
||||
AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checkSig,
|
||||
PRBool isServer)
|
||||
{
|
||||
ScopedCERTCertificate clientCert(SSL_PeerCertificate(fd));
|
||||
|
||||
unsigned char certFingerprint[SHA256_LENGTH];
|
||||
SECStatus rv = PK11_HashBuf(SEC_OID_SHA256, certFingerprint,
|
||||
clientCert->derCert.data,
|
||||
clientCert->derCert.len);
|
||||
if (rv != SECSuccess) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
static_assert(sizeof(sClientCertFingerprint) == SHA256_LENGTH,
|
||||
"Ensure fingerprint has corrent length");
|
||||
bool match = !memcmp(certFingerprint, sClientCertFingerprint,
|
||||
sizeof(certFingerprint));
|
||||
return match ? SECSuccess : SECFailure;
|
||||
}
|
||||
|
||||
int32_t
|
||||
DoSNISocketConfig(PRFileDesc* aFd, const SECItem* aSrvNameArr,
|
||||
uint32_t aSrvNameArrSize, void* aArg)
|
||||
{
|
||||
const ClientAuthHost *host = GetHostForSNI(aSrvNameArr, aSrvNameArrSize,
|
||||
sClientAuthHosts);
|
||||
if (!host) {
|
||||
return SSL_SNI_SEND_ALERT;
|
||||
}
|
||||
|
||||
if (gDebugLevel >= DEBUG_VERBOSE) {
|
||||
fprintf(stderr, "found pre-defined host '%s'\n", host->mHostName);
|
||||
}
|
||||
|
||||
SECStatus srv = ConfigSecureServerWithNamedCert(aFd, DEFAULT_CERT_NICKNAME,
|
||||
nullptr, nullptr);
|
||||
if (srv != SECSuccess) {
|
||||
return SSL_SNI_SEND_ALERT;
|
||||
}
|
||||
|
||||
SSL_OptionSet(aFd, SSL_REQUEST_CERTIFICATE, host->mRequestClientAuth);
|
||||
if (host->mRequireClientAuth) {
|
||||
SSL_OptionSet(aFd, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_ALWAYS);
|
||||
} else {
|
||||
SSL_OptionSet(aFd, SSL_REQUIRE_CERTIFICATE, SSL_REQUIRE_NEVER);
|
||||
}
|
||||
|
||||
// Override default client auth hook to just check fingerprint
|
||||
srv = SSL_AuthCertificateHook(aFd, AuthCertificateHook, nullptr);
|
||||
if (srv != SECSuccess) {
|
||||
return SSL_SNI_SEND_ALERT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char* argv[])
|
||||
{
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "usage: %s <NSS DB directory>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return StartServer(argv[1], DoSNISocketConfig, nullptr);
|
||||
}
|
@ -8,6 +8,7 @@ FAIL_ON_WARNINGS = True
|
||||
|
||||
SIMPLE_PROGRAMS = [
|
||||
'BadCertServer',
|
||||
'ClientAuthServer',
|
||||
'GenerateOCSPResponse',
|
||||
'OCSPStaplingServer',
|
||||
]
|
||||
|
@ -6,6 +6,7 @@ support-files =
|
||||
test_signed_apps/**
|
||||
tlsserver/**
|
||||
test_cert_signatures/**
|
||||
test_client_cert/**
|
||||
test_ev_certs/**
|
||||
test_getchain/**
|
||||
test_intermediate_basic_usage_constraints/**
|
||||
@ -94,3 +95,7 @@ run-sequentially = hardcoded ports
|
||||
skip-if = os == "android"
|
||||
[test_add_preexisting_cert.js]
|
||||
[test_keysize.js]
|
||||
[test_client_cert.js]
|
||||
run-sequentially = hardcoded ports
|
||||
# Bug 1009158: this test times out on Android
|
||||
skip-if = os == "android"
|
||||
|
@ -112,6 +112,7 @@ TEST_HARNESS_BINS := \
|
||||
certutil$(BIN_SUFFIX) \
|
||||
pk12util$(BIN_SUFFIX) \
|
||||
BadCertServer$(BIN_SUFFIX) \
|
||||
ClientAuthServer$(BIN_SUFFIX) \
|
||||
OCSPStaplingServer$(BIN_SUFFIX) \
|
||||
GenerateOCSPResponse$(BIN_SUFFIX) \
|
||||
fix_stack_using_bpsyms.py \
|
||||
|
@ -371,6 +371,7 @@ class XPCShellRemote(xpcshell.XPCShellTests, object):
|
||||
"certutil",
|
||||
"pk12util",
|
||||
"BadCertServer",
|
||||
"ClientAuthServer",
|
||||
"OCSPStaplingServer",
|
||||
"GenerateOCSPResponse"]
|
||||
for fname in binaries:
|
||||
|
@ -615,6 +615,7 @@ NO_PKG_FILES += \
|
||||
certutil* \
|
||||
pk12util* \
|
||||
BadCertServer* \
|
||||
ClientAuthServer* \
|
||||
OCSPStaplingServer* \
|
||||
GenerateOCSPResponse* \
|
||||
winEmbed.exe \
|
||||
|
Loading…
Reference in New Issue
Block a user