зеркало из https://github.com/mozilla/gecko-dev.git
Bug 623917 - Add basic client authentication tests. r=keeler
This patch adds tests for the core aspects of the client authentication code, mainly to ensure the client auth process even works. MozReview-Commit-ID: DzV4BuwlrDE --HG-- extra : rebase_source : 43224d3159964f02b175e8c54491b2cabba2cb8a
This commit is contained in:
Родитель
b223cdde05
Коммит
cb172720f2
|
@ -179,6 +179,8 @@ nsNSSDialogs::ChooseCertificate(nsIInterfaceRequestor* ctx,
|
|||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// SetObjects() expects an nsIMutableArray, which is why we can't directly use
|
||||
// |certList| and have to add an extra layer of indirection.
|
||||
nsCOMPtr<nsIMutableArray> paramBlockArray = nsArrayBase::Create();
|
||||
if (!paramBlockArray) {
|
||||
return NS_ERROR_FAILURE;
|
||||
|
|
|
@ -6,3 +6,5 @@ support-files = head.js
|
|||
[browser_certificateManagerLeak.js]
|
||||
[browser_certViewer.js]
|
||||
support-files = *.pem
|
||||
[browser_clientAuth_connection.js]
|
||||
[browser_clientAuth_ui.js]
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
// Any copyright is dedicated to the Public Domain.
|
||||
// http://creativecommons.org/publicdomain/zero/1.0/
|
||||
"use strict";
|
||||
|
||||
// Tests various scenarios connecting to a server that requires client cert
|
||||
// authentication. Also tests that nsIClientAuthDialogs.chooseCertificate
|
||||
// is called at the appropriate times and with the correct arguments.
|
||||
|
||||
const { MockRegistrar } =
|
||||
Cu.import("resource://testing-common/MockRegistrar.jsm", {});
|
||||
|
||||
const DialogState = {
|
||||
// Assert that chooseCertificate() is never called.
|
||||
ASSERT_NOT_CALLED: "ASSERT_NOT_CALLED",
|
||||
// Return that the user selected the first given cert.
|
||||
RETURN_CERT_SELECTED: "RETURN_CERT_SELECTED",
|
||||
// Return that the user canceled.
|
||||
RETURN_CERT_NOT_SELECTED: "RETURN_CERT_NOT_SELECTED",
|
||||
};
|
||||
|
||||
let sdr = Cc["@mozilla.org/security/sdr;1"].getService(Ci.nsISecretDecoderRing);
|
||||
|
||||
// Mock implementation of nsIClientAuthDialogs.
|
||||
const gClientAuthDialogs = {
|
||||
_state: DialogState.ASSERT_NOT_CALLED,
|
||||
|
||||
set state(newState) {
|
||||
info(`old state: ${this._state}`);
|
||||
this._state = newState;
|
||||
info(`new state: ${this._state}`);
|
||||
},
|
||||
|
||||
get state() {
|
||||
return this._state;
|
||||
},
|
||||
|
||||
chooseCertificate(ctx, hostname, port, organization, issuerOrg, certList,
|
||||
selectedIndex) {
|
||||
Assert.notEqual(this.state, DialogState.ASSERT_NOT_CALLED,
|
||||
"chooseCertificate() should be called only when expected");
|
||||
|
||||
let caud = ctx.QueryInterface(Ci.nsIClientAuthUserDecision);
|
||||
Assert.notEqual(caud, null,
|
||||
"nsIClientAuthUserDecision should be queryable from the " +
|
||||
"given context");
|
||||
caud.rememberClientAuthCertificate = false;
|
||||
|
||||
Assert.equal(hostname, "requireclientcert.example.com",
|
||||
"Hostname should be 'requireclientcert.example.com'");
|
||||
Assert.equal(port, 443, "Port should be 443");
|
||||
Assert.equal(organization, "",
|
||||
"Server cert Organization should be empty/not present");
|
||||
Assert.equal(issuerOrg, "Mozilla Testing",
|
||||
"Server cert issuer Organization should be 'Mozilla Testing'");
|
||||
|
||||
// For mochitests, only the cert at build/pgo/certs/mochitest.client should
|
||||
// be selectable, so we do some brief checks to confirm this.
|
||||
Assert.notEqual(certList, null, "Cert list should not be null");
|
||||
Assert.equal(certList.length, 1, "Only 1 certificate should be available");
|
||||
let cert = certList.queryElementAt(0, Ci.nsIX509Cert);
|
||||
Assert.notEqual(cert, null, "Cert list should contain an nsIX509Cert");
|
||||
Assert.equal(cert.commonName, "Mochitest client",
|
||||
"Cert CN should be 'Mochitest client'");
|
||||
|
||||
if (this.state == DialogState.RETURN_CERT_SELECTED) {
|
||||
selectedIndex.value = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIClientAuthDialogs])
|
||||
};
|
||||
|
||||
add_task(function* setup() {
|
||||
let clientAuthDialogsCID =
|
||||
MockRegistrar.register("@mozilla.org/nsClientAuthDialogs;1",
|
||||
gClientAuthDialogs);
|
||||
registerCleanupFunction(() => {
|
||||
MockRegistrar.unregister(clientAuthDialogsCID);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Test helper for the tests below.
|
||||
*
|
||||
* @param {String} prefValue
|
||||
* Value to set the "security.default_personal_cert" pref to.
|
||||
* @param {String} expectedURL
|
||||
* If the connection is expected to load successfully, the URL that
|
||||
* should load. If the connection is expected to fail and result in an
|
||||
* error page, |undefined|.
|
||||
*/
|
||||
function* testHelper(prefValue, expectedURL) {
|
||||
yield SpecialPowers.pushPrefEnv({"set": [
|
||||
["security.default_personal_cert", prefValue],
|
||||
]});
|
||||
|
||||
yield BrowserTestUtils.loadURI(gBrowser.selectedBrowser,
|
||||
"https://requireclientcert.example.com:443");
|
||||
|
||||
// |loadedURL| will be a string URL if browserLoaded() wins the race, or
|
||||
// |undefined| if waitForErrorPage() wins the race.
|
||||
let loadedURL = yield Promise.race([
|
||||
BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser),
|
||||
BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser),
|
||||
]);
|
||||
Assert.equal(expectedURL, loadedURL, "Expected and actual URLs should match");
|
||||
|
||||
// Ensure previously successful connections don't influence future tests.
|
||||
sdr.logoutAndTeardown();
|
||||
}
|
||||
|
||||
// Test that if a certificate is chosen automatically the connection succeeds,
|
||||
// and that nsIClientAuthDialogs.chooseCertificate() is never called.
|
||||
add_task(function* testCertChosenAutomatically() {
|
||||
gClientAuthDialogs.state = DialogState.ASSERT_NOT_CALLED;
|
||||
yield* testHelper("Select Automatically",
|
||||
"https://requireclientcert.example.com/");
|
||||
});
|
||||
|
||||
// Test that if the user doesn't choose a certificate, the connection fails and
|
||||
// an error page is displayed.
|
||||
add_task(function* testCertNotChosenByUser() {
|
||||
gClientAuthDialogs.state = DialogState.RETURN_CERT_NOT_SELECTED;
|
||||
yield* testHelper("Ask Every Time", undefined);
|
||||
});
|
||||
|
||||
// Test that if the user chooses a certificate the connection suceeeds.
|
||||
add_task(function* testCertChosenByUser() {
|
||||
gClientAuthDialogs.state = DialogState.RETURN_CERT_SELECTED;
|
||||
yield* testHelper("Ask Every Time",
|
||||
"https://requireclientcert.example.com/");
|
||||
});
|
|
@ -0,0 +1,145 @@
|
|||
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
// Any copyright is dedicated to the Public Domain.
|
||||
// http://creativecommons.org/publicdomain/zero/1.0/
|
||||
"use strict";
|
||||
|
||||
// Tests that the client authentication certificate chooser correctly displays
|
||||
// provided information and correctly returns user input.
|
||||
|
||||
const TEST_HOSTNAME = "Test Hostname";
|
||||
const TEST_ORG = "Test Org";
|
||||
const TEST_ISSUER_ORG = "Test Issuer Org";
|
||||
const TEST_PORT = 123;
|
||||
|
||||
var certDB = Cc["@mozilla.org/security/x509certdb;1"]
|
||||
.getService(Ci.nsIX509CertDB);
|
||||
/**
|
||||
* Test certificate (i.e. build/pgo/certs/mochitest.client).
|
||||
* @type nsIX509Cert
|
||||
*/
|
||||
var cert;
|
||||
|
||||
/**
|
||||
* Opens the client auth cert chooser dialog.
|
||||
*
|
||||
* @param {nsIX509Cert} cert The cert to pass to the dialog for display.
|
||||
* @returns {Promise}
|
||||
* A promise that resolves when the dialog has finished loading, with
|
||||
* an array consisting of:
|
||||
* 1. The window of the opened dialog.
|
||||
* 2. The nsIDialogParamBlock passed to the dialog.
|
||||
*/
|
||||
function openClientAuthDialog(cert) {
|
||||
let params = Cc["@mozilla.org/embedcomp/dialogparam;1"]
|
||||
.createInstance(Ci.nsIDialogParamBlock);
|
||||
|
||||
let certList = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
|
||||
certList.appendElement(cert, false);
|
||||
let array = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
|
||||
array.appendElement(certList, false);
|
||||
params.objects = array;
|
||||
|
||||
params.SetString(0, TEST_HOSTNAME);
|
||||
params.SetString(1, TEST_ORG);
|
||||
params.SetString(2, TEST_ISSUER_ORG);
|
||||
params.SetInt(0, TEST_PORT);
|
||||
|
||||
let win = window.openDialog("chrome://pippki/content/clientauthask.xul", "",
|
||||
"", params);
|
||||
return new Promise((resolve, reject) => {
|
||||
win.addEventListener("load", function onLoad() {
|
||||
win.removeEventListener("load", onLoad);
|
||||
resolve([win, params]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the contents of the given cert chooser dialog match the details
|
||||
* of build/pgo/certs/mochitest.client.
|
||||
*
|
||||
* @param {window} win The cert chooser window.
|
||||
* @param {String} notBefore
|
||||
* The notBeforeLocalTime attribute of mochitest.client.
|
||||
* @param {String} notAfter
|
||||
* The notAfterLocalTime attribute of mochitest.client.
|
||||
*/
|
||||
function checkDialogContents(win, notBefore, notAfter) {
|
||||
Assert.equal(win.document.getElementById("hostname").textContent,
|
||||
`${TEST_HOSTNAME}:${TEST_PORT}`,
|
||||
"Actual and expected hostname and port should be equal");
|
||||
// “ and ” don't seem to work when embedded in the following literals, which
|
||||
// is why escape codes are used instead.
|
||||
Assert.equal(win.document.getElementById("organization").textContent,
|
||||
`Organization: \u201C${TEST_ORG}\u201D`,
|
||||
"Actual and expected organization should be equal");
|
||||
Assert.equal(win.document.getElementById("issuer").textContent,
|
||||
`Issued Under: \u201C${TEST_ISSUER_ORG}\u201D`,
|
||||
"Actual and expected issuer organization should be equal");
|
||||
|
||||
Assert.equal(win.document.getElementById("nicknames").label,
|
||||
"test client certificate [03]",
|
||||
"Actual and expected selected cert nickname and serial should " +
|
||||
"be equal");
|
||||
|
||||
let [subject, serialNum, validity, issuer, tokenName] =
|
||||
win.document.getElementById("details").value.split("\n");
|
||||
Assert.equal(subject, "Issued to: CN=Mochitest client",
|
||||
"Actual and expected subject should be equal");
|
||||
Assert.equal(serialNum, "Serial number: 03",
|
||||
"Actual and expected serial number should be equal");
|
||||
Assert.equal(validity, `Valid from ${notBefore} to ${notAfter}`,
|
||||
"Actual and expected validity should be equal");
|
||||
Assert.equal(issuer,
|
||||
"Issued by: CN=Temporary Certificate Authority,O=Mozilla " +
|
||||
"Testing,OU=Profile Guided Optimization",
|
||||
"Actual and expected issuer should be equal");
|
||||
Assert.equal(tokenName, "Stored on: Software Security Device",
|
||||
"Actual and expected token name should be equal");
|
||||
}
|
||||
|
||||
add_task(function* setup() {
|
||||
cert = certDB.findCertByNickname("test client certificate");
|
||||
Assert.notEqual(cert, null, "Should be able to find the test client cert");
|
||||
});
|
||||
|
||||
// Test that the contents of the dialog correspond to the details of the
|
||||
// provided cert.
|
||||
add_task(function* testContents() {
|
||||
let [win, params] = yield openClientAuthDialog(cert);
|
||||
checkDialogContents(win, cert.validity.notBeforeLocalTime,
|
||||
cert.validity.notAfterLocalTime);
|
||||
yield BrowserTestUtils.closeWindow(win);
|
||||
});
|
||||
|
||||
// Test that the right values are returned when the dialog is accepted.
|
||||
add_task(function* testAcceptDialogReturnValues() {
|
||||
let [win, params] = yield openClientAuthDialog(cert);
|
||||
win.document.getElementById("rememberBox").checked = true;
|
||||
info("Accepting dialog");
|
||||
win.document.getElementById("certAuthAsk").acceptDialog();
|
||||
yield BrowserTestUtils.windowClosed(win);
|
||||
|
||||
Assert.equal(params.GetInt(0), 1,
|
||||
"1 should be returned to signal user accepted");
|
||||
Assert.equal(params.GetInt(1), 0,
|
||||
"0 should be returned as the selected index");
|
||||
Assert.equal(params.GetInt(2), 1,
|
||||
"1 should be returned as the state of the 'Remember this " +
|
||||
"decision' checkbox");
|
||||
});
|
||||
|
||||
// Test that the right values are returned when the dialog is canceled.
|
||||
add_task(function* testCancelDialogReturnValues() {
|
||||
let [win, params] = yield openClientAuthDialog(cert);
|
||||
win.document.getElementById("rememberBox").checked = false;
|
||||
info("Canceling dialog");
|
||||
win.document.getElementById("certAuthAsk").cancelDialog();
|
||||
yield BrowserTestUtils.windowClosed(win);
|
||||
|
||||
Assert.equal(params.GetInt(0), 0,
|
||||
"0 should be returned to signal user canceled");
|
||||
Assert.equal(params.GetInt(2), 0,
|
||||
"0 should be returned as the state of the 'Remember this " +
|
||||
"decision' checkbox");
|
||||
});
|
Загрузка…
Ссылка в новой задаче