diff options
Diffstat (limited to 'toolkit/components/certviewer/content/certviewer.mjs')
-rw-r--r-- | toolkit/components/certviewer/content/certviewer.mjs | 475 |
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); + }); +}; |