summaryrefslogtreecommitdiffstats
path: root/toolkit/components/certviewer/content/certviewer.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/certviewer/content/certviewer.mjs')
-rw-r--r--toolkit/components/certviewer/content/certviewer.mjs475
1 files changed, 475 insertions, 0 deletions
diff --git a/toolkit/components/certviewer/content/certviewer.mjs b/toolkit/components/certviewer/content/certviewer.mjs
new file mode 100644
index 0000000000..3e48a069f5
--- /dev/null
+++ b/toolkit/components/certviewer/content/certviewer.mjs
@@ -0,0 +1,475 @@
+/* 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/. */
+
+/* eslint-env mozilla/remote-page */
+
+import { normalizeToKebabCase } from "./components/utils.mjs";
+import {
+ parse,
+ pemToDER,
+} from "chrome://global/content/certviewer/certDecoder.mjs";
+
+document.addEventListener("DOMContentLoaded", async e => {
+ let url = new URL(document.URL);
+ let certInfo = url.searchParams.getAll("cert");
+ if (certInfo.length === 0) {
+ render({}, false, true);
+ return;
+ }
+ certInfo = certInfo.map(cert => decodeURIComponent(cert));
+ await buildChain(certInfo);
+});
+
+export const updateSelectedItem = (() => {
+ let state;
+ return selectedItem => {
+ let certificateSection =
+ document.querySelector("certificate-section") ||
+ document.querySelector("about-certificate-section");
+ if (selectedItem) {
+ if (state !== selectedItem) {
+ state = selectedItem;
+ certificateSection.updateCertificateSource(selectedItem);
+ certificateSection.updateSelectedTab(selectedItem);
+ }
+ }
+ return state;
+ };
+})();
+
+const createEntryItem = (labelId, info, isHex = false) => {
+ if (
+ labelId == null ||
+ info == null ||
+ (Array.isArray(info) && !info.length)
+ ) {
+ return null;
+ }
+ return {
+ labelId,
+ info,
+ isHex,
+ };
+};
+
+const addToResultUsing = (callback, certItems, sectionId, Critical) => {
+ let items = callback();
+ if (items.length) {
+ certItems.push({
+ sectionId,
+ sectionItems: items,
+ Critical,
+ });
+ }
+};
+
+const getElementByPathOrFalse = (obj, pathString) => {
+ let pathArray = pathString.split(".");
+ let result = obj;
+ for (let entry of pathArray) {
+ result = result[entry];
+ if (result == null) {
+ return false;
+ }
+ }
+ return result ? result : false;
+};
+
+export const adjustCertInformation = cert => {
+ let certItems = [];
+ let tabName = cert?.subject?.cn || "";
+ if (cert && !tabName) {
+ // No common name, use the value of the last item in the cert's entries.
+ tabName = cert.subject?.entries?.slice(-1)[0]?.[1] || "";
+ }
+
+ if (!cert) {
+ return {
+ certItems,
+ tabName,
+ };
+ }
+
+ addToResultUsing(
+ () => {
+ let items = [];
+ if (cert.subject && cert.subject.entries) {
+ items = cert.subject.entries
+ .map(entry =>
+ createEntryItem(normalizeToKebabCase(entry[0]), entry[1])
+ )
+ .filter(elem => elem != null);
+ }
+ return items;
+ },
+ certItems,
+ "subject-name",
+ false
+ );
+
+ addToResultUsing(
+ () => {
+ let items = [];
+ if (cert.issuer && cert.issuer.entries) {
+ items = cert.issuer.entries
+ .map(entry =>
+ createEntryItem(normalizeToKebabCase(entry[0]), entry[1])
+ )
+ .filter(elem => elem != null);
+ }
+ return items;
+ },
+ certItems,
+ "issuer-name",
+ false
+ );
+
+ addToResultUsing(
+ () => {
+ let items = [];
+ if (cert.notBefore && cert.notAfter) {
+ items = [
+ createEntryItem("not-before", {
+ local: cert.notBefore,
+ utc: cert.notBeforeUTC,
+ }),
+ createEntryItem("not-after", {
+ local: cert.notAfter,
+ utc: cert.notAfterUTC,
+ }),
+ ].filter(elem => elem != null);
+ }
+ return items;
+ },
+ certItems,
+ "validity",
+ false
+ );
+
+ addToResultUsing(
+ () => {
+ let items = [];
+ if (cert.ext && cert.ext.san && cert.ext.san.altNames) {
+ items = cert.ext.san.altNames
+ .map(entry =>
+ createEntryItem(normalizeToKebabCase(entry[0]), entry[1])
+ )
+ .filter(elem => elem != null);
+ }
+ return items;
+ },
+ certItems,
+ "subject-alt-names",
+ getElementByPathOrFalse(cert, "ext.san.critical")
+ );
+
+ addToResultUsing(
+ () => {
+ let items = [];
+ if (cert.subjectPublicKeyInfo) {
+ items = [
+ createEntryItem("algorithm", cert.subjectPublicKeyInfo.kty),
+ createEntryItem("key-size", cert.subjectPublicKeyInfo.keysize),
+ createEntryItem("curve", cert.subjectPublicKeyInfo.crv),
+ createEntryItem("public-value", cert.subjectPublicKeyInfo.xy, true),
+ createEntryItem("exponent", cert.subjectPublicKeyInfo.e),
+ createEntryItem("modulus", cert.subjectPublicKeyInfo.n, true),
+ ].filter(elem => elem != null);
+ }
+ return items;
+ },
+ certItems,
+ "public-key-info",
+ false
+ );
+
+ addToResultUsing(
+ () => {
+ let items = [
+ createEntryItem("serial-number", cert.serialNumber, true),
+ createEntryItem(
+ "signature-algorithm",
+ cert.signature ? cert.signature.name : null
+ ),
+ createEntryItem("version", cert.version),
+ createEntryItem("download", cert.files ? cert.files.pem : null),
+ ].filter(elem => elem != null);
+ return items;
+ },
+ certItems,
+ "miscellaneous",
+ false
+ );
+
+ addToResultUsing(
+ () => {
+ let items = [];
+ if (cert.fingerprint) {
+ items = [
+ createEntryItem("sha-256", cert.fingerprint.sha256, true),
+ createEntryItem("sha-1", cert.fingerprint.sha1, true),
+ ].filter(elem => elem != null);
+ }
+ return items;
+ },
+ certItems,
+ "fingerprints",
+ false
+ );
+
+ if (!cert.ext) {
+ return {
+ certItems,
+ tabName,
+ };
+ }
+
+ addToResultUsing(
+ () => {
+ let items = [];
+ if (cert.ext.basicConstraints) {
+ items = [
+ createEntryItem(
+ "certificate-authority",
+ cert.ext.basicConstraints.cA
+ ),
+ ].filter(elem => elem != null);
+ }
+ return items;
+ },
+ certItems,
+ "basic-constraints",
+ getElementByPathOrFalse(cert, "ext.basicConstraints.critical")
+ );
+
+ addToResultUsing(
+ () => {
+ let items = [];
+ if (cert.ext.keyUsages) {
+ items = [
+ createEntryItem("purposes", cert.ext.keyUsages.purposes),
+ ].filter(elem => elem != null);
+ }
+ return items;
+ },
+ certItems,
+ "key-usages",
+ getElementByPathOrFalse(cert, "ext.keyUsages.critical")
+ );
+
+ addToResultUsing(
+ () => {
+ let items = [];
+ if (cert.ext.eKeyUsages) {
+ items = [
+ createEntryItem("purposes", cert.ext.eKeyUsages.purposes),
+ ].filter(elem => elem != null);
+ }
+ return items;
+ },
+ certItems,
+ "extended-key-usages",
+ getElementByPathOrFalse(cert, "ext.eKeyUsages.critical")
+ );
+
+ addToResultUsing(
+ () => {
+ let items = [];
+ if (cert.ext.ocspStaple && cert.ext.ocspStaple.required) {
+ items = [createEntryItem("required", true)];
+ }
+ return items;
+ },
+ certItems,
+ "ocsp-stapling",
+ getElementByPathOrFalse(cert, "ext.ocspStaple.critical")
+ );
+
+ addToResultUsing(
+ () => {
+ let items = [];
+ if (cert.ext.sKID) {
+ items = [createEntryItem("key-id", cert.ext.sKID.id, true)].filter(
+ elem => elem != null
+ );
+ }
+ return items;
+ },
+ certItems,
+ "subject-key-id",
+ getElementByPathOrFalse(cert, "ext.sKID.critical")
+ );
+
+ addToResultUsing(
+ () => {
+ let items = [];
+ if (cert.ext.aKID) {
+ items = [createEntryItem("key-id", cert.ext.aKID.id, true)].filter(
+ elem => elem != null
+ );
+ }
+ return items;
+ },
+ certItems,
+ "authority-key-id",
+ getElementByPathOrFalse(cert, "ext.aKID.critical")
+ );
+
+ addToResultUsing(
+ () => {
+ let items = [];
+ if (cert.ext.crlPoints && cert.ext.crlPoints.points) {
+ items = cert.ext.crlPoints.points
+ .map(entry => {
+ let label = "distribution-point";
+ return createEntryItem(label, entry);
+ })
+ .filter(elem => elem != null);
+ }
+ return items;
+ },
+ certItems,
+ "crl-endpoints",
+ getElementByPathOrFalse(cert, "ext.crlPoints.critical")
+ );
+
+ addToResultUsing(
+ () => {
+ let items = [];
+ if (cert.ext.aia && cert.ext.aia.descriptions) {
+ cert.ext.aia.descriptions.forEach(entry => {
+ items.push(createEntryItem("location", entry.location));
+ items.push(createEntryItem("method", entry.method));
+ });
+ }
+ return items.filter(elem => elem != null);
+ },
+ certItems,
+ "authority-info-aia",
+ getElementByPathOrFalse(cert, "ext.aia.critical")
+ );
+
+ addToResultUsing(
+ () => {
+ let items = [];
+ if (cert.ext.cp && cert.ext.cp.policies) {
+ cert.ext.cp.policies.forEach(entry => {
+ if (entry.name && entry.id) {
+ items.push(
+ createEntryItem("policy", entry.name + " ( " + entry.id + " )")
+ );
+ }
+ items.push(createEntryItem("value", entry.value));
+ if (entry.qualifiers) {
+ entry.qualifiers.forEach(qualifier => {
+ if (qualifier.qualifierName && qualifier.qualifierId) {
+ items.push(
+ createEntryItem(
+ "qualifier",
+ qualifier.qualifierName +
+ " ( " +
+ qualifier.qualifierId +
+ " )"
+ )
+ );
+ }
+ items.push(createEntryItem("value", qualifier.qualifierValue));
+ });
+ }
+ });
+ }
+ return items.filter(elem => elem != null);
+ },
+ certItems,
+ "certificate-policies",
+ getElementByPathOrFalse(cert, "ext.cp.critical")
+ );
+
+ addToResultUsing(
+ () => {
+ let items = [];
+ if (cert.ext.scts && cert.ext.scts.timestamps) {
+ cert.ext.scts.timestamps.forEach(entry => {
+ let timestamps = {};
+ for (let key of Object.keys(entry)) {
+ if (key.includes("timestamp")) {
+ timestamps[key.includes("UTC") ? "utc" : "local"] = entry[key];
+ } else {
+ let isHex = false;
+ if (key == "logId") {
+ isHex = true;
+ }
+ items.push(
+ createEntryItem(normalizeToKebabCase(key), entry[key], isHex)
+ );
+ }
+ }
+ items.push(createEntryItem("timestamp", timestamps));
+ });
+ }
+ return items.filter(elem => elem != null);
+ },
+ certItems,
+ "embedded-scts",
+ getElementByPathOrFalse(cert, "ext.scts.critical")
+ );
+
+ return {
+ certItems,
+ tabName,
+ };
+};
+
+// isAboutCertificate means to the standalone page about:certificate, which
+// uses a different customElement than opening a certain certificate
+const render = async (certs, error, isAboutCertificate = false) => {
+ if (isAboutCertificate) {
+ await customElements.whenDefined("about-certificate-section");
+ const AboutCertificateSection = customElements.get(
+ "about-certificate-section"
+ );
+ document.querySelector("body").append(new AboutCertificateSection());
+ } else {
+ await customElements.whenDefined("certificate-section");
+ const CertificateSection = customElements.get("certificate-section");
+ document.querySelector("body").append(new CertificateSection(certs, error));
+ }
+ return Promise.resolve();
+};
+
+const buildChain = async chain => {
+ await Promise.all(
+ chain
+ .map(cert => {
+ try {
+ return pemToDER(cert);
+ } catch (err) {
+ return Promise.reject(err);
+ }
+ })
+ .map(cert => {
+ try {
+ return parse(cert);
+ } catch (err) {
+ return Promise.reject(err);
+ }
+ })
+ )
+ .then(certs => {
+ if (certs.length === 0) {
+ return Promise.reject();
+ }
+ let certTitle = document.querySelector("#certTitle");
+ let firstCertCommonName = certs[0].subject.cn;
+ document.l10n.setAttributes(certTitle, "certificate-viewer-tab-title", {
+ firstCertName: firstCertCommonName,
+ });
+
+ let adjustedCerts = certs.map(cert => adjustCertInformation(cert));
+ return render(adjustedCerts, false);
+ })
+ .catch(err => {
+ render(null, true);
+ });
+};