/* 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 { OS } = Cu.import("resource://gre/modules/osfile.jsm", {}); add_task(function* testCAandTitle() { let cert = yield readCertificate("ca.pem", "CTu,CTu,CTu"); let win = yield displayCertificate(cert); checkUsages(win, ["SSL Certificate Authority"]); // There's no real need to test the title for every cert, so we just test it // once here. Assert.equal(win.document.title, "Certificate Viewer: \u201Cca\u201D", "Actual and expected title should match"); yield BrowserTestUtils.closeWindow(win); }); add_task(function* testSSLEndEntity() { let cert = yield readCertificate("ssl-ee.pem", ",,"); let win = yield displayCertificate(cert); checkUsages(win, ["SSL Server Certificate", "SSL Client Certificate"]); yield BrowserTestUtils.closeWindow(win); }); add_task(function* testEmailEndEntity() { let cert = yield readCertificate("email-ee.pem", ",,"); let win = yield displayCertificate(cert); checkUsages(win, ["Email Recipient Certificate", "Email Signer Certificate"]); yield BrowserTestUtils.closeWindow(win); }); add_task(function* testCodeSignEndEntity() { let cert = yield readCertificate("code-ee.pem", ",,"); let win = yield displayCertificate(cert); checkUsages(win, ["Object Signer"]); yield BrowserTestUtils.closeWindow(win); }); add_task(function* testExpired() { let cert = yield readCertificate("expired-ca.pem", ",,"); let win = yield displayCertificate(cert); checkError(win, "Could not verify this certificate because it has expired."); yield BrowserTestUtils.closeWindow(win); }); add_task(function* testIssuerExpired() { let cert = yield readCertificate("ee-from-expired-ca.pem", ",,"); let win = yield displayCertificate(cert); checkError(win, "Could not verify this certificate because the CA certificate " + "is invalid."); yield BrowserTestUtils.closeWindow(win); }); add_task(function* testUnknownIssuer() { let cert = yield readCertificate("unknown-issuer.pem", ",,"); let win = yield displayCertificate(cert); checkError(win, "Could not verify this certificate because the issuer is " + "unknown."); yield BrowserTestUtils.closeWindow(win); }); add_task(function* testInsecureAlgo() { let cert = yield readCertificate("md5-ee.pem", ",,"); let win = yield displayCertificate(cert); checkError(win, "Could not verify this certificate because it was signed using " + "a signature algorithm that was disabled because that algorithm " + "is not secure."); yield BrowserTestUtils.closeWindow(win); }); add_task(function* testUntrusted() { let cert = yield readCertificate("untrusted-ca.pem", "p,p,p"); let win = yield displayCertificate(cert); checkError(win, "Could not verify this certificate because it is not trusted."); yield BrowserTestUtils.closeWindow(win); }); add_task(function* testUntrustedIssuer() { let cert = yield readCertificate("ee-from-untrusted-ca.pem", ",,"); let win = yield displayCertificate(cert); checkError(win, "Could not verify this certificate because the issuer is not " + "trusted."); yield BrowserTestUtils.closeWindow(win); }); add_task(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). let certBlocklist = Cc["@mozilla.org/security/certblocklist;1"] .getService(Ci.nsICertBlocklist); certBlocklist.revokeCertBySubjectAndPubKey( "MBIxEDAOBgNVBAMMB3Jldm9rZWQ=", // CN=revoked "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8="); // hash of the shared key let cert = yield readCertificate("revoked.pem", ",,"); let win = yield 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, ["Email Recipient Certificate", "Email Signer Certificate", "Object Signer", "SSL Client Certificate"]); yield BrowserTestUtils.closeWindow(win); }); add_task(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 = yield readCertificate("invalid.pem", ",,"); let win = yield displayCertificate(cert); checkError(win, "Could not verify this certificate for unknown reasons."); yield 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.xul", "", "", 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 {String[]} * An array 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(child.getAttribute("value")); } }); return determinedUsages.sort(); } /** * 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 {String} * A string describing the error encountered, or the success message if * the certificate is valid for at least one usage. */ function getError(win) { return win.document.getElementById("verified").textContent; } /** * Given a certificate viewer window and an array 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 {String[]} usages * An array of expected usage descriptions. */ function checkUsages(win, usages) { Assert.equal(getError(win), "This certificate has been verified for the following uses:", "should have successful verification message"); let determinedUsages = getUsages(win); usages.sort(); Assert.equal(determinedUsages.length, usages.length, "number of usages as determined by cert viewer should be equal"); while (usages.length > 0) { Assert.equal(determinedUsages.pop(), usages.pop(), "usages as determined by cert viewer should be equal"); } } /** * Given a certificate viewer window and an expected error, verifies that the * window is actually showing that error. * * @param {window} win * The certificate viewer window. * @param {String} error * The expected error message. */ function checkError(win, error) { let determinedUsages = getUsages(win); Assert.equal(determinedUsages.length, 0, "should not have any successful usages in error case"); Assert.equal(getError(win), error, "determined error should be the same as expected error"); }