summaryrefslogtreecommitdiffstats
path: root/security/manager/pki/resources/content/certViewer.js
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/pki/resources/content/certViewer.js')
-rw-r--r--security/manager/pki/resources/content/certViewer.js354
1 files changed, 354 insertions, 0 deletions
diff --git a/security/manager/pki/resources/content/certViewer.js b/security/manager/pki/resources/content/certViewer.js
new file mode 100644
index 000000000..c62507694
--- /dev/null
+++ b/security/manager/pki/resources/content/certViewer.js
@@ -0,0 +1,354 @@
+/* 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";
+
+/**
+ * @file Implements functionality for certViewer.xul and its tabs certDump.xul
+ * and viewCertDetails.xul: a dialog that allows various attributes of a
+ * certificate to be viewed.
+ * @argument {nsISupports} window.arguments[0]
+ * The cert to view, queryable to nsIX509Cert.
+ */
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+const nsIX509Cert = Ci.nsIX509Cert;
+const nsX509CertDB = "@mozilla.org/security/x509certdb;1";
+const nsIX509CertDB = Ci.nsIX509CertDB;
+const nsPK11TokenDB = "@mozilla.org/security/pk11tokendb;1";
+const nsIPK11TokenDB = Ci.nsIPK11TokenDB;
+const nsIASN1Object = Ci.nsIASN1Object;
+const nsIASN1Sequence = Ci.nsIASN1Sequence;
+const nsIASN1PrintableItem = Ci.nsIASN1PrintableItem;
+const nsIASN1Tree = Ci.nsIASN1Tree;
+const nsASN1Tree = "@mozilla.org/security/nsASN1Tree;1";
+
+var bundle;
+
+function doPrompt(msg)
+{
+ let prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(Components.interfaces.nsIPromptService);
+ prompts.alert(window, null, msg);
+}
+
+/**
+ * Fills out the "Certificate Hierarchy" tree of the cert viewer "Details" tab.
+ *
+ * @param {tree} node
+ * Parent tree node to append to.
+ * @param {nsIArray<nsIX509Cert>} chain
+ * Chain where cert element n is issued by cert element n + 1.
+ */
+function AddCertChain(node, chain)
+{
+ var child = document.getElementById(node);
+ var currCert;
+ var displayVal;
+ for (let i = chain.length - 1; i >= 0; i--) {
+ currCert = chain.queryElementAt(i, nsIX509Cert);
+ if (currCert.commonName) {
+ displayVal = currCert.commonName;
+ } else {
+ displayVal = currCert.windowTitle;
+ }
+ let addTwistie = i != 0;
+ child = addChildrenToTree(child, displayVal, currCert.dbKey, addTwistie);
+ }
+}
+
+/**
+ * Adds a "verified usage" of a cert to the "General" tab of the cert viewer.
+ *
+ * @param {String} usage
+ * Verified usage to add.
+ */
+function AddUsage(usage)
+{
+ let verifyInfoBox = document.getElementById("verify_info_box");
+ let text = document.createElement("textbox");
+ text.setAttribute("value", usage);
+ text.setAttribute("style", "margin: 2px 5px");
+ text.setAttribute("readonly", "true");
+ text.setAttribute("class", "scrollfield");
+ verifyInfoBox.appendChild(text);
+}
+
+function setWindowName()
+{
+ bundle = document.getElementById("pippki_bundle");
+
+ let cert = window.arguments[0].QueryInterface(Ci.nsIX509Cert);
+ document.title = bundle.getFormattedString("certViewerTitle",
+ [cert.windowTitle]);
+
+ //
+ // Set the cert attributes for viewing
+ //
+
+ // The chain of trust
+ AddCertChain("treesetDump", cert.getChain());
+ DisplayGeneralDataFromCert(cert);
+ BuildPrettyPrint(cert);
+
+ asyncDetermineUsages(cert);
+}
+
+// Certificate usages we care about in the certificate viewer.
+const certificateUsageSSLClient = 0x0001;
+const certificateUsageSSLServer = 0x0002;
+const certificateUsageSSLCA = 0x0008;
+const certificateUsageEmailSigner = 0x0010;
+const certificateUsageEmailRecipient = 0x0020;
+const certificateUsageObjectSigner = 0x0040;
+
+// A map from the name of a certificate usage to the value of the usage.
+// Useful for printing debugging information and for enumerating all supported
+// usages.
+const certificateUsages = {
+ certificateUsageSSLClient,
+ certificateUsageSSLServer,
+ certificateUsageSSLCA,
+ certificateUsageEmailSigner,
+ certificateUsageEmailRecipient,
+ certificateUsageObjectSigner,
+};
+
+// Map of certificate usage name to localization identifier.
+const certificateUsageToStringBundleName = {
+ certificateUsageSSLClient: "VerifySSLClient",
+ certificateUsageSSLServer: "VerifySSLServer",
+ certificateUsageSSLCA: "VerifySSLCA",
+ certificateUsageEmailSigner: "VerifyEmailSigner",
+ certificateUsageEmailRecipient: "VerifyEmailRecip",
+ certificateUsageObjectSigner: "VerifyObjSign",
+};
+
+const PRErrorCodeSuccess = 0;
+
+const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
+const SEC_ERROR_EXPIRED_CERTIFICATE = SEC_ERROR_BASE + 11;
+const SEC_ERROR_REVOKED_CERTIFICATE = SEC_ERROR_BASE + 12;
+const SEC_ERROR_UNKNOWN_ISSUER = SEC_ERROR_BASE + 13;
+const SEC_ERROR_UNTRUSTED_ISSUER = SEC_ERROR_BASE + 20;
+const SEC_ERROR_UNTRUSTED_CERT = SEC_ERROR_BASE + 21;
+const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE = SEC_ERROR_BASE + 30;
+const SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED = SEC_ERROR_BASE + 176;
+
+/**
+ * Kicks off asynchronous verifications of the given certificate to determine
+ * what usages it is currently valid for. Updates the usage display area when
+ * complete.
+ *
+ * @param {nsIX509Cert} cert
+ * The certificate to determine valid usages for.
+ */
+function asyncDetermineUsages(cert) {
+ let promises = [];
+ let now = Date.now() / 1000;
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"]
+ .getService(Ci.nsIX509CertDB);
+ Object.keys(certificateUsages).forEach(usageString => {
+ promises.push(new Promise((resolve, reject) => {
+ let usage = certificateUsages[usageString];
+ certdb.asyncVerifyCertAtTime(cert, usage, 0, null, now,
+ (aPRErrorCode, aVerifiedChain, aHasEVPolicy) => {
+ resolve({ usageString: usageString, errorCode: aPRErrorCode });
+ });
+ }));
+ });
+ Promise.all(promises).then(displayUsages);
+}
+
+/**
+ * Updates the usage display area given the results from asyncDetermineUsages.
+ *
+ * @param {Array} results
+ * An array of objects with the properties "usageString" and "errorCode".
+ * usageString is a string that is a key in the certificateUsages map.
+ * errorCode is either an NSPR error code or PRErrorCodeSuccess (which is
+ * a pseudo-NSPR error code with the value 0 that indicates success).
+ */
+function displayUsages(results) {
+ document.getElementById("verify_pending").setAttribute("hidden", "true");
+ let verified = document.getElementById("verified");
+ let someSuccess = results.some(result =>
+ result.errorCode == PRErrorCodeSuccess
+ );
+ if (someSuccess) {
+ let verifystr = bundle.getString("certVerified");
+ verified.textContent = verifystr;
+ let pipnssBundle = Services.strings.createBundle(
+ "chrome://pipnss/locale/pipnss.properties");
+ results.forEach(result => {
+ if (result.errorCode != PRErrorCodeSuccess) {
+ return;
+ }
+ let bundleName = certificateUsageToStringBundleName[result.usageString];
+ let usage = pipnssBundle.GetStringFromName(bundleName);
+ AddUsage(usage);
+ });
+ } else {
+ const errorRankings = [
+ { error: SEC_ERROR_REVOKED_CERTIFICATE,
+ bundleString: "certNotVerified_CertRevoked" },
+ { error: SEC_ERROR_UNTRUSTED_CERT,
+ bundleString: "certNotVerified_CertNotTrusted" },
+ { error: SEC_ERROR_UNTRUSTED_ISSUER,
+ bundleString: "certNotVerified_IssuerNotTrusted" },
+ { error: SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED,
+ bundleString: "certNotVerified_AlgorithmDisabled" },
+ { error: SEC_ERROR_EXPIRED_CERTIFICATE,
+ bundleString: "certNotVerified_CertExpired" },
+ { error: SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE,
+ bundleString: "certNotVerified_CAInvalid" },
+ { error: SEC_ERROR_UNKNOWN_ISSUER,
+ bundleString: "certNotVerified_IssuerUnknown" },
+ ];
+ let verifystr;
+ for (let errorRanking of errorRankings) {
+ let errorPresent = results.some(result =>
+ result.errorCode == errorRanking.error
+ );
+ if (errorPresent) {
+ verifystr = bundle.getString(errorRanking.bundleString);
+ break;
+ }
+ }
+ if (!verifystr) {
+ verifystr = bundle.getString("certNotVerified_Unknown");
+ }
+ verified.textContent = verifystr;
+ }
+ // Notify that we are done determining the certificate's valid usages (this
+ // should be treated as an implementation detail that enables tests to run
+ // efficiently - other code in the browser probably shouldn't rely on this).
+ Services.obs.notifyObservers(window, "ViewCertDetails:CertUsagesDone", null);
+}
+
+function addChildrenToTree(parentTree, label, value, addTwistie)
+{
+ let treeChild1 = document.createElement("treechildren");
+ let treeElement = addTreeItemToTreeChild(treeChild1, label, value,
+ addTwistie);
+ parentTree.appendChild(treeChild1);
+ return treeElement;
+}
+
+function addTreeItemToTreeChild(treeChild, label, value, addTwistie)
+{
+ let treeElem1 = document.createElement("treeitem");
+ if (addTwistie) {
+ treeElem1.setAttribute("container", "true");
+ treeElem1.setAttribute("open", "true");
+ }
+ let treeRow = document.createElement("treerow");
+ let treeCell = document.createElement("treecell");
+ treeCell.setAttribute("label", label);
+ if (value) {
+ treeCell.setAttribute("display", value);
+ }
+ treeRow.appendChild(treeCell);
+ treeElem1.appendChild(treeRow);
+ treeChild.appendChild(treeElem1);
+ return treeElem1;
+}
+
+function displaySelected() {
+ var asn1Tree = document.getElementById('prettyDumpTree')
+ .view.QueryInterface(nsIASN1Tree);
+ var items = asn1Tree.selection;
+ var certDumpVal = document.getElementById('certDumpVal');
+ if (items.currentIndex != -1) {
+ var value = asn1Tree.getDisplayData(items.currentIndex);
+ certDumpVal.value = value;
+ } else {
+ certDumpVal.value = "";
+ }
+}
+
+function BuildPrettyPrint(cert)
+{
+ var certDumpTree = Components.classes[nsASN1Tree].
+ createInstance(nsIASN1Tree);
+ certDumpTree.loadASN1Structure(cert.ASN1Structure);
+ document.getElementById('prettyDumpTree').view = certDumpTree;
+}
+
+function addAttributeFromCert(nodeName, value)
+{
+ var node = document.getElementById(nodeName);
+ if (!value) {
+ value = bundle.getString('notPresent');
+ }
+ node.setAttribute('value', value);
+}
+
+/**
+ * Displays information about a cert in the "General" tab of the cert viewer.
+ *
+ * @param {nsIX509Cert} cert
+ * Cert to display information about.
+ */
+function DisplayGeneralDataFromCert(cert)
+{
+ addAttributeFromCert("commonname", cert.commonName);
+ addAttributeFromCert("organization", cert.organization);
+ addAttributeFromCert("orgunit", cert.organizationalUnit);
+ addAttributeFromCert("serialnumber", cert.serialNumber);
+ addAttributeFromCert("sha256fingerprint", cert.sha256Fingerprint);
+ addAttributeFromCert("sha1fingerprint", cert.sha1Fingerprint);
+ addAttributeFromCert("validitystart", cert.validity.notBeforeLocalDay);
+ addAttributeFromCert("validityend", cert.validity.notAfterLocalDay);
+
+ addAttributeFromCert("issuercommonname", cert.issuerCommonName);
+ addAttributeFromCert("issuerorganization", cert.issuerOrganization);
+ addAttributeFromCert("issuerorgunit", cert.issuerOrganizationUnit);
+}
+
+function updateCertDump()
+{
+ var asn1Tree = document.getElementById('prettyDumpTree')
+ .view.QueryInterface(nsIASN1Tree);
+
+ var tree = document.getElementById('treesetDump');
+ if (tree.currentIndex < 0) {
+ doPrompt("No items are selected."); //This should never happen.
+ } else {
+ var item = tree.contentView.getItemAtIndex(tree.currentIndex);
+ var dbKey = item.firstChild.firstChild.getAttribute('display');
+ // Get the cert from the cert database
+ var certdb = Components.classes[nsX509CertDB].getService(nsIX509CertDB);
+ var cert = certdb.findCertByDBKey(dbKey);
+ asn1Tree.loadASN1Structure(cert.ASN1Structure);
+ }
+ displaySelected();
+}
+
+function getCurrentCert()
+{
+ var realIndex;
+ var tree = document.getElementById('treesetDump');
+ if (tree.view.selection.isSelected(tree.currentIndex)
+ && document.getElementById('prettyprint_tab').selected) {
+ /* if the user manually selected a cert on the Details tab,
+ then take that one */
+ realIndex = tree.currentIndex;
+ } else {
+ /* otherwise, take the one at the bottom of the chain
+ (i.e. the one of the end-entity, unless we're displaying
+ CA certs) */
+ realIndex = tree.view.rowCount - 1;
+ }
+ if (realIndex >= 0) {
+ var item = tree.contentView.getItemAtIndex(realIndex);
+ var dbKey = item.firstChild.firstChild.getAttribute('display');
+ var certdb = Components.classes[nsX509CertDB].getService(nsIX509CertDB);
+ var cert = certdb.findCertByDBKey(dbKey);
+ return cert;
+ }
+ /* shouldn't really happen */
+ return null;
+}