Mypal68/security/manager/ssl/tests/mochitest/browser/browser_certViewer.js
2025-04-19 19:15:10 +03:00

344 lines
12 KiB
JavaScript

/* 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";
// Repeatedly opens the certificate viewer dialog with various certificates and
// determines that the viewer correctly identifies either what usages those
// certificates are valid for or what errors prevented the certificates from
// being verified.
var { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
var { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
add_task(async function testCAandTitle() {
let cert = await readCertificate("ca.pem", "CTu,CTu,CTu");
let win = await displayCertificate(cert);
checkUsages(win, [{ id: "verify-ssl-ca" }]);
checkDetailsPane(win, ["ca"]);
// There's no real need to test the title for every cert, so we just test it
// once here.
Assert.deepEqual(
win.document.l10n.getAttributes(win.document.documentElement),
{ args: { certName: "ca" }, id: "cert-viewer-title" },
"Actual and expected title should match"
);
await BrowserTestUtils.closeWindow(win);
});
add_task(async function testSSLEndEntity() {
let cert = await readCertificate("ssl-ee.pem", ",,");
let win = await displayCertificate(cert);
checkUsages(win, [{ id: "verify-ssl-server" }, { id: "verify-ssl-client" }]);
checkDetailsPane(win, ["ca", "ssl-ee"]);
await BrowserTestUtils.closeWindow(win);
});
add_task(async function testEmailEndEntity() {
let cert = await readCertificate("email-ee.pem", ",,");
let win = await displayCertificate(cert);
checkUsages(win, [
{ id: "verify-email-recip" },
{ id: "verify-email-signer" },
]);
checkDetailsPane(win, ["ca", "email-ee"]);
await BrowserTestUtils.closeWindow(win);
});
add_task(async function testCodeSignEndEntity() {
let cert = await readCertificate("code-ee.pem", ",,");
let win = await displayCertificate(cert);
checkError(win, { id: "cert-not-verified-unknown" });
checkDetailsPane(win, ["code-ee"]);
await BrowserTestUtils.closeWindow(win);
});
add_task(async function testExpired() {
let cert = await readCertificate("expired-ca.pem", ",,");
let win = await displayCertificate(cert);
checkError(win, { id: "cert-not-verified-cert-expired" });
checkDetailsPane(win, ["expired-ca"]);
await BrowserTestUtils.closeWindow(win);
// These tasks may run in any order, so we run this additional testcase in the
// same task.
let eeCert = await readCertificate("ee-from-expired-ca.pem", ",,");
let eeWin = await displayCertificate(eeCert);
checkError(eeWin, { id: "cert-not-verified-ca-invalid" });
checkDetailsPane(eeWin, ["ee-from-expired-ca"]);
await BrowserTestUtils.closeWindow(eeWin);
});
add_task(async function testUnknownIssuer() {
let cert = await readCertificate("unknown-issuer.pem", ",,");
let win = await displayCertificate(cert);
checkError(win, { id: "cert-not-verified-issuer-unknown" });
checkDetailsPane(win, ["unknown-issuer"]);
await BrowserTestUtils.closeWindow(win);
});
add_task(async function testInsecureAlgo() {
let cert = await readCertificate("md5-ee.pem", ",,");
let win = await displayCertificate(cert);
checkError(win, { id: "cert-not-verified_algorithm-disabled" });
checkDetailsPane(win, ["md5-ee"]);
await BrowserTestUtils.closeWindow(win);
});
add_task(async function testUntrusted() {
let cert = await readCertificate("untrusted-ca.pem", "p,p,p");
let win = await displayCertificate(cert);
checkError(win, { id: "cert-not-verified-cert-not-trusted" });
checkDetailsPane(win, ["untrusted-ca"]);
await BrowserTestUtils.closeWindow(win);
// These tasks may run in any order, so we run this additional testcase in the
// same task.
let eeCert = await readCertificate("ee-from-untrusted-ca.pem", ",,");
let eeWin = await displayCertificate(eeCert);
checkError(eeWin, { id: "cert-not-verified-issuer-not-trusted" });
checkDetailsPane(eeWin, ["ee-from-untrusted-ca"]);
await BrowserTestUtils.closeWindow(eeWin);
});
add_task(async function testRevoked() {
// Note that there's currently no way to un-do this. This should only be a
// problem if another test re-uses a certificate with this same key (perhaps
// likely) and subject (less likely).
if (AppConstants.MOZ_NEW_CERT_STORAGE) {
let certBlocklist = Cc["@mozilla.org/security/certstorage;1"].getService(
Ci.nsICertStorage
);
let result = await new Promise(resolve =>
certBlocklist.setRevocations(
[
{
QueryInterface: ChromeUtils.generateQI([
Ci.nsISubjectAndPubKeyRevocationState,
]),
subject: "MBIxEDAOBgNVBAMMB3Jldm9rZWQ=", // CN=revoked
pubKey: "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=", // hash of the shared key
state: Ci.nsICertStorage.STATE_ENFORCE, // yes, we want this to be revoked
},
],
resolve
)
);
Assert.equal(result, Cr.NS_OK, "setting revocation state should succeed");
} else {
let certBlocklist = Cc["@mozilla.org/security/certblocklist;1"].getService(
Ci.nsICertBlocklist
);
certBlocklist.revokeCertBySubjectAndPubKey(
"MBIxEDAOBgNVBAMMB3Jldm9rZWQ=", // CN=revoked
"VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8="
); // hash of the shared key
}
let cert = await readCertificate("revoked.pem", ",,");
let win = await displayCertificate(cert);
// As of bug 1312827, OneCRL only applies to TLS web server certificates, so
// this certificate will actually verify successfully for every end-entity
// usage except TLS web server.
checkUsages(win, [
{ id: "verify-email-recip" },
{ id: "verify-email-signer" },
{ id: "verify-ssl-client" },
]);
checkDetailsPane(win, ["ca", "revoked"]);
await BrowserTestUtils.closeWindow(win);
});
add_task(async function testInvalid() {
// This certificate has a keyUsage extension asserting cRLSign and
// keyCertSign, but it doesn't have a basicConstraints extension. This
// shouldn't be valid for any usage. Sadly, we give a pretty lame error
// message in this case.
let cert = await readCertificate("invalid.pem", ",,");
let win = await displayCertificate(cert);
checkError(win, { id: "cert-not-verified-unknown" });
checkDetailsPane(win, ["invalid"]);
await BrowserTestUtils.closeWindow(win);
});
add_task(async function testLongOID() {
// This certificate has a certificatePolicies extension with a policy with a
// very long OID. This tests that we don't crash when looking at it.
let cert = await readCertificate("longOID.pem", ",,");
let win = await displayCertificate(cert);
checkDetailsPane(win, ["Long OID"]);
await BrowserTestUtils.closeWindow(win);
});
/**
* Given a certificate, returns a promise that will resolve when the certificate
* viewer has opened is displaying that certificate, and has finished
* determining its valid usages.
*
* @param {nsIX509Cert} certificate
* The certificate to view and determine usages for.
* @return {Promise}
* A promise that will resolve with a handle on the opened certificate
* viewer window when the usages have been determined.
*/
function displayCertificate(certificate) {
let win = window.openDialog(
"chrome://pippki/content/certViewer.xhtml",
"",
"",
certificate
);
return TestUtils.topicObserved(
"ViewCertDetails:CertUsagesDone",
(subject, data) => subject == win
).then(
([subject, data]) => subject,
error => {
throw error;
}
);
}
/**
* Given a certificate viewer window, finds the usages the certificate is valid
* for.
*
* @param {window} win
* The certificate viewer window.
* @return {Object[]}
* An array of objects including the L10n Ids of strings describing
* the usages the certificate is valid for.
*/
function getUsages(win) {
let determinedUsages = [];
let verifyInfoBox = win.document.getElementById("verify_info_box");
Array.from(verifyInfoBox.children).forEach(child => {
if (
child.getAttribute("hidden") != "true" &&
child.getAttribute("id") != "verified"
) {
determinedUsages.push(win.document.l10n.getAttributes(child));
}
});
return determinedUsages.sort(compareL10Ids);
}
/**
* Given a certificate viewer window, returns the error string describing a
* failure encountered when determining the certificate's usages. It will be
* "This certificate has been verified for the following uses:" when the
* certificate has successfully verified for at least one usage.
*
* @param {window} win
* The certificate viewer window.
* @return {Object}
* A object with L10n id of the string describing the error encountered,
* or the success message if the certificate is valid for at least one usage.
*/
function getError(win) {
let verified = win.document.getElementById("verified");
return win.document.l10n.getAttributes(verified);
}
/**
* Given a certificate viewer window and an array of l10n ids of expected usage
* descriptions, verifies that the window is actually showing that the
* certificate has validated for those usages.
*
* @param {window} win
* The certificate viewer window.
* @param {Object[]} usagesL10nIds
* An array of object with l10n ids of expected usage descriptions.
*/
function checkUsages(win, usagesL10nIds) {
Assert.deepEqual(
getError(win),
{ id: "cert-verified" },
"should have successful verification message"
);
let determinedUsages = getUsages(win);
usagesL10nIds.sort(compareL10Ids);
Assert.deepEqual(
determinedUsages.length,
usagesL10nIds.length,
"number of usages as determined by cert viewer should be equal"
);
while (usagesL10nIds.length > 0) {
Assert.deepEqual(
determinedUsages.pop(),
usagesL10nIds.pop(),
"usages as determined by cert viewer should be equal"
);
}
}
/**
* Given a certificate viewer window and l10n id of an expected error, verifies that the
* window is actually showing that error.
*
* @param {window} win
* The certificate viewer window.
* @param {Object} errorL10nId
* The object with l10n id of expected error message.
*/
function checkError(win, errorL10nId) {
let determinedUsages = getUsages(win);
Assert.equal(
determinedUsages.length,
0,
"should not have any successful usages in error case"
);
Assert.deepEqual(
getError(win),
errorL10nId,
"determined error should be the same as expected error"
);
}
/**
* Given a certificate viewer window and an expected list of certificate names,
* verifies that the certificate details pane of the viewer shows the expected
* certificates in the expected order.
*
* @param {window} win
* The certificate viewer window.
* @param {String[]} names
* An array of expected certificate names.
*/
function checkDetailsPane(win, names) {
let tree = win.document.getElementById("treesetDump");
let nodes = tree.querySelectorAll("treecell");
Assert.equal(
nodes.length,
names.length,
"details pane: should have the expected number of cert names"
);
for (let i = 0; i < names.length; i++) {
Assert.equal(
nodes[i].getAttribute("label"),
names[i],
"details pain: should have expected cert name"
);
}
}
/**
* Given two objects with l10n id, compare them by l10n id for sorting.
*
* @param {Objects} ida,idb
* The objects with l10n id.
* @param {integer}
* An integer representing true of false.
*/
function compareL10Ids(ida, idb) {
if (ida.id < idb.id) {
return -1;
} else if (ida.id > idb.id) {
return 1;
}
return 0;
}