Bug 1811633 - use updated, vendored version of PKI.js, remove old version r=Gijs default tip
authorDana Keeler <dkeeler@mozilla.com>
Fri, 27 Jan 2023 04:07:10 +0000
changeset 650755 f75c73066b887c2379158c73c994b5ef95460238
parent 650754 6a638a388320bb9b013caddfe0e97459c7cc808b
push id40583
push usernbeleuzu@mozilla.com
push dateFri, 27 Jan 2023 09:46:52 +0000
treeherdermozilla-central@f75c73066b88 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs
bugs1811633
milestone111.0a1
first release with
nightly linux32
f75c73066b88 / 111.0a1 / 20230127094652 / files
nightly linux64
f75c73066b88 / 111.0a1 / 20230127094652 / files
nightly mac
f75c73066b88 / 111.0a1 / 20230127094652 / files
nightly win32
f75c73066b88 / 111.0a1 / 20230127094652 / files
nightly win64
f75c73066b88 / 111.0a1 / 20230127094652 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1811633 - use updated, vendored version of PKI.js, remove old version r=Gijs This also converts certDecoder.jsm to an ES module (as certDecoder.mjs) and updates all uses of it. Differential Revision: https://phabricator.services.mozilla.com/D167466
.eslintrc.js
.hgignore
devtools/shared/network-observer/NetworkHelper.sys.mjs
security/manager/pki/resources/content/clientauthask.js
toolkit/components/certviewer/content/.eslintrc.js
toolkit/components/certviewer/content/README.md
toolkit/components/certviewer/content/asn1js.js
toolkit/components/certviewer/content/bundle.sh
toolkit/components/certviewer/content/certDecoder.jsm
toolkit/components/certviewer/content/certDecoder.mjs
toolkit/components/certviewer/content/certviewer.html
toolkit/components/certviewer/content/certviewer.js
toolkit/components/certviewer/content/certviewer.mjs
toolkit/components/certviewer/content/components/about-certificate-items.js
toolkit/components/certviewer/content/components/about-certificate-items.mjs
toolkit/components/certviewer/content/components/about-certificate-section.js
toolkit/components/certviewer/content/components/about-certificate-section.mjs
toolkit/components/certviewer/content/components/certificate-section.js
toolkit/components/certviewer/content/components/certificate-section.mjs
toolkit/components/certviewer/content/components/certificate-tabs-section.js
toolkit/components/certviewer/content/components/certificate-tabs-section.mjs
toolkit/components/certviewer/content/components/error-section.js
toolkit/components/certviewer/content/components/error-section.mjs
toolkit/components/certviewer/content/components/info-group-container.js
toolkit/components/certviewer/content/components/info-group-container.mjs
toolkit/components/certviewer/content/components/info-group.js
toolkit/components/certviewer/content/components/info-group.mjs
toolkit/components/certviewer/content/components/info-item.js
toolkit/components/certviewer/content/components/info-item.mjs
toolkit/components/certviewer/content/components/list-item.js
toolkit/components/certviewer/content/components/list-item.mjs
toolkit/components/certviewer/content/components/utils.js
toolkit/components/certviewer/content/components/utils.mjs
toolkit/components/certviewer/content/package.json
toolkit/components/certviewer/content/pkijs.js
toolkit/components/certviewer/content/pvutils.js
toolkit/components/certviewer/jar.mn
toolkit/components/certviewer/tests/browser/adjustedCerts.js
toolkit/components/certviewer/tests/chrome/.eslintrc.js
toolkit/components/certviewer/tests/chrome/CSoutput.js
toolkit/components/certviewer/tests/chrome/CSoutput.mjs
toolkit/components/certviewer/tests/chrome/chrome.ini
toolkit/components/certviewer/tests/chrome/parseOutput.js
toolkit/components/certviewer/tests/chrome/parseOutput.mjs
toolkit/components/certviewer/tests/chrome/test_adjustCertInformation.html
toolkit/components/certviewer/tests/chrome/test_certDecoder.html
toolkit/components/certviewer/tests/chrome/test_certDecoderFields.html
toolkit/components/certviewer/tests/chrome/test_kebabCaseInAdjustCertInformation.html
toolkit/content/aboutNetError.mjs
tools/esmify/map.json
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1711,18 +1711,18 @@ module.exports = {
         "toolkit/components/antitracking/test/browser/browser_storageAccessThirdPartyChecks_alwaysPartition.js",
         "toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Embed.js",
         "toolkit/components/antitracking/test/browser/browser_thirdPartyStorageRejectionForCORS.js",
         "toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping.js",
         "toolkit/components/antitracking/test/browser/cookiesCORS.sjs",
         "toolkit/components/antitracking/test/browser/head.js",
         "toolkit/components/antitracking/test/browser/storageAccessAPIHelpers.js",
         "toolkit/components/certviewer/tests/browser/adjustedCerts.js",
-        "toolkit/components/certviewer/tests/chrome/CSoutput.js",
-        "toolkit/components/certviewer/tests/chrome/parseOutput.js",
+        "toolkit/components/certviewer/tests/chrome/CSoutput.mjs",
+        "toolkit/components/certviewer/tests/chrome/parseOutput.mjs",
         "toolkit/components/cleardata/tests/browser/browser_css_cache.js",
         "toolkit/components/cleardata/tests/browser/browser_image_cache.js",
         "toolkit/components/cleardata/tests/browser/browser_preflight_cache.js",
         "toolkit/components/cleardata/tests/browser/browser_quota.js",
         "toolkit/components/cleardata/tests/browser/browser_sessionStorage.js",
         "toolkit/components/cookiebanners/test/browser/browser_cookiebannerservice.js",
         "toolkit/components/downloads/test/unit/common_test_Download.js",
         "toolkit/components/extensions/test/browser/browser_ext_downloads_filters.js",
--- a/.hgignore
+++ b/.hgignore
@@ -241,20 +241,16 @@ tps_result\.json
 # Ignore browsertime output directory
 ^browsertime-results
 
 # Ignore the build directories of WebGPU and WebRender standalone builds.
 gfx/wgpu/target
 gfx/wgpu/.*/build
 gfx/wr/target/
 
-# Ignore this files in certviewer
-toolkit/components/certviewer/content/node_modules/
-toolkit/components/certviewer/content/package-lock.json
-
 # Ignore Rust/Cargo output from running `cargo` directly for image_builder docker image
 ^taskcluster/docker/image_builder/build-image/target
 
 # Ignore ICU4X experimentation data files.
 # See intl/ICU4X.md for more details.
 ^config/external/icu4x
 
 # Ignore the index files generated by clangd.
--- a/devtools/shared/network-observer/NetworkHelper.sys.mjs
+++ b/devtools/shared/network-observer/NetworkHelper.sys.mjs
@@ -68,41 +68,21 @@ ChromeUtils.defineESModuleGetters(lazy, 
   DevToolsInfaillibleUtils:
     "resource://devtools/shared/DevToolsInfaillibleUtils.sys.mjs",
 });
 
 XPCOMUtils.defineLazyModuleGetters(lazy, {
   NetUtil: "resource://gre/modules/NetUtil.jsm",
 });
 
+// It would make sense to put this in the above
+// ChromeUtils.defineESModuleGetters, but that doesn't seem to work.
 XPCOMUtils.defineLazyGetter(lazy, "certDecoder", () => {
-  const { asn1js } = ChromeUtils.import(
-    "chrome://global/content/certviewer/asn1js_bundle.jsm"
-  );
-  const { pkijs } = ChromeUtils.import(
-    "chrome://global/content/certviewer/pkijs_bundle.jsm"
-  );
-  const { pvutils } = ChromeUtils.import(
-    "chrome://global/content/certviewer/pvutils_bundle.jsm"
-  );
-
-  const { Integer, fromBER } = asn1js.asn1js;
-  const { Certificate } = pkijs.pkijs;
-  const { fromBase64, stringToArrayBuffer } = pvutils.pvutils;
-
-  const { certDecoderInitializer } = ChromeUtils.import(
-    "chrome://global/content/certviewer/certDecoder.jsm"
-  );
-  const { parse, pemToDER } = certDecoderInitializer(
-    Integer,
-    fromBER,
-    Certificate,
-    fromBase64,
-    stringToArrayBuffer,
-    crypto
+  const { parse, pemToDER } = ChromeUtils.importESModule(
+    "chrome://global/content/certviewer/certDecoder.mjs"
   );
   return { parse, pemToDER };
 });
 
 // "Lax", "Strict" and "None" are special values of the SameSite cookie
 // attribute that should not be translated.
 const COOKIE_SAMESITE = {
   LAX: "Lax",
--- a/security/manager/pki/resources/content/clientauthask.js
+++ b/security/manager/pki/resources/content/clientauthask.js
@@ -1,40 +1,18 @@
 /* -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*-
  *
  * 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/. */
 /* import-globals-from pippki.js */
 "use strict";
 
-const { asn1js } = ChromeUtils.import(
-  "chrome://global/content/certviewer/asn1js_bundle.jsm"
-);
-const { pkijs } = ChromeUtils.import(
-  "chrome://global/content/certviewer/pkijs_bundle.jsm"
-);
-const { pvutils } = ChromeUtils.import(
-  "chrome://global/content/certviewer/pvutils_bundle.jsm"
-);
-
-const { Integer, fromBER } = asn1js.asn1js;
-const { Certificate } = pkijs.pkijs;
-const { fromBase64, stringToArrayBuffer } = pvutils.pvutils;
-
-const { certDecoderInitializer } = ChromeUtils.import(
-  "chrome://global/content/certviewer/certDecoder.jsm"
-);
-const { parse, pemToDER } = certDecoderInitializer(
-  Integer,
-  fromBER,
-  Certificate,
-  fromBase64,
-  stringToArrayBuffer,
-  crypto
+const { parse, pemToDER } = ChromeUtils.importESModule(
+  "chrome://global/content/certviewer/certDecoder.mjs"
 );
 
 /**
  * @file Implements the functionality of clientauthask.xhtml: a dialog that allows
  *       a user pick a client certificate for TLS client authentication.
  * @param {string} window.arguments.0
  *           The hostname of the server requesting client authentication.
  * @param {string} window.arguments.1
deleted file mode 100644
--- a/toolkit/components/certviewer/content/.eslintrc.js
+++ /dev/null
@@ -1,17 +0,0 @@
-/* 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/. */
-
-module.exports = {
-  parserOptions: {
-    sourceType: "module",
-  },
-  env: {
-    node: true,
-  },
-  globals: {
-    asn1js: true,
-    pvutils: true,
-    pkijs: true,
-  },
-};
deleted file mode 100644
--- a/toolkit/components/certviewer/content/README.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# Certificate Viewer
-
-## Dependencies
-
-[PKI.js](https://github.com/PeculiarVentures/PKI.js)
-[ASN1.js](https://github.com/PeculiarVentures/ASN1.js)
-[pvutils.js](https://github.com/PeculiarVentures/pvutils)
-[Browserify](http://browserify.org/)
-
-## Updating dependencies
-
-Install all the dependencies doing `npm i`.
-
-Run `npm run build` any time you add something new to `pvutils.js`, `pkijs.js`, `asn1.js` or any other file required in that one.
deleted file mode 100644
--- a/toolkit/components/certviewer/content/asn1js.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/* 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/. */
-
-const asn1js = require("asn1js"); // version 2.0.22
-
-module.exports = {
-  asn1js,
-};
deleted file mode 100755
--- a/toolkit/components/certviewer/content/bundle.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-# 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 https://mozilla.org/MPL/2.0/.
-
-# Helper script to bundle the given library and amend it to be loadable in more
-# contexts.
-
-WHICH="${1}"
-
-# Run the browserify command
-./node_modules/browserify/bin/cmd.js "$WHICH".js --standalone "$WHICH" -o ./vendor/"$WHICH"_bundle.jsm
-
-# Amend 'this' in the first line to 'globalThis'
-sed -e '1s/{g=this}/{g=globalThis}/' -i "" ./vendor/"$WHICH"_bundle.jsm
-
-# Append code to export the library
-echo "var $WHICH = globalThis.$WHICH;" >> ./vendor/"$WHICH"_bundle.jsm
-echo "var EXPORTED_SYMBOLS = [\"$WHICH\"];" >> ./vendor/"$WHICH"_bundle.jsm
rename from toolkit/components/certviewer/content/certDecoder.jsm
rename to toolkit/components/certviewer/content/certDecoder.mjs
--- a/toolkit/components/certviewer/content/certDecoder.jsm
+++ b/toolkit/components/certviewer/content/certDecoder.mjs
@@ -1,1304 +1,1270 @@
 /* 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/. */
 
-function certDecoderInitializer(
-  Integer,
-  fromBER,
+import {
   Certificate,
-  fromBase64,
-  stringToArrayBuffer,
-  crypto
-) {
-  const getTimeZone = () => {
-    let timeZone = new Date().toString().match(/\(([A-Za-z\s].*)\)/);
-    if (timeZone === null) {
-      // America/Chicago
-      timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
-    } else if (timeZone.length > 1) {
-      timeZone = timeZone[1]; // Central Daylight Time
-    } else {
-      timeZone = "Local Time"; // not sure if this is right, but let's go with it for now
-    }
-    return timeZone;
-  };
+  ECNamedCurves,
+  ECPublicKey,
+  RSAPublicKey,
+} from "./vendor/pkijs.js";
 
-  const getPublicKeyInfo = x509 => {
-    let spki = Object.assign(
-      {
-        crv: undefined,
-        e: undefined,
-        kty: undefined,
-        n: undefined,
-        keysize: undefined,
-        x: undefined,
-        xy: undefined,
-        y: undefined,
-      },
-      x509.subjectPublicKeyInfo
-    );
+const getTimeZone = () => {
+  let timeZone = new Date().toString().match(/\(([A-Za-z\s].*)\)/);
+  if (timeZone === null) {
+    // America/Chicago
+    timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
+  } else if (timeZone.length > 1) {
+    timeZone = timeZone[1]; // Central Daylight Time
+  } else {
+    timeZone = "Local Time"; // not sure if this is right, but let's go with it for now
+  }
+  return timeZone;
+};
 
-    if (spki.kty === "RSA") {
-      spki.e = b64urltodec(spki.e); // exponent
-      spki.keysize = b64urltohex(spki.n).length * 8; // key size in bits
-      spki.n = hashify(b64urltohex(spki.n)); // modulus
-    } else if (spki.kty === "EC") {
-      spki.kty = "Elliptic Curve";
-      spki.keysize = parseInt(spki.crv.split("-")[1]); // this is a bit hacky
-      spki.x = hashify(b64urltohex(spki.x)); // x coordinate
-      spki.y = hashify(b64urltohex(spki.y)); // y coordinate
-      spki.xy = `04:${spki.x}:${spki.y}`; // 04 (uncompressed) public key
-    }
-    return spki;
-  };
-
-  const getX509Ext = (extensions, v) => {
-    for (var extension in extensions) {
-      if (extensions[extension].extnID === v) {
-        return extensions[extension];
-      }
-    }
+const getPublicKeyInfo = x509 => {
+  let publicKey = x509.subjectPublicKeyInfo.parsedKey;
+  if (publicKey instanceof RSAPublicKey) {
+    let modulusJSON = publicKey.modulus.toJSON();
+    let modulusHex = modulusJSON.valueBlock.valueHex;
     return {
-      extnValue: undefined,
-      parsedValue: undefined,
+      e: publicKey.publicExponent.toJSON().valueBlock.valueDec,
+      kty: "RSA",
+      n: hashify(modulusHex),
+      keysize: modulusHex.length * 4, // key size in bits
     };
-  };
+  }
+  if (publicKey instanceof ECPublicKey) {
+    let x = hashify(publicKey.x);
+    let y = hashify(publicKey.y);
+    let curve = ECNamedCurves.find(publicKey.namedCurve);
+    let keysize = curve ? curve.size * 8 : undefined;
+    return {
+      kty: "Elliptic Curve",
+      keysize,
+      x, // x coordinate
+      y, // y coordinate
+      xy: `04:${x}:${y}`, // 04 (uncompressed) public key
+    };
+  }
+  return { kty: "Unknown" };
+};
 
-  const getKeyUsages = (x509, criticalExtensions) => {
-    let keyUsages = {
-      critical: criticalExtensions.includes("2.5.29.15"),
-      purposes: [],
-    };
-
-    let keyUsagesBS = getX509Ext(x509.extensions, "2.5.29.15").parsedValue;
-    if (keyUsagesBS !== undefined) {
-      // parse the bit string, shifting as necessary
-      let unusedBits = keyUsagesBS.valueBlock.unusedBits;
-      keyUsagesBS = parseInt(keyUsagesBS.valueBlock.valueHex, 16) >> unusedBits;
+const getX509Ext = (extensions, v) => {
+  for (var extension in extensions) {
+    if (extensions[extension].extnID === v) {
+      return extensions[extension].toJSON().parsedValue;
+    }
+  }
+  return undefined;
+};
 
-      // iterate through the bit string
-      strings.keyUsages.slice(unusedBits - 1).forEach(usage => {
-        if (keyUsagesBS & 1) {
-          keyUsages.purposes.push(usage);
-        }
-
-        keyUsagesBS = keyUsagesBS >> 1;
-      });
-
-      // reverse the order for legibility
-      keyUsages.purposes.reverse();
-    }
-
-    return keyUsages;
+const getKeyUsages = (x509, criticalExtensions) => {
+  let keyUsages = {
+    critical: criticalExtensions.includes("2.5.29.15"),
+    purposes: [],
   };
 
-  const parseSubsidiary = distinguishedNames => {
-    const subsidiary = {
-      cn: "",
-      dn: [],
-      entries: [],
-    };
-
-    distinguishedNames.forEach(dn => {
-      const name = strings.names[dn.type];
-      const value = dn.value.valueBlock.value;
+  let keyUsagesBS = getX509Ext(x509.extensions, "2.5.29.15");
+  if (keyUsagesBS !== undefined) {
+    // parse the bit string, shifting as necessary
+    let unusedBits = keyUsagesBS.valueBlock.unusedBits;
+    keyUsagesBS = parseInt(keyUsagesBS.valueBlock.valueHex, 16) >> unusedBits;
 
-      if (name === undefined) {
-        subsidiary.dn.push(`OID.${dn.type}=${value}`);
-        subsidiary.entries.push([`OID.${dn.type}`, value]);
-      } else if (name.short === undefined) {
-        subsidiary.dn.push(`OID.${dn.type}=${value}`);
-        subsidiary.entries.push([name.long, value]);
-      } else {
-        subsidiary.dn.push(`${name.short}=${value}`);
-        subsidiary.entries.push([name.long, value]);
+    // iterate through the bit string
+    strings.keyUsages.slice(unusedBits - 1).forEach(usage => {
+      if (keyUsagesBS & 1) {
+        keyUsages.purposes.push(usage);
+      }
 
-        // add the common name for tab display
-        if (name.short === "cn") {
-          subsidiary.cn = value;
-        }
-      }
+      keyUsagesBS = keyUsagesBS >> 1;
     });
 
-    // turn path into a string
-    subsidiary.dn = subsidiary.dn.join(", ");
-
-    return subsidiary;
-  };
-
-  const getSubjectAltNames = (x509, criticalExtensions) => {
-    let san = getX509Ext(x509.extensions, "2.5.29.17").parsedValue;
-    if (san && san.hasOwnProperty("altNames")) {
-      san = Object.keys(san.altNames).map(x => {
-        const type = san.altNames[x].type;
-
-        switch (type) {
-          case 4: // directory
-            return [
-              strings.san[type],
-              parseSubsidiary(san.altNames[x].value.typesAndValues).dn,
-            ];
-          case 7: // ip address
-            let address = san.altNames[x].value.valueBlock.valueHex;
-
-            if (address.length === 8) {
-              // ipv4
-              return [
-                strings.san[type],
-                address
-                  .match(/.{1,2}/g)
-                  .map(x => parseInt(x, 16))
-                  .join("."),
-              ];
-            } else if (address.length === 32) {
-              // ipv6
-              return [
-                strings.san[type],
-                address
-                  .toLowerCase()
-                  .match(/.{1,4}/g)
-                  .join(":")
-                  .replace(/\b:?(?:0+:?){2,}/, "::"),
-              ];
-            }
-            return [strings.san[type], "Unknown IP address"];
+    // reverse the order for legibility
+    keyUsages.purposes.reverse();
+  }
 
-          default:
-            return [strings.san[type], san.altNames[x].value];
-        }
-      });
-    } else {
-      san = [];
-    }
-    san = {
-      altNames: san,
-      critical: criticalExtensions.includes("2.5.29.17"),
-    };
-    return san;
-  };
-
-  const getBasicConstraints = (x509, criticalExtensions) => {
-    let basicConstraints;
-    const basicConstraintsExt = getX509Ext(x509.extensions, "2.5.29.19");
-    if (basicConstraintsExt && basicConstraintsExt.parsedValue) {
-      basicConstraints = {
-        cA:
-          basicConstraintsExt.parsedValue.cA !== undefined &&
-          basicConstraintsExt.parsedValue.cA,
-        critical: criticalExtensions.includes("2.5.29.19"),
-      };
-    }
-    return basicConstraints;
-  };
+  return keyUsages;
+};
 
-  const getEKeyUsages = (x509, criticalExtensions) => {
-    let eKeyUsages = getX509Ext(x509.extensions, "2.5.29.37").parsedValue;
-    if (eKeyUsages) {
-      eKeyUsages = {
-        critical: criticalExtensions.includes("2.5.29.37"),
-        purposes: eKeyUsages.keyPurposes.map(x => strings.eKU[x] || x),
-      };
-    }
-    return eKeyUsages;
-  };
-
-  const getSubjectKeyID = (x509, criticalExtensions) => {
-    let sKID = getX509Ext(x509.extensions, "2.5.29.14").parsedValue;
-    if (sKID) {
-      sKID = {
-        critical: criticalExtensions.includes("2.5.29.14"),
-        id: hashify(sKID.valueBlock.valueHex),
-      };
-    }
-    return sKID;
-  };
-
-  const getAuthorityKeyID = (x509, criticalExtensions) => {
-    let aKID = getX509Ext(x509.extensions, "2.5.29.35").parsedValue;
-    if (!aKID || !aKID.keyIdentifier) {
-      return null;
-    }
-    aKID = {
-      critical: criticalExtensions.includes("2.5.29.35"),
-      id: hashify(aKID.keyIdentifier.valueBlock.valueHex),
-    };
-    return aKID;
+const parseSubsidiary = distinguishedNames => {
+  const subsidiary = {
+    cn: "",
+    dn: [],
+    entries: [],
   };
 
-  const getCRLPoints = (x509, criticalExtensions) => {
-    let crlPoints = getX509Ext(x509.extensions, "2.5.29.31").parsedValue;
-    if (crlPoints) {
-      crlPoints = {
-        critical: criticalExtensions.includes("2.5.29.31"),
-        points: crlPoints.distributionPoints.map(
-          x => x.distributionPoint[0].value
-        ),
-      };
-    }
-    return crlPoints;
-  };
+  distinguishedNames.forEach(dn => {
+    const name = strings.names[dn.type];
+    const value = dn.value.valueBlock.value;
 
-  const getOcspStaple = (x509, criticalExtensions) => {
-    let ocspStaple = getX509Ext(x509.extensions, "1.3.6.1.5.5.7.1.24")
-      .extnValue;
-    if (ocspStaple && ocspStaple.valueBlock.valueHex === "3003020105") {
-      ocspStaple = {
-        critical: criticalExtensions.includes("1.3.6.1.5.5.7.1.24"),
-        required: true,
-      };
+    if (name === undefined) {
+      subsidiary.dn.push(`OID.${dn.type}=${value}`);
+      subsidiary.entries.push([`OID.${dn.type}`, value]);
+    } else if (name.short === undefined) {
+      subsidiary.dn.push(`OID.${dn.type}=${value}`);
+      subsidiary.entries.push([name.long, value]);
     } else {
-      ocspStaple = {
-        critical: criticalExtensions.includes("1.3.6.1.5.5.7.1.24"),
-        required: false,
-      };
+      subsidiary.dn.push(`${name.short}=${value}`);
+      subsidiary.entries.push([name.long, value]);
+
+      // add the common name for tab display
+      if (name.short === "cn") {
+        subsidiary.cn = value;
+      }
     }
-    return ocspStaple;
-  };
+  });
+
+  // turn path into a string
+  subsidiary.dn = subsidiary.dn.join(", ");
+
+  return subsidiary;
+};
+
+const getSubjectAltNames = (x509, criticalExtensions) => {
+  let san = getX509Ext(x509.extensions, "2.5.29.17");
+  if (san && san.hasOwnProperty("altNames")) {
+    san = Object.keys(san.altNames).map(index => {
+      const type = san.altNames[index].type;
 
-  const getAuthorityInfoAccess = (x509, criticalExtensions) => {
-    let aia = getX509Ext(x509.extensions, "1.3.6.1.5.5.7.1.1").parsedValue;
-    if (aia) {
-      aia = aia.accessDescriptions.map(x => {
-        return {
-          location: x.accessLocation.value,
-          method: strings.aia[x.accessMethod],
-        };
-      });
-    }
+      switch (type) {
+        case 4: // directory
+          return [
+            strings.san[type],
+            parseSubsidiary(san.altNames[index].value.typesAndValues).dn,
+          ];
+        case 7: // ip address
+          let address = san.altNames[index].value.valueBlock.valueHex;
 
-    aia = {
-      descriptions: aia,
-      critical: criticalExtensions.includes("1.3.6.1.5.5.7.1.1"),
-    };
-    return aia;
-  };
+          if (address.length === 8) {
+            // ipv4
+            return [
+              strings.san[type],
+              address
+                .match(/.{1,2}/g)
+                .map(x => parseInt(x, 16))
+                .join("."),
+            ];
+          } else if (address.length === 32) {
+            // ipv6
+            return [
+              strings.san[type],
+              address
+                .toLowerCase()
+                .match(/.{1,4}/g)
+                .join(":")
+                .replace(/\b:?(?:0+:?){2,}/, "::"),
+            ];
+          }
+          return [strings.san[type], "Unknown IP address"];
 
-  const getSCTs = (x509, criticalExtensions) => {
-    let scts = getX509Ext(x509.extensions, "1.3.6.1.4.1.11129.2.4.2")
-      .parsedValue;
-    if (scts) {
-      scts = Object.keys(scts.timestamps).map(x => {
-        let logId = scts.timestamps[x].logID.toLowerCase();
-        let sctsTimestamp = scts.timestamps[x].timestamp;
-        return {
-          logId: hashify(logId),
-          name: ctLogNames.hasOwnProperty(logId)
-            ? ctLogNames[logId]
-            : undefined,
-          signatureAlgorithm: `${scts.timestamps[x].hashAlgorithm.replace(
-            "sha",
-            "SHA-"
-          )} ${scts.timestamps[x].signatureAlgorithm.toUpperCase()}`,
-          timestamp: `${sctsTimestamp.toLocaleString()} (${getTimeZone()})`,
-          timestampUTC: sctsTimestamp.toUTCString(),
-          version: scts.timestamps[x].version + 1,
-        };
-      });
-    } else {
-      scts = [];
-    }
+        default:
+          return [strings.san[type], san.altNames[index].value];
+      }
+    });
+  } else {
+    san = [];
+  }
+  san = {
+    altNames: san,
+    critical: criticalExtensions.includes("2.5.29.17"),
+  };
+  return san;
+};
 
-    scts = {
-      critical: criticalExtensions.includes("1.3.6.1.4.1.11129.2.4.2"),
-      timestamps: scts,
+const getBasicConstraints = (x509, criticalExtensions) => {
+  let basicConstraints;
+  const basicConstraintsExt = getX509Ext(x509.extensions, "2.5.29.19");
+  if (basicConstraintsExt) {
+    basicConstraints = {
+      cA: basicConstraintsExt.cA !== undefined && basicConstraintsExt.cA,
+      critical: criticalExtensions.includes("2.5.29.19"),
+    };
+  }
+  return basicConstraints;
+};
+
+const getEKeyUsages = (x509, criticalExtensions) => {
+  let eKeyUsages = getX509Ext(x509.extensions, "2.5.29.37");
+  if (eKeyUsages) {
+    eKeyUsages = {
+      critical: criticalExtensions.includes("2.5.29.37"),
+      purposes: eKeyUsages.keyPurposes.map(x => strings.eKU[x] || x),
     };
-    return scts;
-  };
+  }
+  return eKeyUsages;
+};
+
+const getSubjectKeyID = (x509, criticalExtensions) => {
+  let sKID = getX509Ext(x509.extensions, "2.5.29.14");
+  if (sKID) {
+    sKID = {
+      critical: criticalExtensions.includes("2.5.29.14"),
+      id: hashify(sKID.valueBlock.valueHex),
+    };
+  }
+  return sKID;
+};
 
-  const getCertificatePolicies = (x509, criticalExtensions) => {
-    let cp = getX509Ext(x509.extensions, "2.5.29.32").parsedValue;
-    if (cp && cp.hasOwnProperty("certificatePolicies")) {
-      cp = cp.certificatePolicies.map(x => {
-        let id = x.policyIdentifier;
-        let name = strings.cps.hasOwnProperty(id)
-          ? strings.cps[id].name
-          : undefined;
-        let qualifiers = undefined;
-        let value = strings.cps.hasOwnProperty(id)
-          ? strings.cps[id].value
-          : undefined;
+const getAuthorityKeyID = (x509, criticalExtensions) => {
+  let aKID = getX509Ext(x509.extensions, "2.5.29.35");
+  if (!aKID || !aKID.keyIdentifier) {
+    return null;
+  }
+  aKID = {
+    critical: criticalExtensions.includes("2.5.29.35"),
+    id: hashify(aKID.keyIdentifier.valueBlock.valueHex),
+  };
+  return aKID;
+};
 
-        // ansi organization identifiers
-        if (id.startsWith("2.16.840.")) {
-          value = id;
-          id = "2.16.840";
-          name = strings.cps["2.16.840"].name;
-        }
-
-        // statement identifiers
-        if (id.startsWith("1.3.6.1.4.1")) {
-          value = id;
-          id = "1.3.6.1.4.1";
-          name = strings.cps["1.3.6.1.4.1"].name;
-        }
+const getCRLPoints = (x509, criticalExtensions) => {
+  let crlPoints = getX509Ext(x509.extensions, "2.5.29.31");
+  if (crlPoints) {
+    crlPoints = {
+      critical: criticalExtensions.includes("2.5.29.31"),
+      points: crlPoints.distributionPoints.map(
+        x => x.distributionPoint[0].value
+      ),
+    };
+  }
+  return crlPoints;
+};
 
-        if (x.hasOwnProperty("policyQualifiers")) {
-          qualifiers = x.policyQualifiers.map(qualifier => {
-            let id = qualifier.policyQualifierId;
-            let name = strings.cps.hasOwnProperty(id)
-              ? strings.cps[id].name
-              : undefined;
-            let value = qualifier.qualifier.valueBlock.value;
+const getOcspStaple = (x509, criticalExtensions) => {
+  let ocspStaple = getX509Ext(x509.extensions, "1.3.6.1.5.5.7.1.24");
+  if (ocspStaple && ocspStaple.valueBeforeDecode === "3003020105") {
+    ocspStaple = {
+      critical: criticalExtensions.includes("1.3.6.1.5.5.7.1.24"),
+      required: true,
+    };
+  } else {
+    ocspStaple = {
+      critical: criticalExtensions.includes("1.3.6.1.5.5.7.1.24"),
+      required: false,
+    };
+  }
+  return ocspStaple;
+};
 
-            // sometimes they are multiple qualifier subblocks, and for now we'll
-            // only return the first one because it's getting really messy at this point
-            if (Array.isArray(value) && value.length === 1) {
-              value = value[0].valueBlock.value;
-            } else if (Array.isArray(value) && value.length > 1) {
-              value = "(currently unsupported)";
-            }
-
-            return {
-              id,
-              name,
-              value,
-            };
-          });
-        }
+const getAuthorityInfoAccess = (x509, criticalExtensions) => {
+  let aia = getX509Ext(x509.extensions, "1.3.6.1.5.5.7.1.1");
+  if (aia) {
+    aia = aia.accessDescriptions.map(x => {
+      return {
+        location: x.accessLocation.value,
+        method: strings.aia[x.accessMethod],
+      };
+    });
+  }
 
-        return {
-          id,
-          name,
-          qualifiers,
-          value,
-        };
-      });
-    }
-
-    cp = {
-      critical: criticalExtensions.includes("2.5.29.32"),
-      policies: cp,
-    };
-    return cp;
+  aia = {
+    descriptions: aia,
+    critical: criticalExtensions.includes("1.3.6.1.5.5.7.1.1"),
   };
+  return aia;
+};
 
-  const getMicrosoftCryptographicExtensions = (x509, criticalExtensions) => {
-    // now let's parse the Microsoft cryptographic extensions
-    let msCrypto = {
-      caVersion: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.21.1")
-        .parsedValue,
-      certificatePolicies: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.21.10")
-        .parsedValue,
-      certificateTemplate: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.21.7")
-        .parsedValue,
-      certificateType: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.20.2")
-        .parsedValue,
-      previousHash: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.21.2")
-        .parsedValue,
-    };
+const getSCTs = (x509, criticalExtensions) => {
+  let scts = getX509Ext(x509.extensions, "1.3.6.1.4.1.11129.2.4.2");
+  if (scts) {
+    scts = Object.keys(scts.timestamps).map(x => {
+      let logId = scts.timestamps[x].logID.toLowerCase();
+      let sctsTimestamp = scts.timestamps[x].timestamp;
+      return {
+        logId: hashify(logId),
+        name: ctLogNames.hasOwnProperty(logId) ? ctLogNames[logId] : undefined,
+        signatureAlgorithm: `${scts.timestamps[x].hashAlgorithm.replace(
+          "sha",
+          "SHA-"
+        )} ${scts.timestamps[x].signatureAlgorithm.toUpperCase()}`,
+        timestamp: `${sctsTimestamp.toLocaleString()} (${getTimeZone()})`,
+        timestampUTC: sctsTimestamp.toUTCString(),
+        version: scts.timestamps[x].version + 1,
+      };
+    });
+  } else {
+    scts = [];
+  }
+
+  scts = {
+    critical: criticalExtensions.includes("1.3.6.1.4.1.11129.2.4.2"),
+    timestamps: scts,
+  };
+  return scts;
+};
 
-    if (
-      msCrypto.caVersion &&
-      Number.isInteger(msCrypto.caVersion.keyIndex) &&
-      Number.isInteger(msCrypto.caVersion.certificateIndex)
-    ) {
-      msCrypto.caVersion = {
-        critical: criticalExtensions.includes("1.3.6.1.4.1.311.21.1"),
-        caRenewals: msCrypto.caVersion.certificateIndex,
-        keyReuses:
-          msCrypto.caVersion.certificateIndex - msCrypto.caVersion.keyIndex,
-      };
-    }
+const getCertificatePolicies = (x509, criticalExtensions) => {
+  let cp = getX509Ext(x509.extensions, "2.5.29.32");
+  if (cp && cp.hasOwnProperty("certificatePolicies")) {
+    cp = cp.certificatePolicies.map(x => {
+      let id = x.policyIdentifier;
+      let name = strings.cps.hasOwnProperty(id)
+        ? strings.cps[id].name
+        : undefined;
+      let qualifiers = undefined;
+      let value = strings.cps.hasOwnProperty(id)
+        ? strings.cps[id].value
+        : undefined;
 
-    if (msCrypto.certificatePolicies) {
-      msCrypto.certificatePolicies = {
-        critical: criticalExtensions.includes("1.3.6.1.4.1.311.21.10"),
-        purposes: msCrypto.certificatePolicies.certificatePolicies.map(
-          x => strings.eKU[x.policyIdentifier] || x.policyIdentifier
-        ),
-      };
-    }
+      // ansi organization identifiers
+      if (id.startsWith("2.16.840.")) {
+        value = id;
+        id = "2.16.840";
+        name = strings.cps["2.16.840"].name;
+      }
 
-    if (msCrypto.certificateTemplate) {
-      msCrypto.certificateTemplate = {
-        critical: criticalExtensions.includes("1.3.6.1.4.1.311.21.7"),
-        id: msCrypto.certificateTemplate.extnID,
-        major: msCrypto.certificateTemplate.templateMajorVersion,
-        minor: msCrypto.certificateTemplate.templateMinorVersion,
-      };
-    }
+      // statement identifiers
+      if (id.startsWith("1.3.6.1.4.1")) {
+        value = id;
+        id = "1.3.6.1.4.1";
+        name = strings.cps["1.3.6.1.4.1"].name;
+      }
+
+      if (x.hasOwnProperty("policyQualifiers")) {
+        qualifiers = x.policyQualifiers.map(qualifier => {
+          let qualifierId = qualifier.policyQualifierId;
+          let qualifierName = strings.cps.hasOwnProperty(qualifierId)
+            ? strings.cps[qualifierId].name
+            : undefined;
+          let qualifierValue = qualifier.qualifier.valueBlock.value;
 
-    if (msCrypto.certificateType) {
-      msCrypto.certificateType = {
-        critical: criticalExtensions.includes("1.3.6.1.4.1.311.20.2"),
-        type:
-          strings.microsoftCertificateTypes[
-            msCrypto.certificateType.valueBlock.value
-          ] || "Unknown",
-      };
-    }
+          // sometimes they are multiple qualifier subblocks, and for now we'll
+          // only return the first one because it's getting really messy at this point
+          if (Array.isArray(qualifierValue) && qualifierValue.length === 1) {
+            qualifierValue = qualifierValue[0].valueBlock.value;
+          } else if (
+            Array.isArray(qualifierValue) &&
+            qualifierValue.length > 1
+          ) {
+            qualifierValue = "(currently unsupported)";
+          }
 
-    if (msCrypto.previousHash) {
-      msCrypto.previousHash = {
-        critical: criticalExtensions.includes("1.3.6.1.4.1.311.21.2"),
-        previousHash: hashify(msCrypto.previousHash.valueBlock.valueHex),
+          return {
+            qualifierId,
+            qualifierName,
+            qualifierValue,
+          };
+        });
+      }
+
+      return {
+        id,
+        name,
+        qualifiers,
+        value,
       };
-    }
+    });
+  }
 
-    msCrypto.exists = !!(
-      msCrypto.caVersion ||
-      msCrypto.certificatePolicies ||
-      msCrypto.certificateTemplate ||
-      msCrypto.certificateType ||
-      msCrypto.previousHash
-    );
+  cp = {
+    critical: criticalExtensions.includes("2.5.29.32"),
+    policies: cp,
+  };
+  return cp;
+};
 
-    return msCrypto;
-  };
-
-  const b64ToPEM = string => {
-    let wrapped = string.match(/.{1,64}/g).join("\r\n");
-    return `-----BEGIN CERTIFICATE-----\r\n${wrapped}\r\n-----END CERTIFICATE-----\r\n`;
+const getMicrosoftCryptographicExtensions = (x509, criticalExtensions) => {
+  // now let's parse the Microsoft cryptographic extensions
+  let msCrypto = {
+    caVersion: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.21.1"),
+    certificatePolicies: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.21.10"),
+    certificateTemplate: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.21.7"),
+    certificateType: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.20.2"),
+    previousHash: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.21.2"),
   };
 
-  const parse = async certificate => {
-    // certificate could be an array of BER or an array of buffers
-    const supportedExtensions = [
-      "1.3.6.1.4.1.311.20.2", // microsoft certificate type
-      "1.3.6.1.4.1.311.21.2", // microsoft certificate previous hash
-      "1.3.6.1.4.1.311.21.7", // microsoft certificate template
-      "1.3.6.1.4.1.311.21.1", // microsoft certification authority renewal
-      "1.3.6.1.4.1.311.21.10", // microsoft certificate policies
-      "1.3.6.1.4.1.11129.2.4.2", // embedded scts
-      "1.3.6.1.5.5.7.1.1", // authority info access
-      "1.3.6.1.5.5.7.1.24", // ocsp stapling
-      "1.3.101.77", // ct redaction - deprecated and not displayed
-      "2.5.29.14", // subject key identifier
-      "2.5.29.15", // key usages
-      "2.5.29.17", // subject alt names
-      "2.5.29.19", // basic constraints
-      "2.5.29.31", // crl points
-      "2.5.29.32", // certificate policies
-      "2.5.29.35", // authority key identifier
-      "2.5.29.37", // extended key usage
-    ];
+  if (
+    msCrypto.caVersion &&
+    Number.isInteger(msCrypto.caVersion.keyIndex) &&
+    Number.isInteger(msCrypto.caVersion.certificateIndex)
+  ) {
+    msCrypto.caVersion = {
+      critical: criticalExtensions.includes("1.3.6.1.4.1.311.21.1"),
+      caRenewals: msCrypto.caVersion.certificateIndex,
+      keyReuses:
+        msCrypto.caVersion.certificateIndex - msCrypto.caVersion.keyIndex,
+    };
+  }
+
+  if (msCrypto.certificatePolicies) {
+    msCrypto.certificatePolicies = {
+      critical: criticalExtensions.includes("1.3.6.1.4.1.311.21.10"),
+      purposes: msCrypto.certificatePolicies.certificatePolicies.map(
+        x => strings.eKU[x.policyIdentifier] || x.policyIdentifier
+      ),
+    };
+  }
+
+  if (msCrypto.certificateTemplate) {
+    msCrypto.certificateTemplate = {
+      critical: criticalExtensions.includes("1.3.6.1.4.1.311.21.7"),
+      id: msCrypto.certificateTemplate.extnID,
+      major: msCrypto.certificateTemplate.templateMajorVersion,
+      minor: msCrypto.certificateTemplate.templateMinorVersion,
+    };
+  }
+
+  if (msCrypto.certificateType) {
+    msCrypto.certificateType = {
+      critical: criticalExtensions.includes("1.3.6.1.4.1.311.20.2"),
+      type:
+        strings.microsoftCertificateTypes[
+          msCrypto.certificateType.valueBlock.value
+        ] || "Unknown",
+    };
+  }
 
-    let timeZone = getTimeZone();
+  if (msCrypto.previousHash) {
+    msCrypto.previousHash = {
+      critical: criticalExtensions.includes("1.3.6.1.4.1.311.21.2"),
+      previousHash: hashify(msCrypto.previousHash.valueBlock.valueHex),
+    };
+  }
 
-    // parse the certificate
-    const asn1 = fromBER(certificate);
+  msCrypto.exists = !!(
+    msCrypto.caVersion ||
+    msCrypto.certificatePolicies ||
+    msCrypto.certificateTemplate ||
+    msCrypto.certificateType ||
+    msCrypto.previousHash
+  );
+
+  return msCrypto;
+};
 
-    let x509 = new Certificate({ schema: asn1.result });
-    x509 = x509.toJSON();
+const b64ToPEM = string => {
+  let wrapped = string.match(/.{1,64}/g).join("\r\n");
+  return `-----BEGIN CERTIFICATE-----\r\n${wrapped}\r\n-----END CERTIFICATE-----\r\n`;
+};
 
-    // convert the cert to PEM
-    const certPEM = b64ToPEM(
-      btoa(String.fromCharCode.apply(null, new Uint8Array(certificate)))
-    );
+export const parse = async certificate => {
+  // certificate could be an array of BER or an array of buffers
+  const supportedExtensions = [
+    "1.3.6.1.4.1.311.20.2", // microsoft certificate type
+    "1.3.6.1.4.1.311.21.2", // microsoft certificate previous hash
+    "1.3.6.1.4.1.311.21.7", // microsoft certificate template
+    "1.3.6.1.4.1.311.21.1", // microsoft certification authority renewal
+    "1.3.6.1.4.1.311.21.10", // microsoft certificate policies
+    "1.3.6.1.4.1.11129.2.4.2", // embedded scts
+    "1.3.6.1.5.5.7.1.1", // authority info access
+    "1.3.6.1.5.5.7.1.24", // ocsp stapling
+    "1.3.101.77", // ct redaction - deprecated and not displayed
+    "2.5.29.14", // subject key identifier
+    "2.5.29.15", // key usages
+    "2.5.29.17", // subject alt names
+    "2.5.29.19", // basic constraints
+    "2.5.29.31", // crl points
+    "2.5.29.32", // certificate policies
+    "2.5.29.35", // authority key identifier
+    "2.5.29.37", // extended key usage
+  ];
 
-    // get which extensions are critical
-    const criticalExtensions = [];
-    if (x509.extensions) {
-      x509.extensions.forEach(ext => {
-        if (ext.hasOwnProperty("critical") && ext.critical === true) {
-          criticalExtensions.push(ext.extnID);
-        }
-      });
-    }
-    const spki = getPublicKeyInfo(x509);
-    const keyUsages = getKeyUsages(x509, criticalExtensions);
-    const san = getSubjectAltNames(x509, criticalExtensions);
-    const basicConstraints = getBasicConstraints(x509, criticalExtensions);
-    const eKeyUsages = getEKeyUsages(x509, criticalExtensions);
-    const sKID = getSubjectKeyID(x509, criticalExtensions);
-    const aKID = getAuthorityKeyID(x509, criticalExtensions);
-    const crlPoints = getCRLPoints(x509, criticalExtensions);
-    const ocspStaple = getOcspStaple(x509, criticalExtensions);
-    const aia = getAuthorityInfoAccess(x509, criticalExtensions);
-    const scts = getSCTs(x509, criticalExtensions);
-    const cp = getCertificatePolicies(x509, criticalExtensions);
-    const msCrypto = getMicrosoftCryptographicExtensions(
-      x509,
-      criticalExtensions
-    );
+  let timeZone = getTimeZone();
+
+  // parse the certificate
+  let x509 = Certificate.fromBER(certificate);
+
+  // convert the cert to PEM
+  const certPEM = b64ToPEM(
+    btoa(String.fromCharCode.apply(null, new Uint8Array(certificate)))
+  );
 
-    // determine which extensions weren't supported
-    let unsupportedExtensions = [];
-    if (x509.extensions) {
-      x509.extensions.forEach(ext => {
-        if (!supportedExtensions.includes(ext.extnID)) {
-          unsupportedExtensions.push(ext.extnID);
-        }
-      });
-    }
+  // get which extensions are critical
+  const criticalExtensions = [];
+  if (x509.extensions) {
+    x509.extensions.forEach(ext => {
+      if (ext.hasOwnProperty("critical") && ext.critical === true) {
+        criticalExtensions.push(ext.extnID);
+      }
+    });
+  }
+  const spki = getPublicKeyInfo(x509);
+  const keyUsages = getKeyUsages(x509, criticalExtensions);
+  const san = getSubjectAltNames(x509, criticalExtensions);
+  const basicConstraints = getBasicConstraints(x509, criticalExtensions);
+  const eKeyUsages = getEKeyUsages(x509, criticalExtensions);
+  const sKID = getSubjectKeyID(x509, criticalExtensions);
+  const aKID = getAuthorityKeyID(x509, criticalExtensions);
+  const crlPoints = getCRLPoints(x509, criticalExtensions);
+  const ocspStaple = getOcspStaple(x509, criticalExtensions);
+  const aia = getAuthorityInfoAccess(x509, criticalExtensions);
+  const scts = getSCTs(x509, criticalExtensions);
+  const cp = getCertificatePolicies(x509, criticalExtensions);
+  const msCrypto = getMicrosoftCryptographicExtensions(
+    x509,
+    criticalExtensions
+  );
 
-    // the output shell
-    return {
-      ext: {
-        aia,
-        aKID,
-        basicConstraints,
-        crlPoints,
-        cp,
-        eKeyUsages,
-        keyUsages,
-        msCrypto,
-        ocspStaple,
-        scts,
-        sKID,
-        san,
-      },
-      files: {
-        der: undefined, // TODO: implement!
-        pem: encodeURI(certPEM),
-      },
-      fingerprint: {
-        sha1: await hash("SHA-1", certificate),
-        sha256: await hash("SHA-256", certificate),
-      },
-      issuer: parseSubsidiary(x509.issuer.typesAndValues),
-      notBefore: `${x509.notBefore.value.toLocaleString()} (${timeZone})`,
-      notBeforeUTC: x509.notBefore.value.toUTCString(),
-      notAfter: `${x509.notAfter.value.toLocaleString()} (${timeZone})`,
-      notAfterUTC: x509.notAfter.value.toUTCString(),
-      subject: parseSubsidiary(x509.subject.typesAndValues),
-      serialNumber: hashify(
-        getObjPath(x509, "serialNumber.valueBlock.valueHex")
-      ),
-      signature: {
-        name: strings.signature[getObjPath(x509, "signature.algorithmId")],
-        type: getObjPath(x509, "signature.algorithmId"),
-      },
-      subjectPublicKeyInfo: spki,
-      unsupportedExtensions,
-      version: (x509.version + 1).toString(),
-    };
+  // determine which extensions weren't supported
+  let unsupportedExtensions = [];
+  if (x509.extensions) {
+    x509.extensions.forEach(ext => {
+      if (!supportedExtensions.includes(ext.extnID)) {
+        unsupportedExtensions.push(ext.extnID);
+      }
+    });
+  }
+
+  // the output shell
+  return {
+    ext: {
+      aia,
+      aKID,
+      basicConstraints,
+      crlPoints,
+      cp,
+      eKeyUsages,
+      keyUsages,
+      msCrypto,
+      ocspStaple,
+      scts,
+      sKID,
+      san,
+    },
+    files: {
+      der: undefined, // TODO: implement!
+      pem: encodeURI(certPEM),
+    },
+    fingerprint: {
+      sha1: await hash("SHA-1", certificate),
+      sha256: await hash("SHA-256", certificate),
+    },
+    issuer: parseSubsidiary(x509.issuer.typesAndValues),
+    notBefore: `${x509.notBefore.value.toLocaleString()} (${timeZone})`,
+    notBeforeUTC: x509.notBefore.value.toUTCString(),
+    notAfter: `${x509.notAfter.value.toLocaleString()} (${timeZone})`,
+    notAfterUTC: x509.notAfter.value.toUTCString(),
+    subject: parseSubsidiary(x509.subject.typesAndValues),
+    serialNumber: hashify(getObjPath(x509, "serialNumber.valueBlock.valueHex")),
+    signature: {
+      name: strings.signature[getObjPath(x509, "signature.algorithmId")],
+      type: getObjPath(x509, "signature.algorithmId"),
+    },
+    subjectPublicKeyInfo: spki,
+    unsupportedExtensions,
+    version: (x509.version + 1).toString(),
   };
+};
 
-  const ctLogNames = {
-    "9606c02c690033aa1d145f59c6e2648d0549f0df96aab8db915a70d8ecf390a5":
-      "Akamai CT",
-    "39376f545f7b4607f59742d768cd5d2437bf3473b6534a4834bcf72e681c83c9":
-      "Alpha CT",
-    a577ac9ced7548dd8f025b67a241089df86e0f476ec203c2ecbedb185f282638:
-      "CNNIC CT",
-    cdb5179b7fc1c046feea31136a3f8f002e6182faf8896fecc8b2f5b5ab604900:
-      "Certly.IO",
-    "1fbc36e002ede97f40199e86b3573b8a4217d80187746ad0da03a06054d20df4":
-      "Cloudflare “Nimbus2017”",
-    db74afeecb29ecb1feca3e716d2ce5b9aabb36f7847183c75d9d4f37b61fbf64:
-      "Cloudflare “Nimbus2018”",
-    "747eda8331ad331091219cce254f4270c2bffd5e422008c6373579e6107bcc56":
-      "Cloudflare “Nimbus2019”",
-    "5ea773f9df56c0e7b536487dd049e0327a919a0c84a112128418759681714558":
-      "Cloudflare “Nimbus2020”",
-    "4494652eb0eeceafc44007d8a8fe28c0dae682bed8cb31b53fd33396b5b681a8":
-      "Cloudflare “Nimbus2021”",
-    "41c8cab1df22464a10c6a13a0942875e4e318b1b03ebeb4bc768f090629606f6":
-      "Cloudflare “Nimbus2022”",
-    "7a328c54d8b72db620ea38e0521ee98416703213854d3bd22bc13a57a352eb52":
-      "Cloudflare “Nimbus2023”",
-    "6ff141b5647e4222f7ef052cefae7c21fd608e27d2af5a6e9f4b8a37d6633ee5":
-      "DigiCert Nessie2018",
-    fe446108b1d01ab78a62ccfeab6ab2b2babff3abdad80a4d8b30df2d0008830c:
-      "DigiCert Nessie2019",
-    c652a0ec48ceb3fcab170992c43a87413309e80065a26252401ba3362a17c565:
-      "DigiCert Nessie2020",
-    eec095ee8d72640f92e3c3b91bc712a3696a097b4b6a1a1438e647b2cbedc5f9:
-      "DigiCert Nessie2021",
-    "51a3b0f5fd01799c566db837788f0ca47acc1b27cbf79e88429a0dfed48b05e5":
-      "DigiCert Nessie2022",
-    b3737707e18450f86386d605a9dc11094a792db1670c0b87dcf0030e7936a59a:
-      "DigiCert Nessie2023",
-    "5614069a2fd7c2ecd3f5e1bd44b23ec74676b9bc99115cc0ef949855d689d0dd":
-      "DigiCert Server",
-    "8775bfe7597cf88c43995fbdf36eff568d475636ff4ab560c1b4eaff5ea0830f":
-      "DigiCert Server 2",
-    c1164ae0a772d2d4392dc80ac10770d4f0c49bde991a4840c1fa075164f63360:
-      "DigiCert Yeti2018",
-    e2694bae26e8e94009e8861bb63b83d43ee7fe7488fba48f2893019dddf1dbfe:
-      "DigiCert Yeti2019",
-    f095a459f200d18240102d2f93888ead4bfe1d47e399e1d034a6b0a8aa8eb273:
-      "DigiCert Yeti2020",
-    "5cdc4392fee6ab4544b15e9ad456e61037fbd5fa47dca17394b25ee6f6c70eca":
-      "DigiCert Yeti2021",
-    "2245450759552456963fa12ff1f76d86e0232663adc04b7f5dc6835c6ee20f02":
-      "DigiCert Yeti2022",
-    "35cf191bbfb16c57bf0fad4c6d42cbbbb627202651ea3fe12aefa803c33bd64c":
-      "DigiCert Yeti2023",
-    "717ea7420975be84a2723553f1777c26dd51af4e102144094d9019b462fb6668":
-      "GDCA 1",
-    "14308d90ccd030135005c01ca526d81e84e87624e39b6248e08f724aea3bb42a":
-      "GDCA 2",
-    c9cf890a21109c666cc17a3ed065c930d0e0135a9feba85af14210b8072421aa:
-      "GDCA CT #1",
-    "924a30f909336ff435d6993a10ac75a2c641728e7fc2d659ae6188ffad40ce01":
-      "GDCA CT #2",
-    fad4c97cc49ee2f8ac85c5ea5cea09d0220dbbf4e49c6b50662ff868f86b8c28:
-      "Google “Argon2017”",
-    a4501269055a15545e6211ab37bc103f62ae5576a45e4b1714453e1b22106a25:
-      "Google “Argon2018”",
-    "63f2dbcde83bcc2ccf0b728427576b33a48d61778fbd75a638b1c768544bd88d":
-      "Google “Argon2019”",
-    b21e05cc8ba2cd8a204e8766f92bb98a2520676bdafa70e7b249532def8b905e:
-      "Google “Argon2020”",
-    f65c942fd1773022145418083094568ee34d131933bfdf0c2f200bcc4ef164e3:
-      "Google “Argon2021”",
-    "2979bef09e393921f056739f63a577e5be577d9c600af8f94d5d265c255dc784":
-      "Google “Argon2022”",
-    "68f698f81f6482be3a8ceeb9281d4cfc71515d6793d444d10a67acbb4f4ffbc4":
-      "Google “Aviator”",
-    c3bf03a7e1ca8841c607bae3ff4270fca5ec45b186ebbe4e2cf3fc778630f5f6:
-      "Google “Crucible”",
-    "1d024b8eb1498b344dfd87ea3efc0996f7506f235d1d497061a4773c439c25fb":
-      "Google “Daedalus”",
-    "293c519654c83965baaa50fc5807d4b76fbf587a2972dca4c30cf4e54547f478":
-      "Google “Icarus”",
-    a4b90990b418581487bb13a2cc67700a3c359804f91bdfb8e377cd0ec80ddc10:
-      "Google “Pilot”",
-    ee4bbdb775ce60bae142691fabe19e66a30f7e5fb072d88300c47b897aa8fdcb:
-      "Google “Rocketeer”",
-    bbd9dfbc1f8a71b593942397aa927b473857950aab52e81a909664368e1ed185:
-      "Google “Skydiver”",
-    "52eb4b225ec896974850675f23e43bc1d021e3214ce52ecd5fa87c203cdfca03":
-      "Google “Solera2018”",
-    "0b760e9a8b9a682f88985b15e947501a56446bba8830785c3842994386450c00":
-      "Google “Solera2019”",
-    "1fc72ce5a1b799f400c359bff96ca3913548e8644220610952e9ba1774f7bac7":
-      "Google “Solera2020”",
-    a3c99845e80ab7ce00157b3742df0207dd272b2b602ecf98ee2c12db9c5ae7e7:
-      "Google “Solera2021”",
-    "697aafca1a6b536fae21205046debad7e0eaea13d2432e6e9d8fb379f2b9aaf3":
-      "Google “Solera2022”",
-    a899d8780c9290aaf462f31880ccfbd52451e970d0fbf591ef75b0d99b645681:
-      "Google “Submariner”",
-    b0cc83e5a5f97d6baf7c09cc284904872ac7e88b132c6350b7c6fd26e16c6c77:
-      "Google “Testtube”",
-    b10cd559a6d67846811f7df9a51532739ac48d703bea0323da5d38755bc0ad4e:
-      "Google “Xenon2018”",
-    "084114980071532c16190460bcfc47fdc2653afa292c72b37ff863ae29ccc9f0":
-      "Google “Xenon2019”",
-    "07b75c1be57d68fff1b0c61d2315c7bae6577c5794b76aeebc613a1a69d3a21c":
-      "Google “Xenon2020”",
-    "7d3ef2f88fff88556824c2c0ca9e5289792bc50e78097f2e6a9768997e22f0d7":
-      "Google “Xenon2021”",
-    "46a555eb75fa912030b5a28969f4f37d112c4174befd49b885abf2fc70fe6d47":
-      "Google “Xenon2022”",
-    "7461b4a09cfb3d41d75159575b2e7649a445a8d27709b0cc564a6482b7eb41a3":
-      "Izenpe",
-    "8941449c70742e06b9fc9ce7b116ba0024aa36d59af44f0204404f00f7ea8566":
-      "Izenpe “Argi”",
-    "296afa2d568bca0d2ea844956ae9721fc35fa355ecda99693aafd458a71aefdd":
-      "Let“s Encrypt ”Clicky”",
-    "537b69a3564335a9c04904e39593b2c298eb8d7a6e83023635c627248cd6b440":
-      "Nordu “flimsy”",
-    aae70b7f3cb8d566c86c2f16979c9f445f69ab0eb4535589b2f77a030104f3cd:
-      "Nordu “plausible”",
-    e0127629e90496564e3d0147984498aa48f8adb16600eb7902a1ef9909906273:
-      "PuChuangSiDa CT",
-    cf55e28923497c340d5206d05353aeb25834b52f1f8dc9526809f212efdd7ca6:
-      "SHECA CT 1",
-    "32dc59c2d4c41968d56e14bc61ac8f0e45db39faf3c155aa4252f5001fa0c623":
-      "SHECA CT 2",
-    db76fdadac65e7d09508886e2159bd8b90352f5fead3e3dc5e22eb350acc7b98:
-      "Sectigo (Comodo) “Dodo” CT",
-    "6f5376ac31f03119d89900a45115ff77151c11d902c10029068db2089a37d913":
-      "Sectigo (Comodo) “Mammoth” CT",
-    "5581d4c2169036014aea0b9b573c53f0c0e43878702508172fa3aa1d0713d30c":
-      "Sectigo (Comodo) “Sabre” CT",
-    "34bb6ad6c3df9c03eea8a499ff7891486c9d5e5cac92d01f7bfd1bce19db48ef":
-      "StartCom",
-    ddeb1d2b7a0d4fa6208b81ad8168707e2e8e9d01d55c888d3d11c4cdb6ecbecc:
-      "Symantec",
-    a7ce4a4e6207e0addee5fdaa4b1f86768767b5d002a55d47310e7e670a95eab2:
-      "Symantec Deneb",
-    "15970488d7b997a05beb52512adee8d2e8b4a3165264121a9fabfbd5f85ad93f":
-      "Symantec “Sirius”",
-    bc78e1dfc5f63c684649334da10fa15f0979692009c081b4f3f6917f3ed9b8a5:
-      "Symantec “Vega”",
-    b0b784bc81c0ddc47544e883f05985bb9077d134d8ab88b2b2e533980b8e508b:
-      "Up In The Air “Behind the Sofa”",
-    ac3b9aed7fa9674757159e6d7d575672f9d98100941e9bdeffeca1313b75782d: "Venafi",
-    "03019df3fd85a69a8ebd1facc6da9ba73e469774fe77f579fc5a08b8328c1d6b":
-      "Venafi Gen2 CT",
-    "41b2dc2e89e63ce4af1ba7bb29bf68c6dee6f9f1cc047e30dffae3b3ba259263":
-      "WoSign",
-    "63d0006026dde10bb0601f452446965ee2b6ea2cd4fbc95ac866a550af9075b7":
-      "WoSign 2",
-    "9e4ff73dc3ce220b69217c899e468076abf8d78636d5ccfc85a31a75628ba88b":
-      "WoSign CT #1",
-    "659b3350f43b12cc5ea5ab4ec765d3fde6c88243777778e72003f9eb2b8c3129":
-      "Let's Encrypt Oak 2019",
-    e712f2b0377e1a62fb8ec90c6184f1ea7b37cb561d11265bf3e0f34bf241546e:
-      "Let's Encrypt Oak 2020",
-    "9420bc1e8ed58d6c88731f828b222c0dd1da4d5e6c4f943d61db4e2f584da2c2":
-      "Let's Encrypt Oak 2021",
-    dfa55eab68824f1f6cadeeb85f4e3e5aeacda212a46a5e8e3b12c020445c2a73:
-      "Let's Encrypt Oak 2022",
-    b73efb24df9c4dba75f239c5ba58f46c5dfc42cf7a9f35c49e1d098125edb499:
-      "Let's Encrypt Oak 2023",
-    "849f5f7f58d2bf7b54ecbd74611cea45c49c98f1d6481bc6f69e8c174f24f3cf":
-      "Let's Encrypt Testflume 2019",
-    c63f2218c37d56a6aa06b596da8e53d4d7156d1e9bac8e44d2202de64d69d9dc:
-      "Let's Encrypt Testflume 2020",
-    "03edf1da9776b6f38c341e39ed9d707a7570369cf9844f327fe9e14138361b60":
-      "Let's Encrypt Testflume 2021",
-    "2327efda352510dbc019ef491ae3ff1cc5a479bce37878360ee318cffb64f8c8":
-      "Let's Encrypt Testflume 2022",
-    "5534b7ab5a6ac3a7cbeba65487b2a2d71b48f650fa17c5197c97a0cb2076f3c6":
-      "Let's Encrypt Testflume 2023",
-  };
+const ctLogNames = {
+  "9606c02c690033aa1d145f59c6e2648d0549f0df96aab8db915a70d8ecf390a5":
+    "Akamai CT",
+  "39376f545f7b4607f59742d768cd5d2437bf3473b6534a4834bcf72e681c83c9":
+    "Alpha CT",
+  a577ac9ced7548dd8f025b67a241089df86e0f476ec203c2ecbedb185f282638: "CNNIC CT",
+  cdb5179b7fc1c046feea31136a3f8f002e6182faf8896fecc8b2f5b5ab604900: "Certly.IO",
+  "1fbc36e002ede97f40199e86b3573b8a4217d80187746ad0da03a06054d20df4":
+    "Cloudflare “Nimbus2017”",
+  db74afeecb29ecb1feca3e716d2ce5b9aabb36f7847183c75d9d4f37b61fbf64:
+    "Cloudflare “Nimbus2018”",
+  "747eda8331ad331091219cce254f4270c2bffd5e422008c6373579e6107bcc56":
+    "Cloudflare “Nimbus2019”",
+  "5ea773f9df56c0e7b536487dd049e0327a919a0c84a112128418759681714558":
+    "Cloudflare “Nimbus2020”",
+  "4494652eb0eeceafc44007d8a8fe28c0dae682bed8cb31b53fd33396b5b681a8":
+    "Cloudflare “Nimbus2021”",
+  "41c8cab1df22464a10c6a13a0942875e4e318b1b03ebeb4bc768f090629606f6":
+    "Cloudflare “Nimbus2022”",
+  "7a328c54d8b72db620ea38e0521ee98416703213854d3bd22bc13a57a352eb52":
+    "Cloudflare “Nimbus2023”",
+  "6ff141b5647e4222f7ef052cefae7c21fd608e27d2af5a6e9f4b8a37d6633ee5":
+    "DigiCert Nessie2018",
+  fe446108b1d01ab78a62ccfeab6ab2b2babff3abdad80a4d8b30df2d0008830c:
+    "DigiCert Nessie2019",
+  c652a0ec48ceb3fcab170992c43a87413309e80065a26252401ba3362a17c565:
+    "DigiCert Nessie2020",
+  eec095ee8d72640f92e3c3b91bc712a3696a097b4b6a1a1438e647b2cbedc5f9:
+    "DigiCert Nessie2021",
+  "51a3b0f5fd01799c566db837788f0ca47acc1b27cbf79e88429a0dfed48b05e5":
+    "DigiCert Nessie2022",
+  b3737707e18450f86386d605a9dc11094a792db1670c0b87dcf0030e7936a59a:
+    "DigiCert Nessie2023",
+  "5614069a2fd7c2ecd3f5e1bd44b23ec74676b9bc99115cc0ef949855d689d0dd":
+    "DigiCert Server",
+  "8775bfe7597cf88c43995fbdf36eff568d475636ff4ab560c1b4eaff5ea0830f":
+    "DigiCert Server 2",
+  c1164ae0a772d2d4392dc80ac10770d4f0c49bde991a4840c1fa075164f63360:
+    "DigiCert Yeti2018",
+  e2694bae26e8e94009e8861bb63b83d43ee7fe7488fba48f2893019dddf1dbfe:
+    "DigiCert Yeti2019",
+  f095a459f200d18240102d2f93888ead4bfe1d47e399e1d034a6b0a8aa8eb273:
+    "DigiCert Yeti2020",
+  "5cdc4392fee6ab4544b15e9ad456e61037fbd5fa47dca17394b25ee6f6c70eca":
+    "DigiCert Yeti2021",
+  "2245450759552456963fa12ff1f76d86e0232663adc04b7f5dc6835c6ee20f02":
+    "DigiCert Yeti2022",
+  "35cf191bbfb16c57bf0fad4c6d42cbbbb627202651ea3fe12aefa803c33bd64c":
+    "DigiCert Yeti2023",
+  "717ea7420975be84a2723553f1777c26dd51af4e102144094d9019b462fb6668": "GDCA 1",
+  "14308d90ccd030135005c01ca526d81e84e87624e39b6248e08f724aea3bb42a": "GDCA 2",
+  c9cf890a21109c666cc17a3ed065c930d0e0135a9feba85af14210b8072421aa:
+    "GDCA CT #1",
+  "924a30f909336ff435d6993a10ac75a2c641728e7fc2d659ae6188ffad40ce01":
+    "GDCA CT #2",
+  fad4c97cc49ee2f8ac85c5ea5cea09d0220dbbf4e49c6b50662ff868f86b8c28:
+    "Google “Argon2017”",
+  a4501269055a15545e6211ab37bc103f62ae5576a45e4b1714453e1b22106a25:
+    "Google “Argon2018”",
+  "63f2dbcde83bcc2ccf0b728427576b33a48d61778fbd75a638b1c768544bd88d":
+    "Google “Argon2019”",
+  b21e05cc8ba2cd8a204e8766f92bb98a2520676bdafa70e7b249532def8b905e:
+    "Google “Argon2020”",
+  f65c942fd1773022145418083094568ee34d131933bfdf0c2f200bcc4ef164e3:
+    "Google “Argon2021”",
+  "2979bef09e393921f056739f63a577e5be577d9c600af8f94d5d265c255dc784":
+    "Google “Argon2022”",
+  "68f698f81f6482be3a8ceeb9281d4cfc71515d6793d444d10a67acbb4f4ffbc4":
+    "Google “Aviator”",
+  c3bf03a7e1ca8841c607bae3ff4270fca5ec45b186ebbe4e2cf3fc778630f5f6:
+    "Google “Crucible”",
+  "1d024b8eb1498b344dfd87ea3efc0996f7506f235d1d497061a4773c439c25fb":
+    "Google “Daedalus”",
+  "293c519654c83965baaa50fc5807d4b76fbf587a2972dca4c30cf4e54547f478":
+    "Google “Icarus”",
+  a4b90990b418581487bb13a2cc67700a3c359804f91bdfb8e377cd0ec80ddc10:
+    "Google “Pilot”",
+  ee4bbdb775ce60bae142691fabe19e66a30f7e5fb072d88300c47b897aa8fdcb:
+    "Google “Rocketeer”",
+  bbd9dfbc1f8a71b593942397aa927b473857950aab52e81a909664368e1ed185:
+    "Google “Skydiver”",
+  "52eb4b225ec896974850675f23e43bc1d021e3214ce52ecd5fa87c203cdfca03":
+    "Google “Solera2018”",
+  "0b760e9a8b9a682f88985b15e947501a56446bba8830785c3842994386450c00":
+    "Google “Solera2019”",
+  "1fc72ce5a1b799f400c359bff96ca3913548e8644220610952e9ba1774f7bac7":
+    "Google “Solera2020”",
+  a3c99845e80ab7ce00157b3742df0207dd272b2b602ecf98ee2c12db9c5ae7e7:
+    "Google “Solera2021”",
+  "697aafca1a6b536fae21205046debad7e0eaea13d2432e6e9d8fb379f2b9aaf3":
+    "Google “Solera2022”",
+  a899d8780c9290aaf462f31880ccfbd52451e970d0fbf591ef75b0d99b645681:
+    "Google “Submariner”",
+  b0cc83e5a5f97d6baf7c09cc284904872ac7e88b132c6350b7c6fd26e16c6c77:
+    "Google “Testtube”",
+  b10cd559a6d67846811f7df9a51532739ac48d703bea0323da5d38755bc0ad4e:
+    "Google “Xenon2018”",
+  "084114980071532c16190460bcfc47fdc2653afa292c72b37ff863ae29ccc9f0":
+    "Google “Xenon2019”",
+  "07b75c1be57d68fff1b0c61d2315c7bae6577c5794b76aeebc613a1a69d3a21c":
+    "Google “Xenon2020”",
+  "7d3ef2f88fff88556824c2c0ca9e5289792bc50e78097f2e6a9768997e22f0d7":
+    "Google “Xenon2021”",
+  "46a555eb75fa912030b5a28969f4f37d112c4174befd49b885abf2fc70fe6d47":
+    "Google “Xenon2022”",
+  "7461b4a09cfb3d41d75159575b2e7649a445a8d27709b0cc564a6482b7eb41a3": "Izenpe",
+  "8941449c70742e06b9fc9ce7b116ba0024aa36d59af44f0204404f00f7ea8566":
+    "Izenpe “Argi”",
+  "296afa2d568bca0d2ea844956ae9721fc35fa355ecda99693aafd458a71aefdd":
+    "Let“s Encrypt ”Clicky”",
+  "537b69a3564335a9c04904e39593b2c298eb8d7a6e83023635c627248cd6b440":
+    "Nordu “flimsy”",
+  aae70b7f3cb8d566c86c2f16979c9f445f69ab0eb4535589b2f77a030104f3cd:
+    "Nordu “plausible”",
+  e0127629e90496564e3d0147984498aa48f8adb16600eb7902a1ef9909906273:
+    "PuChuangSiDa CT",
+  cf55e28923497c340d5206d05353aeb25834b52f1f8dc9526809f212efdd7ca6:
+    "SHECA CT 1",
+  "32dc59c2d4c41968d56e14bc61ac8f0e45db39faf3c155aa4252f5001fa0c623":
+    "SHECA CT 2",
+  db76fdadac65e7d09508886e2159bd8b90352f5fead3e3dc5e22eb350acc7b98:
+    "Sectigo (Comodo) “Dodo” CT",
+  "6f5376ac31f03119d89900a45115ff77151c11d902c10029068db2089a37d913":
+    "Sectigo (Comodo) “Mammoth” CT",
+  "5581d4c2169036014aea0b9b573c53f0c0e43878702508172fa3aa1d0713d30c":
+    "Sectigo (Comodo) “Sabre” CT",
+  "34bb6ad6c3df9c03eea8a499ff7891486c9d5e5cac92d01f7bfd1bce19db48ef":
+    "StartCom",
+  ddeb1d2b7a0d4fa6208b81ad8168707e2e8e9d01d55c888d3d11c4cdb6ecbecc: "Symantec",
+  a7ce4a4e6207e0addee5fdaa4b1f86768767b5d002a55d47310e7e670a95eab2:
+    "Symantec Deneb",
+  "15970488d7b997a05beb52512adee8d2e8b4a3165264121a9fabfbd5f85ad93f":
+    "Symantec “Sirius”",
+  bc78e1dfc5f63c684649334da10fa15f0979692009c081b4f3f6917f3ed9b8a5:
+    "Symantec “Vega”",
+  b0b784bc81c0ddc47544e883f05985bb9077d134d8ab88b2b2e533980b8e508b:
+    "Up In The Air “Behind the Sofa”",
+  ac3b9aed7fa9674757159e6d7d575672f9d98100941e9bdeffeca1313b75782d: "Venafi",
+  "03019df3fd85a69a8ebd1facc6da9ba73e469774fe77f579fc5a08b8328c1d6b":
+    "Venafi Gen2 CT",
+  "41b2dc2e89e63ce4af1ba7bb29bf68c6dee6f9f1cc047e30dffae3b3ba259263": "WoSign",
+  "63d0006026dde10bb0601f452446965ee2b6ea2cd4fbc95ac866a550af9075b7":
+    "WoSign 2",
+  "9e4ff73dc3ce220b69217c899e468076abf8d78636d5ccfc85a31a75628ba88b":
+    "WoSign CT #1",
+  "659b3350f43b12cc5ea5ab4ec765d3fde6c88243777778e72003f9eb2b8c3129":
+    "Let's Encrypt Oak 2019",
+  e712f2b0377e1a62fb8ec90c6184f1ea7b37cb561d11265bf3e0f34bf241546e:
+    "Let's Encrypt Oak 2020",
+  "9420bc1e8ed58d6c88731f828b222c0dd1da4d5e6c4f943d61db4e2f584da2c2":
+    "Let's Encrypt Oak 2021",
+  dfa55eab68824f1f6cadeeb85f4e3e5aeacda212a46a5e8e3b12c020445c2a73:
+    "Let's Encrypt Oak 2022",
+  b73efb24df9c4dba75f239c5ba58f46c5dfc42cf7a9f35c49e1d098125edb499:
+    "Let's Encrypt Oak 2023",
+  "849f5f7f58d2bf7b54ecbd74611cea45c49c98f1d6481bc6f69e8c174f24f3cf":
+    "Let's Encrypt Testflume 2019",
+  c63f2218c37d56a6aa06b596da8e53d4d7156d1e9bac8e44d2202de64d69d9dc:
+    "Let's Encrypt Testflume 2020",
+  "03edf1da9776b6f38c341e39ed9d707a7570369cf9844f327fe9e14138361b60":
+    "Let's Encrypt Testflume 2021",
+  "2327efda352510dbc019ef491ae3ff1cc5a479bce37878360ee318cffb64f8c8":
+    "Let's Encrypt Testflume 2022",
+  "5534b7ab5a6ac3a7cbeba65487b2a2d71b48f650fa17c5197c97a0cb2076f3c6":
+    "Let's Encrypt Testflume 2023",
+};
 
-  const strings = {
-    ux: {
-      upload: "Upload Certificate",
+const strings = {
+  ux: {
+    upload: "Upload Certificate",
+  },
+
+  names: {
+    // Directory Pilot Attributes
+    "0.9.2342.19200300.100.1.1": {
+      short: "uid",
+      long: "User ID",
+    },
+    "0.9.2342.19200300.100.1.25": {
+      short: "dc",
+      long: "Domain Component",
     },
 
-    names: {
-      // Directory Pilot Attributes
-      "0.9.2342.19200300.100.1.1": {
-        short: "uid",
-        long: "User ID",
-      },
-      "0.9.2342.19200300.100.1.25": {
-        short: "dc",
-        long: "Domain Component",
-      },
-
-      // PKCS-9
-      "1.2.840.113549.1.9.1": {
-        short: "e",
-        long: "Email Address",
-      },
-
-      // Incorporated Locations
-      "1.3.6.1.4.1.311.60.2.1.1": {
-        short: undefined,
-        long: "Inc. Locality",
-      },
-      "1.3.6.1.4.1.311.60.2.1.2": {
-        short: undefined,
-        long: "Inc. State / Province",
-      },
-      "1.3.6.1.4.1.311.60.2.1.3": {
-        short: undefined,
-        long: "Inc. Country",
-      },
-
-      // microsoft cryptographic extensions
-      "1.3.6.1.4.1.311.21.7": {
-        name: {
-          short: "Certificate Template",
-          long: "Microsoft Certificate Template",
-        },
-      },
-      "1.3.6.1.4.1.311.21.10": {
-        name: {
-          short: "Certificate Policies",
-          long: "Microsoft Certificate Policies",
-        },
-      },
-
-      // certificate extensions
-      "1.3.6.1.4.1.11129.2.4.2": {
-        name: {
-          short: "Embedded SCTs",
-          long: "Embedded Signed Certificate Timestamps",
-        },
-      },
-      "1.3.6.1.5.5.7.1.1": {
-        name: {
-          short: undefined,
-          long: "Authority Information Access",
-        },
-      },
-      "1.3.6.1.5.5.7.1.24": {
-        name: {
-          short: "OCSP Stapling",
-          long: "Online Certificate Status Protocol Stapling",
-        },
-      },
+    // PKCS-9
+    "1.2.840.113549.1.9.1": {
+      short: "e",
+      long: "Email Address",
+    },
 
-      // X.500 attribute types
-      "2.5.4.1": {
-        short: undefined,
-        long: "Aliased Entry",
-      },
-      "2.5.4.2": {
-        short: undefined,
-        long: "Knowledge Information",
-      },
-      "2.5.4.3": {
-        short: "cn",
-        long: "Common Name",
-      },
-      "2.5.4.4": {
-        short: "sn",
-        long: "Surname",
-      },
-      "2.5.4.5": {
-        short: "serialNumber",
-        long: "Serial Number",
-      },
-      "2.5.4.6": {
-        short: "c",
-        long: "Country",
-      },
-      "2.5.4.7": {
-        short: "l",
-        long: "Locality",
-      },
-      "2.5.4.8": {
-        short: "s",
-        long: "State / Province",
-      },
-      "2.5.4.9": {
-        short: "street",
-        long: "Stress Address",
-      },
-      "2.5.4.10": {
-        short: "o",
-        long: "Organization",
-      },
-      "2.5.4.11": {
-        short: "ou",
-        long: "Organizational Unit",
-      },
-      "2.5.4.12": {
-        short: "t",
-        long: "Title",
-      },
-      "2.5.4.13": {
-        short: "description",
-        long: "Description",
-      },
-      "2.5.4.14": {
-        short: undefined,
-        long: "Search Guide",
-      },
-      "2.5.4.15": {
-        short: undefined,
-        long: "Business Category",
-      },
-      "2.5.4.16": {
-        short: undefined,
-        long: "Postal Address",
-      },
-      "2.5.4.17": {
-        short: "postalCode",
-        long: "Postal Code",
-      },
-      "2.5.4.18": {
-        short: "POBox",
-        long: "PO Box",
-      },
-      "2.5.4.19": {
-        short: undefined,
-        long: "Physical Delivery Office Name",
-      },
-      "2.5.4.20": {
-        short: "phone",
-        long: "Phone Number",
-      },
-      "2.5.4.21": {
-        short: undefined,
-        long: "Telex Number",
-      },
-      "2.5.4.22": {
-        short: undefined,
-        long: "Teletex Terminal Identifier",
-      },
-      "2.5.4.23": {
-        short: undefined,
-        long: "Fax Number",
-      },
-      "2.5.4.24": {
-        short: undefined,
-        long: "X.121 Address",
-      },
-      "2.5.4.25": {
-        short: undefined,
-        long: "International ISDN Number",
+    // Incorporated Locations
+    "1.3.6.1.4.1.311.60.2.1.1": {
+      short: undefined,
+      long: "Inc. Locality",
+    },
+    "1.3.6.1.4.1.311.60.2.1.2": {
+      short: undefined,
+      long: "Inc. State / Province",
+    },
+    "1.3.6.1.4.1.311.60.2.1.3": {
+      short: undefined,
+      long: "Inc. Country",
+    },
+
+    // microsoft cryptographic extensions
+    "1.3.6.1.4.1.311.21.7": {
+      name: {
+        short: "Certificate Template",
+        long: "Microsoft Certificate Template",
       },
-      "2.5.4.26": {
-        short: undefined,
-        long: "Registered Address",
-      },
-      "2.5.4.27": {
-        short: undefined,
-        long: "Destination Indicator",
-      },
-      "2.5.4.28": {
-        short: undefined,
-        long: "Preferred Delivery Method",
-      },
-      "2.5.4.29": {
-        short: undefined,
-        long: "Presentation Address",
-      },
-      "2.5.4.30": {
-        short: undefined,
-        long: "Supported Application Context",
-      },
-      "2.5.4.31": {
-        short: undefined,
-        long: "Member",
-      },
-      "2.5.4.32": {
-        short: undefined,
-        long: "Owner",
-      },
-      "2.5.4.33": {
-        short: undefined,
-        long: "Role Occupant",
-      },
-      "2.5.4.34": {
-        short: undefined,
-        long: "See Also",
-      },
-      "2.5.4.35": {
-        short: undefined,
-        long: "User Password",
-      },
-      "2.5.4.36": {
-        short: undefined,
-        long: "User Certificate",
-      },
-      "2.5.4.37": {
-        short: undefined,
-        long: "CA Certificate",
-      },
-      "2.5.4.38": {
-        short: undefined,
-        long: "Authority Revocation List",
-      },
-      "2.5.4.39": {
-        short: undefined,
-        long: "Certificate Revocation List",
-      },
-      "2.5.4.40": {
-        short: undefined,
-        long: "Cross-certificate Pair",
-      },
-      "2.5.4.41": {
-        short: undefined,
-        long: "Name",
-      },
-      "2.5.4.42": {
-        short: "g",
-        long: "Given Name",
-      },
-      "2.5.4.43": {
-        short: "i",
-        long: "Initials",
-      },
-      "2.5.4.44": {
-        short: undefined,
-        long: "Generation Qualifier",
-      },
-      "2.5.4.45": {
-        short: undefined,
-        long: "Unique Identifier",
-      },
-      "2.5.4.46": {
-        short: undefined,
-        long: "DN Qualifier",
-      },
-      "2.5.4.47": {
-        short: undefined,
-        long: "Enhanced Search Guide",
-      },
-      "2.5.4.48": {
-        short: undefined,
-        long: "Protocol Information",
-      },
-      "2.5.4.49": {
-        short: "dn",
-        long: "Distinguished Name",
-      },
-      "2.5.4.50": {
-        short: undefined,
-        long: "Unique Member",
-      },
-      "2.5.4.51": {
-        short: undefined,
-        long: "House Identifier",
-      },
-      "2.5.4.52": {
-        short: undefined,
-        long: "Supported Algorithms",
-      },
-      "2.5.4.53": {
-        short: undefined,
-        long: "Delta Revocation List",
-      },
-      "2.5.4.58": {
-        short: undefined,
-        long: "Attribute Certificate Attribute", // huh
-      },
-      "2.5.4.65": {
-        short: undefined,
-        long: "Pseudonym",
-      },
-
-      // extensions
-      "2.5.29.14": {
-        name: {
-          short: "Subject Key ID",
-          long: "Subject Key Identifier",
-        },
-      },
-      "2.5.29.15": {
-        name: {
-          short: undefined,
-          long: "Key Usages",
-        },
-      },
-      "2.5.29.17": {
-        name: {
-          short: "Subject Alt Names",
-          long: "Subject Alternative Names",
-        },
-      },
-      "2.5.29.19": {
-        name: {
-          short: undefined,
-          long: "Basic Constraints",
-        },
-      },
-      "2.5.29.31": {
-        name: {
-          short: "CRL Endpoints",
-          long: "Certificate Revocation List Endpoints",
-        },
-      },
-      "2.5.29.32": {
-        name: {
-          short: undefined,
-          long: "Certificate Policies",
-        },
-      },
-      "2.5.29.35": {
-        name: {
-          short: "Authority Key ID",
-          long: "Authority Key Identifier",
-        },
-      },
-      "2.5.29.37": {
-        name: {
-          short: undefined,
-          long: "Extended Key Usages",
-        },
+    },
+    "1.3.6.1.4.1.311.21.10": {
+      name: {
+        short: "Certificate Policies",
+        long: "Microsoft Certificate Policies",
       },
     },
 
-    keyUsages: [
-      "CRL Signing",
-      "Certificate Signing",
-      "Key Agreement",
-      "Data Encipherment",
-      "Key Encipherment",
-      "Non-Repudiation",
-      "Digital Signature",
-    ],
-
-    san: [
-      "Other Name",
-      "RFC 822 Name",
-      "DNS Name",
-      "X.400 Address",
-      "Directory Name",
-      "EDI Party Name",
-      "URI",
-      "IP Address",
-      "Registered ID",
-    ],
-
-    eKU: {
-      "1.3.6.1.4.1.311.10.3.1": "Certificate Trust List (CTL) Signing",
-      "1.3.6.1.4.1.311.10.3.2": "Timestamp Signing",
-      "1.3.6.1.4.1.311.10.3.4": "EFS Encryption",
-      "1.3.6.1.4.1.311.10.3.4.1": "EFS Recovery",
-      "1.3.6.1.4.1.311.10.3.5":
-        "Windows Hardware Quality Labs (WHQL) Cryptography",
-      "1.3.6.1.4.1.311.10.3.7": "Windows NT 5 Cryptography",
-      "1.3.6.1.4.1.311.10.3.8": "Windows NT Embedded Cryptography",
-      "1.3.6.1.4.1.311.10.3.10": "Qualified Subordination",
-      "1.3.6.1.4.1.311.10.3.11": "Escrowed Key Recovery",
-      "1.3.6.1.4.1.311.10.3.12": "Document Signing",
-      "1.3.6.1.4.1.311.10.5.1": "Digital Rights Management",
-      "1.3.6.1.4.1.311.10.6.1": "Key Pack Licenses",
-      "1.3.6.1.4.1.311.10.6.2": "License Server",
-      "1.3.6.1.4.1.311.20.2.1": "Enrollment Agent",
-      "1.3.6.1.4.1.311.20.2.2": "Smartcard Login",
-      "1.3.6.1.4.1.311.21.5": "Certificate Authority Private Key Archival",
-      "1.3.6.1.4.1.311.21.6": "Key Recovery Agent",
-      "1.3.6.1.4.1.311.21.19": "Directory Service Email Replication",
-      "1.3.6.1.5.5.7.3.1": "Server Authentication",
-      "1.3.6.1.5.5.7.3.2": "Client Authentication",
-      "1.3.6.1.5.5.7.3.3": "Code Signing",
-      "1.3.6.1.5.5.7.3.4": "E-mail Protection",
-      "1.3.6.1.5.5.7.3.5": "IPsec End System",
-      "1.3.6.1.5.5.7.3.6": "IPsec Tunnel",
-      "1.3.6.1.5.5.7.3.7": "IPSec User",
-      "1.3.6.1.5.5.7.3.8": "Timestamping",
-      "1.3.6.1.5.5.7.3.9": "OCSP Signing",
-      "1.3.6.1.5.5.8.2.2": "Internet Key Exchange (IKE)",
+    // certificate extensions
+    "1.3.6.1.4.1.11129.2.4.2": {
+      name: {
+        short: "Embedded SCTs",
+        long: "Embedded Signed Certificate Timestamps",
+      },
     },
-
-    signature: {
-      "1.2.840.113549.1.1.4": "MD5 with RSA Encryption",
-      "1.2.840.113549.1.1.5": "SHA-1 with RSA Encryption",
-      "1.2.840.113549.1.1.11": "SHA-256 with RSA Encryption",
-      "1.2.840.113549.1.1.12": "SHA-384 with RSA Encryption",
-      "1.2.840.113549.1.1.13": "SHA-512 with RSA Encryption",
-      "1.2.840.10040.4.3": "DSA with SHA-1",
-      "2.16.840.1.101.3.4.3.2": "DSA with SHA-256",
-      "1.2.840.10045.4.1": "ECDSA with SHA-1",
-      "1.2.840.10045.4.3.2": "ECDSA with SHA-256",
-      "1.2.840.10045.4.3.3": "ECDSA with SHA-384",
-      "1.2.840.10045.4.3.4": "ECDSA with SHA-512",
-    },
-
-    aia: {
-      "1.3.6.1.5.5.7.48.1": "Online Certificate Status Protocol (OCSP)",
-      "1.3.6.1.5.5.7.48.2": "CA Issuers",
-    },
-
-    // this includes qualifiers as well
-    cps: {
-      "1.3.6.1.4.1": {
-        name: "Statement Identifier",
-        value: undefined,
-      },
-      "1.3.6.1.5.5.7.2.1": {
-        name: "Practices Statement",
-        value: undefined,
+    "1.3.6.1.5.5.7.1.1": {
+      name: {
+        short: undefined,
+        long: "Authority Information Access",
       },
-      "1.3.6.1.5.5.7.2.2": {
-        name: "User Notice",
-        value: undefined,
-      },
-      "2.16.840": {
-        name: "ANSI Organizational Identifier",
-        value: undefined,
-      },
-      "2.23.140.1.1": {
-        name: "Certificate Type",
-        value: "Extended Validation",
-      },
-      "2.23.140.1.2.1": {
-        name: "Certificate Type",
-        value: "Domain Validation",
-      },
-      "2.23.140.1.2.2": {
-        name: "Certificate Type",
-        value: "Organization Validation",
-      },
-      "2.23.140.1.2.3": {
-        name: "Certificate Type",
-        value: "Individual Validation",
-      },
-      "2.23.140.1.3": {
-        name: "Certificate Type",
-        value: "Extended Validation (Code Signing)",
-      },
-      "2.23.140.1.31": {
-        name: "Certificate Type",
-        value: ".onion Extended Validation",
-      },
-      "2.23.140.2.1": {
-        name: "Certificate Type",
-        value: "Test Certificate",
+    },
+    "1.3.6.1.5.5.7.1.24": {
+      name: {
+        short: "OCSP Stapling",
+        long: "Online Certificate Status Protocol Stapling",
       },
     },
 
-    microsoftCertificateTypes: {
-      Administrator: "Administrator",
-      CA: "Root Certification Authority",
-      CAExchange: "CA Exchange",
-      CEPEncryption: "CEP Encryption",
-      CertificateRequestAgent: "Certificate Request Agent",
-      ClientAuth: "Authenticated Session",
-      CodeSigning: "Code Signing",
-      CrossCA: "Cross Certification Authority",
-      CTLSigning: "Trust List Signing",
-      DirectoryEmailReplication: "Directory Email Replication",
-      DomainController: "Domain Controller",
-      DomainControllerAuthentication: "Domain Controller Authentication",
-      EFS: "Basic EFS",
-      EFSRecovery: "EFS Recovery Agent",
-      EnrollmentAgent: "Enrollment Agent",
-      EnrollmentAgentOffline: "Exchange Enrollment Agent (Offline request)",
-      ExchangeUser: "Exchange User",
-      ExchangeUserSignature: "Exchange Signature Only",
-      IPSECIntermediateOffline: "IPSec (Offline request)",
-      IPSECIntermediateOnline: "IPSEC",
-      KerberosAuthentication: "Kerberos Authentication",
-      KeyRecoveryAgent: "Key Recovery Agent",
-      Machine: "Computer",
-      MachineEnrollmentAgent: "Enrollment Agent (Computer)",
-      OCSPResponseSigning: "OCSP Response Signing",
-      OfflineRouter: "Router (Offline request)",
-      RASAndIASServer: "RAS and IAS Server",
-      SmartcardLogon: "Smartcard Logon",
-      SmartcardUser: "Smartcard User",
-      SubCA: "Subordinate Certification Authority",
-      User: "User",
-      UserSignature: "User Signature Only",
-      WebServer: "Web Server",
-      Workstation: "Workstation Authentication",
+    // X.500 attribute types
+    "2.5.4.1": {
+      short: undefined,
+      long: "Aliased Entry",
+    },
+    "2.5.4.2": {
+      short: undefined,
+      long: "Knowledge Information",
+    },
+    "2.5.4.3": {
+      short: "cn",
+      long: "Common Name",
+    },
+    "2.5.4.4": {
+      short: "sn",
+      long: "Surname",
+    },
+    "2.5.4.5": {
+      short: "serialNumber",
+      long: "Serial Number",
+    },
+    "2.5.4.6": {
+      short: "c",
+      long: "Country",
+    },
+    "2.5.4.7": {
+      short: "l",
+      long: "Locality",
+    },
+    "2.5.4.8": {
+      short: "s",
+      long: "State / Province",
+    },
+    "2.5.4.9": {
+      short: "street",
+      long: "Stress Address",
+    },
+    "2.5.4.10": {
+      short: "o",
+      long: "Organization",
+    },
+    "2.5.4.11": {
+      short: "ou",
+      long: "Organizational Unit",
+    },
+    "2.5.4.12": {
+      short: "t",
+      long: "Title",
+    },
+    "2.5.4.13": {
+      short: "description",
+      long: "Description",
+    },
+    "2.5.4.14": {
+      short: undefined,
+      long: "Search Guide",
+    },
+    "2.5.4.15": {
+      short: undefined,
+      long: "Business Category",
+    },
+    "2.5.4.16": {
+      short: undefined,
+      long: "Postal Address",
+    },
+    "2.5.4.17": {
+      short: "postalCode",
+      long: "Postal Code",
+    },
+    "2.5.4.18": {
+      short: "POBox",
+      long: "PO Box",
+    },
+    "2.5.4.19": {
+      short: undefined,
+      long: "Physical Delivery Office Name",
+    },
+    "2.5.4.20": {
+      short: "phone",
+      long: "Phone Number",
+    },
+    "2.5.4.21": {
+      short: undefined,
+      long: "Telex Number",
+    },
+    "2.5.4.22": {
+      short: undefined,
+      long: "Teletex Terminal Identifier",
+    },
+    "2.5.4.23": {
+      short: undefined,
+      long: "Fax Number",
+    },
+    "2.5.4.24": {
+      short: undefined,
+      long: "X.121 Address",
+    },
+    "2.5.4.25": {
+      short: undefined,
+      long: "International ISDN Number",
+    },
+    "2.5.4.26": {
+      short: undefined,
+      long: "Registered Address",
+    },
+    "2.5.4.27": {
+      short: undefined,
+      long: "Destination Indicator",
+    },
+    "2.5.4.28": {
+      short: undefined,
+      long: "Preferred Delivery Method",
+    },
+    "2.5.4.29": {
+      short: undefined,
+      long: "Presentation Address",
+    },
+    "2.5.4.30": {
+      short: undefined,
+      long: "Supported Application Context",
+    },
+    "2.5.4.31": {
+      short: undefined,
+      long: "Member",
+    },
+    "2.5.4.32": {
+      short: undefined,
+      long: "Owner",
+    },
+    "2.5.4.33": {
+      short: undefined,
+      long: "Role Occupant",
+    },
+    "2.5.4.34": {
+      short: undefined,
+      long: "See Also",
+    },
+    "2.5.4.35": {
+      short: undefined,
+      long: "User Password",
+    },
+    "2.5.4.36": {
+      short: undefined,
+      long: "User Certificate",
+    },
+    "2.5.4.37": {
+      short: undefined,
+      long: "CA Certificate",
+    },
+    "2.5.4.38": {
+      short: undefined,
+      long: "Authority Revocation List",
+    },
+    "2.5.4.39": {
+      short: undefined,
+      long: "Certificate Revocation List",
+    },
+    "2.5.4.40": {
+      short: undefined,
+      long: "Cross-certificate Pair",
+    },
+    "2.5.4.41": {
+      short: undefined,
+      long: "Name",
+    },
+    "2.5.4.42": {
+      short: "g",
+      long: "Given Name",
+    },
+    "2.5.4.43": {
+      short: "i",
+      long: "Initials",
+    },
+    "2.5.4.44": {
+      short: undefined,
+      long: "Generation Qualifier",
+    },
+    "2.5.4.45": {
+      short: undefined,
+      long: "Unique Identifier",
+    },
+    "2.5.4.46": {
+      short: undefined,
+      long: "DN Qualifier",
+    },
+    "2.5.4.47": {
+      short: undefined,
+      long: "Enhanced Search Guide",
+    },
+    "2.5.4.48": {
+      short: undefined,
+      long: "Protocol Information",
+    },
+    "2.5.4.49": {
+      short: "dn",
+      long: "Distinguished Name",
+    },
+    "2.5.4.50": {
+      short: undefined,
+      long: "Unique Member",
+    },
+    "2.5.4.51": {
+      short: undefined,
+      long: "House Identifier",
+    },
+    "2.5.4.52": {
+      short: undefined,
+      long: "Supported Algorithms",
+    },
+    "2.5.4.53": {
+      short: undefined,
+      long: "Delta Revocation List",
+    },
+    "2.5.4.58": {
+      short: undefined,
+      long: "Attribute Certificate Attribute", // huh
+    },
+    "2.5.4.65": {
+      short: undefined,
+      long: "Pseudonym",
+    },
+
+    // extensions
+    "2.5.29.14": {
+      name: {
+        short: "Subject Key ID",
+        long: "Subject Key Identifier",
+      },
+    },
+    "2.5.29.15": {
+      name: {
+        short: undefined,
+        long: "Key Usages",
+      },
     },
-  };
+    "2.5.29.17": {
+      name: {
+        short: "Subject Alt Names",
+        long: "Subject Alternative Names",
+      },
+    },
+    "2.5.29.19": {
+      name: {
+        short: undefined,
+        long: "Basic Constraints",
+      },
+    },
+    "2.5.29.31": {
+      name: {
+        short: "CRL Endpoints",
+        long: "Certificate Revocation List Endpoints",
+      },
+    },
+    "2.5.29.32": {
+      name: {
+        short: undefined,
+        long: "Certificate Policies",
+      },
+    },
+    "2.5.29.35": {
+      name: {
+        short: "Authority Key ID",
+        long: "Authority Key Identifier",
+      },
+    },
+    "2.5.29.37": {
+      name: {
+        short: undefined,
+        long: "Extended Key Usages",
+      },
+    },
+  },
 
-  const b64urltodec = b64 => {
-    return new Integer({
-      valueHex: stringToArrayBuffer(fromBase64("AQAB", true, true)),
-    }).valueBlock._valueDec;
-  };
+  keyUsages: [
+    "CRL Signing",
+    "Certificate Signing",
+    "Key Agreement",
+    "Data Encipherment",
+    "Key Encipherment",
+    "Non-Repudiation",
+    "Digital Signature",
+  ],
 
-  const b64urltohex = b64 => {
-    const hexBuffer = new Integer({
-      valueHex: stringToArrayBuffer(fromBase64(b64, true, true)),
-    }).valueBlock._valueHex;
-    const hexArray = Array.from(new Uint8Array(hexBuffer));
+  san: [
+    "Other Name",
+    "RFC 822 Name",
+    "DNS Name",
+    "X.400 Address",
+    "Directory Name",
+    "EDI Party Name",
+    "URI",
+    "IP Address",
+    "Registered ID",
+  ],
 
-    return hexArray.map(b => ("00" + b.toString(16)).slice(-2));
-  };
+  eKU: {
+    "1.3.6.1.4.1.311.10.3.1": "Certificate Trust List (CTL) Signing",
+    "1.3.6.1.4.1.311.10.3.2": "Timestamp Signing",
+    "1.3.6.1.4.1.311.10.3.4": "EFS Encryption",
+    "1.3.6.1.4.1.311.10.3.4.1": "EFS Recovery",
+    "1.3.6.1.4.1.311.10.3.5":
+      "Windows Hardware Quality Labs (WHQL) Cryptography",
+    "1.3.6.1.4.1.311.10.3.7": "Windows NT 5 Cryptography",
+    "1.3.6.1.4.1.311.10.3.8": "Windows NT Embedded Cryptography",
+    "1.3.6.1.4.1.311.10.3.10": "Qualified Subordination",
+    "1.3.6.1.4.1.311.10.3.11": "Escrowed Key Recovery",
+    "1.3.6.1.4.1.311.10.3.12": "Document Signing",
+    "1.3.6.1.4.1.311.10.5.1": "Digital Rights Management",
+    "1.3.6.1.4.1.311.10.6.1": "Key Pack Licenses",
+    "1.3.6.1.4.1.311.10.6.2": "License Server",
+    "1.3.6.1.4.1.311.20.2.1": "Enrollment Agent",
+    "1.3.6.1.4.1.311.20.2.2": "Smartcard Login",
+    "1.3.6.1.4.1.311.21.5": "Certificate Authority Private Key Archival",
+    "1.3.6.1.4.1.311.21.6": "Key Recovery Agent",
+    "1.3.6.1.4.1.311.21.19": "Directory Service Email Replication",
+    "1.3.6.1.5.5.7.3.1": "Server Authentication",
+    "1.3.6.1.5.5.7.3.2": "Client Authentication",
+    "1.3.6.1.5.5.7.3.3": "Code Signing",
+    "1.3.6.1.5.5.7.3.4": "E-mail Protection",
+    "1.3.6.1.5.5.7.3.5": "IPsec End System",
+    "1.3.6.1.5.5.7.3.6": "IPsec Tunnel",
+    "1.3.6.1.5.5.7.3.7": "IPSec User",
+    "1.3.6.1.5.5.7.3.8": "Timestamping",
+    "1.3.6.1.5.5.7.3.9": "OCSP Signing",
+    "1.3.6.1.5.5.8.2.2": "Internet Key Exchange (IKE)",
+  },
+
+  signature: {
+    "1.2.840.113549.1.1.4": "MD5 with RSA Encryption",
+    "1.2.840.113549.1.1.5": "SHA-1 with RSA Encryption",
+    "1.2.840.113549.1.1.11": "SHA-256 with RSA Encryption",
+    "1.2.840.113549.1.1.12": "SHA-384 with RSA Encryption",
+    "1.2.840.113549.1.1.13": "SHA-512 with RSA Encryption",
+    "1.2.840.10040.4.3": "DSA with SHA-1",
+    "2.16.840.1.101.3.4.3.2": "DSA with SHA-256",
+    "1.2.840.10045.4.1": "ECDSA with SHA-1",
+    "1.2.840.10045.4.3.2": "ECDSA with SHA-256",
+    "1.2.840.10045.4.3.3": "ECDSA with SHA-384",
+    "1.2.840.10045.4.3.4": "ECDSA with SHA-512",
+  },
+
+  aia: {
+    "1.3.6.1.5.5.7.48.1": "Online Certificate Status Protocol (OCSP)",
+    "1.3.6.1.5.5.7.48.2": "CA Issuers",
+  },
 
-  // this particular prototype override makes it easy to chain down complex objects
-  const getObjPath = (obj, path) => {
-    path = path.split(".");
-    for (let i = 0, len = path.length; i < len; i++) {
-      if (Array.isArray(obj[path[i]])) {
-        obj = obj[path[i]][path[i + 1]];
-        i++;
-      } else {
-        obj = obj[path[i]];
-      }
+  // this includes qualifiers as well
+  cps: {
+    "1.3.6.1.4.1": {
+      name: "Statement Identifier",
+      value: undefined,
+    },
+    "1.3.6.1.5.5.7.2.1": {
+      name: "Practices Statement",
+      value: undefined,
+    },
+    "1.3.6.1.5.5.7.2.2": {
+      name: "User Notice",
+      value: undefined,
+    },
+    "2.16.840": {
+      name: "ANSI Organizational Identifier",
+      value: undefined,
+    },
+    "2.23.140.1.1": {
+      name: "Certificate Type",
+      value: "Extended Validation",
+    },
+    "2.23.140.1.2.1": {
+      name: "Certificate Type",
+      value: "Domain Validation",
+    },
+    "2.23.140.1.2.2": {
+      name: "Certificate Type",
+      value: "Organization Validation",
+    },
+    "2.23.140.1.2.3": {
+      name: "Certificate Type",
+      value: "Individual Validation",
+    },
+    "2.23.140.1.3": {
+      name: "Certificate Type",
+      value: "Extended Validation (Code Signing)",
+    },
+    "2.23.140.1.31": {
+      name: "Certificate Type",
+      value: ".onion Extended Validation",
+    },
+    "2.23.140.2.1": {
+      name: "Certificate Type",
+      value: "Test Certificate",
+    },
+  },
+
+  microsoftCertificateTypes: {
+    Administrator: "Administrator",
+    CA: "Root Certification Authority",
+    CAExchange: "CA Exchange",
+    CEPEncryption: "CEP Encryption",
+    CertificateRequestAgent: "Certificate Request Agent",
+    ClientAuth: "Authenticated Session",
+    CodeSigning: "Code Signing",
+    CrossCA: "Cross Certification Authority",
+    CTLSigning: "Trust List Signing",
+    DirectoryEmailReplication: "Directory Email Replication",
+    DomainController: "Domain Controller",
+    DomainControllerAuthentication: "Domain Controller Authentication",
+    EFS: "Basic EFS",
+    EFSRecovery: "EFS Recovery Agent",
+    EnrollmentAgent: "Enrollment Agent",
+    EnrollmentAgentOffline: "Exchange Enrollment Agent (Offline request)",
+    ExchangeUser: "Exchange User",
+    ExchangeUserSignature: "Exchange Signature Only",
+    IPSECIntermediateOffline: "IPSec (Offline request)",
+    IPSECIntermediateOnline: "IPSEC",
+    KerberosAuthentication: "Kerberos Authentication",
+    KeyRecoveryAgent: "Key Recovery Agent",
+    Machine: "Computer",
+    MachineEnrollmentAgent: "Enrollment Agent (Computer)",
+    OCSPResponseSigning: "OCSP Response Signing",
+    OfflineRouter: "Router (Offline request)",
+    RASAndIASServer: "RAS and IAS Server",
+    SmartcardLogon: "Smartcard Logon",
+    SmartcardUser: "Smartcard User",
+    SubCA: "Subordinate Certification Authority",
+    User: "User",
+    UserSignature: "User Signature Only",
+    WebServer: "Web Server",
+    Workstation: "Workstation Authentication",
+  },
+};
+
+function stringToArrayBuffer(string) {
+  let result = new Uint8Array(string.length);
+  for (let i = 0; i < string.length; i++) {
+    result[i] = string.charCodeAt(i);
+  }
+  return result;
+}
+
+// this particular prototype override makes it easy to chain down complex objects
+const getObjPath = (obj, path) => {
+  path = path.split(".");
+  for (let i = 0, len = path.length; i < len; i++) {
+    if (Array.isArray(obj[path[i]])) {
+      obj = obj[path[i]][path[i + 1]];
+      i++;
+    } else {
+      obj = obj[path[i]];
     }
-    return obj;
-  };
+  }
+  return obj;
+};
+
+const arrayBufferToHex = arrayBuffer => {
+  const array = Array.from(new Uint8Array(arrayBuffer));
 
-  const hash = async (algo, buffer) => {
-    const hashBuffer = await crypto.subtle.digest(algo, buffer);
-    const hashArray = Array.from(new Uint8Array(hashBuffer));
+  return array
+    .map(b => ("00" + b.toString(16)).slice(-2))
+    .join(":")
+    .toUpperCase();
+};
 
-    return hashArray
-      .map(b => ("00" + b.toString(16)).slice(-2))
+const hash = async (algo, buffer) => {
+  const hashBuffer = await crypto.subtle.digest(algo, buffer);
+  return arrayBufferToHex(hashBuffer);
+};
+
+const hashify = rawHash => {
+  if (typeof rawHash === "string") {
+    return rawHash
+      .match(/.{2}/g)
       .join(":")
       .toUpperCase();
-  };
-
-  const hashify = hash => {
-    if (typeof hash === "string") {
-      return hash
-        .match(/.{2}/g)
-        .join(":")
-        .toUpperCase();
-    }
-    return hash.join(":").toUpperCase();
-  };
+  }
+  if (rawHash instanceof ArrayBuffer) {
+    return arrayBufferToHex(rawHash);
+  }
+  return rawHash.join(":").toUpperCase();
+};
 
-  const pemToDER = pem => {
-    return stringToArrayBuffer(atob(pem));
-  };
-
-  return { parse, pemToDER };
-}
-
-// This must be removed when all consumer is converted to ES module.
-// eslint-disable-next-line mozilla/reject-globalThis-modification
-globalThis.certDecoderInitializer = certDecoderInitializer;
-/* eslint-disable-next-line no-unused-vars */
-var EXPORTED_SYMBOLS = ["certDecoderInitializer"];
+export const pemToDER = pem => {
+  return stringToArrayBuffer(atob(pem));
+};
--- a/toolkit/components/certviewer/content/certviewer.html
+++ b/toolkit/components/certviewer/content/certviewer.html
@@ -6,19 +6,19 @@
 
 <html>
   <head>
     <meta name="viewport" content="width=device-width" />
     <meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'" />
     <meta name="color-scheme" content="light dark" />
     <link rel="localization" href="toolkit/about/certviewer.ftl">
     <link rel="localization" href="branding/brand.ftl">
-    <script type="module" src="chrome://global/content/certviewer/certviewer.js"></script>
-    <script type="module" src="chrome://global/content/certviewer/components/certificate-section.js"></script>
-    <script type="module" src="chrome://global/content/certviewer/components/about-certificate-section.js"></script>
+    <script type="module" src="chrome://global/content/certviewer/certviewer.mjs"></script>
+    <script type="module" src="chrome://global/content/certviewer/components/certificate-section.mjs"></script>
+    <script type="module" src="chrome://global/content/certviewer/components/about-certificate-section.mjs"></script>
     <link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
     <link rel="stylesheet" href="chrome://global/content/certviewer/certviewer.css">
     <title id="certTitle">about:certificate</title>
   </head>
   <body>
     <template id="certificate-section-template" class="section">
       <link rel="stylesheet" href="chrome://global/content/certviewer/components/certificate-section.css">
       <h1 class="title"></h1>
rename from toolkit/components/certviewer/content/certviewer.js
rename to toolkit/components/certviewer/content/certviewer.mjs
--- a/toolkit/components/certviewer/content/certviewer.js
+++ b/toolkit/components/certviewer/content/certviewer.mjs
@@ -1,34 +1,19 @@
 /* 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 */
 
-"use strict";
-
-import { normalizeToKebabCase } from "./components/utils.js";
-
-import "chrome://global/content/certviewer/pvutils_bundle.jsm";
-import "chrome://global/content/certviewer/asn1js_bundle.jsm";
-import "chrome://global/content/certviewer/pkijs_bundle.jsm";
-import "chrome://global/content/certviewer/certDecoder.jsm";
-
-const { Integer, fromBER } = globalThis.asn1js.asn1js;
-const { Certificate } = globalThis.pkijs.pkijs;
-const { fromBase64, stringToArrayBuffer } = globalThis.pvutils.pvutils;
-const { parse, pemToDER } = globalThis.certDecoderInitializer(
-  Integer,
-  fromBER,
-  Certificate,
-  fromBase64,
-  stringToArrayBuffer,
-  crypto
-);
+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;
   }
@@ -373,25 +358,28 @@ export const adjustCertInformation = cer
           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.name && qualifier.id) {
+              if (qualifier.qualifierName && qualifier.qualifierId) {
                 items.push(
                   createEntryItem(
                     "qualifier",
-                    qualifier.name + " ( " + qualifier.id + " )"
+                    qualifier.qualifierName +
+                      " ( " +
+                      qualifier.qualifierId +
+                      " )"
                   )
                 );
               }
-              items.push(createEntryItem("value", qualifier.value));
+              items.push(createEntryItem("value", qualifier.qualifierValue));
             });
           }
         });
       }
       return items.filter(elem => elem != null);
     },
     certItems,
     "certificate-policies",
rename from toolkit/components/certviewer/content/components/about-certificate-items.js
rename to toolkit/components/certviewer/content/components/about-certificate-items.mjs
--- a/toolkit/components/certviewer/content/components/about-certificate-items.js
+++ b/toolkit/components/certviewer/content/components/about-certificate-items.mjs
@@ -1,15 +1,15 @@
 /* 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 { ListItem } from "./list-item.js";
+import { ListItem } from "./list-item.mjs";
 
 export class AboutCertificateItems extends HTMLElement {
   constructor(id, data) {
     super();
     this.id = id;
     this.data = data;
   }
 
rename from toolkit/components/certviewer/content/components/about-certificate-section.js
rename to toolkit/components/certviewer/content/components/about-certificate-section.mjs
--- a/toolkit/components/certviewer/content/components/about-certificate-section.js
+++ b/toolkit/components/certviewer/content/components/about-certificate-section.mjs
@@ -1,16 +1,16 @@
 /* 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 { InfoGroupContainer } from "./info-group-container.js";
-import { CertificateTabsSection } from "./certificate-tabs-section.js";
+import { InfoGroupContainer } from "./info-group-container.mjs";
+import { CertificateTabsSection } from "./certificate-tabs-section.mjs";
 
 const TYPE_CA = 1;
 const TYPE_USER = 2;
 const TYPE_EMAIL = 4;
 const TYPE_SERVER = 8;
 
 export class AboutCertificateSection extends HTMLElement {
   constructor() {
rename from toolkit/components/certviewer/content/components/certificate-section.js
rename to toolkit/components/certviewer/content/components/certificate-section.mjs
--- a/toolkit/components/certviewer/content/components/certificate-section.js
+++ b/toolkit/components/certviewer/content/components/certificate-section.mjs
@@ -1,15 +1,15 @@
 /* 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/. */
 
-import { ErrorSection } from "./error-section.js";
-import { InfoGroupContainer } from "./info-group-container.js";
-import { CertificateTabsSection } from "./certificate-tabs-section.js";
+import { ErrorSection } from "./error-section.mjs";
+import { InfoGroupContainer } from "./info-group-container.mjs";
+import { CertificateTabsSection } from "./certificate-tabs-section.mjs";
 
 class CertificateSection extends HTMLElement {
   constructor(certs, error) {
     super();
     this.certs = certs;
     this.error = error;
   }
 
rename from toolkit/components/certviewer/content/components/certificate-tabs-section.js
rename to toolkit/components/certviewer/content/components/certificate-tabs-section.mjs
--- a/toolkit/components/certviewer/content/components/certificate-tabs-section.js
+++ b/toolkit/components/certviewer/content/components/certificate-tabs-section.mjs
@@ -1,14 +1,14 @@
 /* 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/. */
 
-import { normalizeToKebabCase } from "./utils.js";
-import { updateSelectedItem } from "../certviewer.js";
+import { normalizeToKebabCase } from "./utils.mjs";
+import { updateSelectedItem } from "../certviewer.mjs";
 
 export class CertificateTabsSection extends HTMLElement {
   constructor(isAboutCertificate) {
     super();
     this.isAboutCertificate = isAboutCertificate || false;
     this.connectedCallback();
   }
 
@@ -54,17 +54,17 @@ export class CertificateTabsSection exte
       // Not needed in the standalone version of about:certificate
       // because the tab text there should be localized.
       tab.dir = "auto";
     }
     this.tabsElement.appendChild(tab);
 
     // If it is the first tab, allow it to be tabbable by the user.
     // If it isn't the first tab, do not allow tab functionality,
-    // as arrow functionality is implemented in certviewer.js.
+    // as arrow functionality is implemented in certviewer.mjs.
     if (i === 0) {
       tab.classList.add("selected");
       tab.setAttribute("tabindex", 0);
     } else {
       tab.setAttribute("tabindex", -1);
     }
   }
 
rename from toolkit/components/certviewer/content/components/error-section.js
rename to toolkit/components/certviewer/content/components/error-section.mjs
rename from toolkit/components/certviewer/content/components/info-group-container.js
rename to toolkit/components/certviewer/content/components/info-group-container.mjs
--- a/toolkit/components/certviewer/content/components/info-group-container.js
+++ b/toolkit/components/certviewer/content/components/info-group-container.mjs
@@ -1,14 +1,14 @@
 /* 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/. */
 
-import { InfoGroup } from "./info-group.js";
-import { AboutCertificateItems } from "./about-certificate-items.js";
+import { InfoGroup } from "./info-group.mjs";
+import { AboutCertificateItems } from "./about-certificate-items.mjs";
 
 export class InfoGroupContainer extends HTMLElement {
   constructor(isAboutCertificate = false) {
     super();
     this.infoGroupsContainers = [];
     this.isAboutCertificate = isAboutCertificate;
   }
 
rename from toolkit/components/certviewer/content/components/info-group.js
rename to toolkit/components/certviewer/content/components/info-group.mjs
--- a/toolkit/components/certviewer/content/components/info-group.js
+++ b/toolkit/components/certviewer/content/components/info-group.mjs
@@ -1,15 +1,15 @@
 /* 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/. */
 
-import { InfoItem } from "./info-item.js";
-import { updateSelectedItem } from "../certviewer.js";
-import { normalizeToKebabCase } from "./utils.js";
+import { InfoItem } from "./info-item.mjs";
+import { updateSelectedItem } from "../certviewer.mjs";
+import { normalizeToKebabCase } from "./utils.mjs";
 
 export class InfoGroup extends HTMLElement {
   constructor(item, final) {
     super();
     this.item = item;
     this.final = final;
   }
 
rename from toolkit/components/certviewer/content/components/info-item.js
rename to toolkit/components/certviewer/content/components/info-item.mjs
--- a/toolkit/components/certviewer/content/components/info-item.js
+++ b/toolkit/components/certviewer/content/components/info-item.mjs
@@ -1,13 +1,13 @@
 /* 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/. */
 
-import { b64ToPEM, normalizeToKebabCase } from "./utils.js";
+import { b64ToPEM, normalizeToKebabCase } from "./utils.mjs";
 
 export class InfoItem extends HTMLElement {
   constructor(item) {
     super();
     this.item = item;
   }
 
   connectedCallback() {
rename from toolkit/components/certviewer/content/components/list-item.js
rename to toolkit/components/certviewer/content/components/list-item.mjs
--- a/toolkit/components/certviewer/content/components/list-item.js
+++ b/toolkit/components/certviewer/content/components/list-item.mjs
@@ -1,13 +1,13 @@
 /* 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/. */
 
-import { normalizeToKebabCase } from "./utils.js";
+import { normalizeToKebabCase } from "./utils.mjs";
 
 export class ListItem extends HTMLElement {
   constructor(item) {
     super();
     this.item = item;
   }
 
   connectedCallback() {
rename from toolkit/components/certviewer/content/components/utils.js
rename to toolkit/components/certviewer/content/components/utils.mjs
deleted file mode 100644
--- a/toolkit/components/certviewer/content/package.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-  "name": "certviewer",
-  "version": "1.0.0",
-  "description": "",
-  "scripts": {
-    "build-pvutils": "./bundle.sh pvutils",
-    "build-asn1js": "./bundle.sh asn1js",
-    "build-pkijs": "./bundle.sh pkijs",
-    "build": "npm run build-pvutils && npm run build-asn1js && npm run build-pkijs"
-  },
-  "license": "MPL-2.0",
-  "dependencies": {
-    "asn1js": "^2.0.22",
-    "pkijs": "^2.1.81",
-    "pvutils": "^1.0.17"
-  },
-  "devDependencies": {
-    "browserify": "^16.2.3"
-  }
-}
deleted file mode 100644
--- a/toolkit/components/certviewer/content/pkijs.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/* 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/. */
-
-const pkijs = require("pkijs"); // version 2.1.78
-
-module.exports = {
-  pkijs,
-};
deleted file mode 100644
--- a/toolkit/components/certviewer/content/pvutils.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/* 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/. */
-
-const pvutils = require("pvutils"); // version 1.0.17
-
-module.exports = {
-  pvutils,
-};
--- a/toolkit/components/certviewer/jar.mn
+++ b/toolkit/components/certviewer/jar.mn
@@ -1,14 +1,12 @@
 # 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/.
 
 toolkit.jar:
-  content/global/certviewer/certviewer.html           (content/certviewer.html)
-  content/global/certviewer/certviewer.css            (content/certviewer.css)
-  content/global/certviewer/certviewer.js             (content/certviewer.js)
-  content/global/certviewer/components/               (content/components/*.js)
-  content/global/certviewer/components/               (content/components/*.css)
-  content/global/certviewer/certDecoder.jsm           (content/certDecoder.jsm)
-  content/global/certviewer/pvutils_bundle.jsm        (content/vendor/pvutils_bundle.jsm)
-  content/global/certviewer/asn1js_bundle.jsm         (content/vendor/asn1js_bundle.jsm)
-  content/global/certviewer/pkijs_bundle.jsm          (content/vendor/pkijs_bundle.jsm)
+  content/global/certviewer/certviewer.html  (content/certviewer.html)
+  content/global/certviewer/certviewer.css   (content/certviewer.css)
+  content/global/certviewer/certviewer.mjs   (content/certviewer.mjs)
+  content/global/certviewer/components/      (content/components/*.mjs)
+  content/global/certviewer/components/      (content/components/*.css)
+  content/global/certviewer/certDecoder.mjs  (content/certDecoder.mjs)
+  content/global/certviewer/vendor/pkijs.js  (content/vendor/pkijs.js)
--- a/toolkit/components/certviewer/tests/browser/adjustedCerts.js
+++ b/toolkit/components/certviewer/tests/browser/adjustedCerts.js
@@ -1,13 +1,13 @@
 "use strict";
 
 /*
-To change this file you will have to add a function to download a file in toolkit/components/certviewer/content/certviewer.js (e.g: https://ourcodeworld.com/articles/read/189/how-to-create-a-file-and-generate-a-download-with-javascript-in-the-browser-without-a-server),
-download adjustedCerts (e.g: download("out.txt", JSON.stringify(adjustedCerts)), do it after this line https://searchfox.org/mozilla-central/rev/e3fc8f8970491aef14d3212b2d052942f4d29818/toolkit/components/certviewer/content/certviewer.js#428),
+To change this file you will have to add a function to download a file in toolkit/components/certviewer/content/certviewer.mjs (e.g: https://ourcodeworld.com/articles/read/189/how-to-create-a-file-and-generate-a-download-with-javascript-in-the-browser-without-a-server),
+download adjustedCerts (e.g: download("out.txt", JSON.stringify(adjustedCerts)), do it after this line https://searchfox.org/mozilla-central/rev/e3fc8f8970491aef14d3212b2d052942f4d29818/toolkit/components/certviewer/content/certviewer.mjs#428),
 then open Nightly and go to about:certificate?cert=MIIHQjCCBiqgAwIBAgIQCgYwQn9bvO1pVzllk7ZFHzANBgkqhkiG9w0BAQsFADB1MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVkIFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE4MDUwODAwMDAwMFoXDTIwMDYwMzEyMDAwMFowgccxHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYBBAGCNzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMRAwDgYDVQQFEwc1MTU3NTUwMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRMwEQYDVQQDEwpnaXRodWIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxjyq8jyXDDrBTyitcnB90865tWBzpHSbindG%2FXqYQkzFMBlXmqkzC%2BFdTRBYyneZw5Pz%2BXWQvL%2B74JW6LsWNc2EF0xCEqLOJuC9zjPAqbr7uroNLghGxYf13YdqbG5oj%2F4x%2BogEG3dF%2FU5YIwVr658DKyESMV6eoYV9mDVfTuJastkqcwero%2B5ZAKfYVMLUEsMwFtoTDJFmVf6JlkOWwsxp1WcQ%2FMRQK1cyqOoUFUgYylgdh3yeCDPeF22Ax8AlQxbcaI%2BGwfQL1FB7Jy%2Bh%2BKjME9lE%2FUpgV6Qt2R1xNSmvFCBWu%2BNFX6epwFP%2FJRbkMfLz0beYFUvmMgLtwVpEPSwIDAQABo4IDeTCCA3UwHwYDVR0jBBgwFoAUPdNQpdagre7zSmAKZdMh1Pj41g8wHQYDVR0OBBYEFMnCU2FmnV%2BrJfQmzQ84mqhJ6kipMCUGA1UdEQQeMByCCmdpdGh1Yi5jb22CDnd3dy5naXRodWIuY29tMA4GA1UdDwEB%2FwQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdQYDVR0fBG4wbDA0oDKgMIYuaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItZXYtc2VydmVyLWcyLmNybDA0oDKgMIYuaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTItZXYtc2VydmVyLWcyLmNybDBLBgNVHSAERDBCMDcGCWCGSAGG%2FWwCATAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAcGBWeBDAEBMIGIBggrBgEFBQcBAQR8MHowJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBSBggrBgEFBQcwAoZGaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkV4dGVuZGVkVmFsaWRhdGlvblNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMIIBfgYKKwYBBAHWeQIEAgSCAW4EggFqAWgAdgCkuQmQtBhYFIe7E6LMZ3AKPDWYBPkb37jjd80OyA3cEAAAAWNBYm0KAAAEAwBHMEUCIQDRZp38cTWsWH2GdBpe%2FuPTWnsu%2Fm4BEC2%2BdIcvSykZYgIgCP5gGv6yzaazxBK2NwGdmmyuEFNSg2pARbMJlUFgU5UAdgBWFAaaL9fC7NP14b1Esj7HRna5vJkRXMDvlJhV1onQ3QAAAWNBYm0tAAAEAwBHMEUCIQCi7omUvYLm0b2LobtEeRAYnlIo7n6JxbYdrtYdmPUWJQIgVgw1AZ51vK9ENinBg22FPxb82TvNDO05T17hxXRC2IYAdgC72d%2B8H4pxtZOUI5eqkntHOFeVCqtS6BqQlmQ2jh7RhQAAAWNBYm3fAAAEAwBHMEUCIQChzdTKUU2N%2BXcqcK0OJYrN8EYynloVxho4yPk6Dq3EPgIgdNH5u8rC3UcslQV4B9o0a0w204omDREGKTVuEpxGeOQwDQYJKoZIhvcNAQELBQADggEBAHAPWpanWOW%2Fip2oJ5grAH8mqQfaunuCVE%2Bvac%2B88lkDK%2FLVdFgl2B6kIHZiYClzKtfczG93hWvKbST4NRNHP9LiaQqdNC17e5vNHnXVUGw%2ByxyjMLGqkgepOnZ2Rb14kcTOGp4i5AuJuuaMwXmCo7jUwPwfLe1NUlVBKqg6LK0Hcq4K0sZnxE8HFxiZ92WpV2AVWjRMEc%2F2z2shNoDvxvFUYyY1Oe67xINkmyQKc%2BygSBZzyLnXSFVWmHr3u5dcaaQGGAR42v6Ydr4iL38Hd4dOiBma%2BFXsXBIqWUjbST4VXmdaol7uzFMojA4zkxQDZAvF5XgJlAFadfySna%2Fteik%3D
 open the file, and finally copy and paste it here like
 
 const adjustedCerts = <what you just copied>;
 */
 
 const adjustedCerts = [
   {
deleted file mode 100644
--- a/toolkit/components/certviewer/tests/chrome/.eslintrc.js
+++ /dev/null
@@ -1,7 +0,0 @@
-"use strict";
-
-module.exports = {
-  parserOptions: {
-    sourceType: "module",
-  },
-};
rename from toolkit/components/certviewer/tests/chrome/CSoutput.js
rename to toolkit/components/certviewer/tests/chrome/CSoutput.mjs
--- a/toolkit/components/certviewer/tests/chrome/CSoutput.js
+++ b/toolkit/components/certviewer/tests/chrome/CSoutput.mjs
@@ -30,19 +30,19 @@ export const certOutputCS = [
       cp: {
         critical: false,
         policies: [
           {
             id: "2.16.840",
             name: "ANSI Organizational Identifier",
             qualifiers: [
               {
-                id: "1.3.6.1.5.5.7.2.1",
-                name: "Practices Statement",
-                value: "https://www.digicert.com/CPS",
+                qualifierId: "1.3.6.1.5.5.7.2.1",
+                qualifierName: "Practices Statement",
+                qualifierValue: "https://www.digicert.com/CPS",
               },
             ],
             value: "2.16.840.1.114412.1.1",
           },
           {
             id: "2.23.140.1.2.2",
             name: "Certificate Type",
             value: "Organization Validation",
--- a/toolkit/components/certviewer/tests/chrome/chrome.ini
+++ b/toolkit/components/certviewer/tests/chrome/chrome.ini
@@ -1,12 +1,12 @@
 [DEFAULT]
 scheme=https
 [test_adjustCertInformation.html]
 support-files =
-   parseOutput.js
+   parseOutput.mjs
 [test_certDecoder.html]
 [test_certDecoderFields.html]
 support-files =
-   CSoutput.js
+   CSoutput.mjs
 [test_kebabCaseInAdjustCertInformation.html]
 support-files =
-   parseOutput.js
+   parseOutput.mjs
rename from toolkit/components/certviewer/tests/chrome/parseOutput.js
rename to toolkit/components/certviewer/tests/chrome/parseOutput.mjs
--- a/toolkit/components/certviewer/tests/chrome/test_adjustCertInformation.html
+++ b/toolkit/components/certviewer/tests/chrome/test_adjustCertInformation.html
@@ -3,32 +3,32 @@
   <head>
     <title>certviewer adjustCertInformation test</title>
     <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
     <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
     <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
   </head>
 <body>
   <script type="module">
-    import { adjustCertInformation } from "chrome://global/content/certviewer/certviewer.js";
-    import { parseOutput } from "./parseOutput.js";
-
     function hasNullElements(obj) {
       for (let key of Object.keys(obj)) {
         if (obj[key] == null) return false;
         if (typeof(obj[key]) === 'object') {
           if (hasNullElements(obj[key]) === false) {
             return false;
           }
         }
       }
       return true;
     }
 
     async function doTest() {
+      const { adjustCertInformation } = await import("chrome://global/content/certviewer/certviewer.mjs");
+      const { parseOutput } = await import("./parseOutput.mjs");
+
       ok(adjustCertInformation, "adjustCertInformation should be available in this context");
       ok(parseOutput, "parseOutput should be available in this context");
       is(typeof(parseOutput), 'object', "parseOutput must be an object");
 
       for (let cert of parseOutput) {
         let adjustedCerts = adjustCertInformation(cert);
         let result = hasNullElements(adjustedCerts.certItems);
         ok(result, "adjustCertInformation function shouldn't return null elements");
--- a/toolkit/components/certviewer/tests/chrome/test_certDecoder.html
+++ b/toolkit/components/certviewer/tests/chrome/test_certDecoder.html
@@ -3,40 +3,24 @@
   <head>
     <title>certviewer parse test</title>
     <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
     <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
     <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
   </head>
 <body>
   <script type="module">
-    import "chrome://global/content/certviewer/pvutils_bundle.jsm";
-    import "chrome://global/content/certviewer/asn1js_bundle.jsm";
-    import "chrome://global/content/certviewer/pkijs_bundle.jsm";
-    import "chrome://global/content/certviewer/certDecoder.jsm";
-
-    const { Integer, fromBER } = globalThis.asn1js.asn1js;
-    const { Certificate } = globalThis.pkijs.pkijs;
-    const { fromBase64, stringToArrayBuffer } = globalThis.pvutils.pvutils;
-
-    const { parse, pemToDER } = globalThis.certDecoderInitializer(
-      Integer,
-      fromBER,
-      Certificate,
-      fromBase64,
-      stringToArrayBuffer,
-      crypto
-    );
-
     // inputPEM is the same input to CS extension (https://github.com/april/certainly-something)
     const inputPEM = "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";
     const certOutputCS = "-----BEGIN%20CERTIFICATE-----%0D%0AMIIGRjCCBS6gAwIBAgIQDJduPkI49CDWPd+G7+u6kDANBgkqhkiG9w0BAQsFADBN%0D%0AMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E%0D%0AaWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMTgxMTA1MDAwMDAwWhcN%0D%0AMTkxMTEzMTIwMDAwWjCBgzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3Ju%0D%0AaWExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxHDAaBgNVBAoTE01vemlsbGEgQ29y%0D%0AcG9yYXRpb24xDzANBgNVBAsTBldlYk9wczEYMBYGA1UEAxMPd3d3Lm1vemlsbGEu%0D%0Ab3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKruymkkmkqCJh7Q%0D%0AjmXlUOBcLFRyw5LG/vUUWVrsxC2gsbR8WJq+cYoYBpoNVStKrO4U2rBh1GEbccvT%0D%0A6qKOQI+pjjDxx9cmRdubGTGp8L0MF1ohVvhIvYLumOEoRDDPU4PvGJjGhek/ojve%0D%0AdPWe8dhciHkxOC2qPFZvVFMwg1/o/b80147BwZQmzB18mnHsmcyKlpsCN8pxw86u%0D%0Aao9Iun8gZQrsllW64rTZlRR56pHdAcuGAoZjYZxwS9Z+lvrSjEgrddemWyGGalqy%0D%0AFp1rXlVM1Tf4/IYWAQXTgTUN303u3xMjss7QK7eUDsACRxiWPLW9XQDd1c+yvaYJ%0D%0AKzgJ2wIDAQABo4IC6TCCAuUwHwYDVR0jBBgwFoAUD4BhHIIxYdUvKOeNRji0LOHG%0D%0A2eIwHQYDVR0OBBYEFNpSvSGcN2VT/B9TdQ8eXwebo60/MCcGA1UdEQQgMB6CD3d3%0D%0Ady5tb3ppbGxhLm9yZ4ILbW96aWxsYS5vcmcwDgYDVR0PAQH/BAQDAgWgMB0GA1Ud%0D%0AJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBrBgNVHR8EZDBiMC+gLaArhilodHRw%0D%0AOi8vY3JsMy5kaWdpY2VydC5jb20vc3NjYS1zaGEyLWc2LmNybDAvoC2gK4YpaHR0%0D%0AcDovL2NybDQuZGlnaWNlcnQuY29tL3NzY2Etc2hhMi1nNi5jcmwwTAYDVR0gBEUw%0D%0AQzA3BglghkgBhv1sAQEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNl%0D%0AcnQuY29tL0NQUzAIBgZngQwBAgIwfAYIKwYBBQUHAQEEcDBuMCQGCCsGAQUFBzAB%0D%0AhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wRgYIKwYBBQUHMAKGOmh0dHA6Ly9j%0D%0AYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJTZWN1cmVTZXJ2ZXJDQS5j%0D%0AcnQwDAYDVR0TAQH/BAIwADCCAQIGCisGAQQB1nkCBAIEgfMEgfAA7gB1AKS5CZC0%0D%0AGFgUh7sTosxncAo8NZgE+RvfuON3zQ7IDdwQAAABZuYWiHwAAAQDAEYwRAIgZnMS%0D%0AH1JdG6NASHWTwD0mlP/zbr0hzP263c02Ym0DU64CIEe4QHJDP47j0b6oTFu6RrZz%0D%0A1NQ9cq8Az1KnMKRuaFAlAHUAh3W/51l8+IxDmV+9827/Vo1HVjb/SrVgwbTq/16g%0D%0Agw8AAAFm5haJAgAABAMARjBEAiAxGLXkUaOAkZhXNeNR3pWyahZeKmSaMXadgu18%0D%0ASfK1ZAIgKtwu5eGxK76rgaszLCZ9edBIjuU0DKorzPUuxUXFY0QwDQYJKoZIhvcN%0D%0AAQELBQADggEBAKLJAFO3wuaP5MM/ed1lhk5Uc2aDokhcM7XyvdhEKSHbgPhcgMoT%0D%0A9YIVoPa70gNC6KHcwoXu0g8wt7X6Vm1ql/68G5q844kFuC6JPl4LVT9mciD+VW6b%0D%0AHUSXD9xifL9DqdJ0Ic0SllTlM+oq5aAeOxUQGXhXIqj6fSQv9fQN6mXxQIoc/gjx%0D%0Ateskq/Vl8YmY1FIZP9Bh7g27kxZ9GAAGQtjTL03RzKAuSg6yeImYVdQWasc7UPnB%0D%0AXlRAzZ8+OJThUbzK16a2CI3Rg4agKSJk+uA47h1/ImmngpFLRb/MvRX6H1oWcUuy%0D%0AH6O7PZdl0YpwTpw1THIuqCGl/wpPgyQgcTM=%0D%0A-----END%20CERTIFICATE-----%0D%0A";
 
 
     async function doTest() {
+      const { parse, pemToDER } = await import("chrome://global/content/certviewer/certDecoder.mjs");
+
       ok(parse, "parse should be available in this context");
       ok(pemToDER, "pemToDER should be available in this context");
 
       let input = inputPEM.trim()
                   .replace(/\r|\n|\0/g, "")
                   .split()
                   .filter(v => v.startsWith("MII"))[0];
 
--- a/toolkit/components/certviewer/tests/chrome/test_certDecoderFields.html
+++ b/toolkit/components/certviewer/tests/chrome/test_certDecoderFields.html
@@ -1,45 +1,29 @@
 <!DOCTYPE html>
 <html>
   <head>
     <title>certviewer parse test</title>
     <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
     <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-    <script type="module" src="CSoutput.js"></script>
     <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
   </head>
 <body>
   <script type="module">
-    import "chrome://global/content/certviewer/pvutils_bundle.jsm";
-    import "chrome://global/content/certviewer/asn1js_bundle.jsm";
-    import "chrome://global/content/certviewer/pkijs_bundle.jsm";
-    import "chrome://global/content/certviewer/certDecoder.jsm";
-
-    const { Integer, fromBER } = globalThis.asn1js.asn1js;
-    const { Certificate } = globalThis.pkijs.pkijs;
-    const { fromBase64, stringToArrayBuffer } = globalThis.pvutils.pvutils;
-
-    const { parse, pemToDER } = globalThis.certDecoderInitializer(
-      Integer,
-      fromBER,
-      Certificate,
-      fromBase64,
-      stringToArrayBuffer,
-      crypto
-    );
-
-    import { certOutputCS } from "./CSoutput.js";
-
     // inputPEM is the same input to CS extension (https://github.com/april/certainly-something)
     const inputPEM = "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";
     const inputPEMerror1 = "MIIGRjCCBS6gAwIBAgIQDJduPkI49CDWPd+G7+u6kDANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5EaWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMTgxMTA1MDAwMDAwWhcNMTkxMTEzMTIwMDAwWjCBgzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxHDAaBgNVBAoTE01vemlsbGEgQ29ycG9yYXRpb24xDzANBgNVBAsTBldlYk9wczEYMBYGA1UEAxMPd3d3Lm1vemlsbGEub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKruymkkmkqCJh7QjmXlUOBcLFRyw5LG/vUUWVrsxC2gsbR8WJq+cYoYBpoNVStKrO4U2rBh1GEbccvT6qKOQI+pjjDxx9cmRdubGTGp8L0MF1ohVvhIvYLumOEoRDDPU4PvGJjGhek/ojvedPWe8dhciHkxOC2qPFZvVFMwg1/o/b80147BwZQmzB18mnHsmcyKlpsCN8pxw86uao9Iun8gZQrsllW64rTZlRR56pHdAcuGAoZjYZxwS9Z+lvrSjEgrddemWyGGalqyFp1rXlVM1Tf4/IYWAQXTgTUN303u3xMjss7QK7eUDsACRxiWPLW9XQDd1c+yvaYJKzgJ2wIDAQABo4IC6TCCAuUwHwYDVR0jBBgwFoAUD4BhHIIxYdUvKOeNRji0LOHG2eIwHQYDVR0OBBYEFNpSvSGcN2VT/B9TdQ8eXwebo60/MCcGA1UdEQQgMB6CD3d3dy5tb3ppbGxhLm9yZ4ILbW96aWxsYS5vcmcwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBrBgNVHR8EZDBiMC+gLaArhilodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc3NjYS1zaGEyLWc2LmNybDAvoC2gK4YpaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NzY2Etc2hhMi1nNi5jcmwwTAYDVR0gBEUwQzA3BglghkgBhv1sAQEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAIBgZngQwBAgIwfAYIKwYBBQUHAQEEcDBuMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wRgYIKwYBBQUHMAKGOmh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJTZWN1cmVTZXJ2ZXJDQS5jcnQwDAYDVR0TAQH/BAIwADCCAQIGCisGAQQB1nkCBAIEgfMEgfAA7gB1AKS5CZC0GFgUh7sTosxncAo8NZgE+RvfuON3zQ7IDdwQAAABZuYWiHwAAAQDAEYwRAIgZnMSH1JdG6NASHWTwD0mlP/zbr0hzP263c02Ym0DU64CIEe4QHJDP47j0b6oTFu6RrZz1NQ9cq8Az1KnMKRuaFAlAHUAh3W/51l8+IxDmV+9827/Vo1HVjb/SrVgwbTq/16ggw8AAAFm5haJAgAABAMARjBEAiAxGLXkUaOAkZhXNeNR3pWyahZeKmSaMXadgu18SfK1ZAIgKtwu5eGxK76rgaszLCZ9edBIjuU0DKorzPUuxUXFY0QwDQYJKoZIhvcNAQELBQADggEBAKLJAFO3wuaP5MM/ed1lhk5Uc2aDokhcM7XyvdhEKSHbgPhcgMoT9YIVoPa70gNC6KHcwoXu0g8wt7X6Vm1ql/68G5q844kFuC6JPl4LVT9mciD+VW6bHUSXD9xifL9D";
     const inputPEMerror2 = "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";
 
     async function run(input, expected) {
+      const { parse, pemToDER } = await import("chrome://global/content/certviewer/certDecoder.mjs");
+
+      ok(parse, "parse should be available in this context");
+      ok(pemToDER, "pemToDER should be available in this context");
+
       let certDER;
       try {
         certDER = pemToDER(input);
       } catch (err) {
         is("error", expected, "OK, error found when executing pemToDER function");
         return;
       }
       ok(certDER, "pemToDER returned a non null value");
@@ -63,18 +47,18 @@
 
       cert.ext.scts.timestamps = cert.ext.scts.timestamps.map(elem => elem.timestamp = "");
       expected.ext.scts.timestamps = expected.ext.scts.timestamps.map(elem => elem.timestamp = "");
 
       is(JSON.stringify(cert), JSON.stringify(expected), "All the fields in CS must be equal to our output");
     }
 
     async function doTest() {
-      ok(parse, "parse should be available in this context");
-      ok(pemToDER, "pemToDER should be available in this context");
+      const { certOutputCS } = await import("./CSoutput.mjs");
+
       ok(certOutputCS, "certOutputCS should be available in this context");
       is(typeof(certOutputCS), 'object', "certOutputCS must be an object");
 
       let inputs = [
         inputPEM,
         inputPEMerror1,
         inputPEMerror2
       ];
--- a/toolkit/components/certviewer/tests/chrome/test_kebabCaseInAdjustCertInformation.html
+++ b/toolkit/components/certviewer/tests/chrome/test_kebabCaseInAdjustCertInformation.html
@@ -3,19 +3,16 @@
   <head>
     <title>certviewer adjustCertInformation test</title>
     <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
     <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
     <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
   </head>
 <body>
   <script type="module">
-    import { adjustCertInformation } from "chrome://global/content/certviewer/certviewer.js";
-    import { parseOutput } from "./parseOutput.js";
-
     function isKebabCase(str) {
       if (str === "") return true;
       return /^([a-z][a-z0-9]*)(-[a-z0-9]+)*$/.test(str);
     }
 
     function validateIsKebabCase() {
       let tests = [
         {
@@ -74,16 +71,19 @@
 
       for (let test of tests) {
         let result = isKebabCase(test.input);
         is(result, test.expected, `${test.input} should${test.expected === false ? "n't" : ""} be a kebab-case string`);
       }
     }
 
     async function doTest() {
+      const { adjustCertInformation } = await import("chrome://global/content/certviewer/certviewer.mjs");
+      const { parseOutput } = await import("./parseOutput.mjs");
+
       ok(adjustCertInformation, "adjustCertInformation should be available in this context");
       ok(parseOutput, "parseOutput should be available in this context");
       is(typeof(parseOutput), 'object', "parseOutput must be an object");
 
       validateIsKebabCase();
 
       for (let cert of parseOutput) {
         let adjustedCerts = adjustCertInformation(cert);
--- a/toolkit/content/aboutNetError.mjs
+++ b/toolkit/content/aboutNetError.mjs
@@ -1,31 +1,19 @@
 /* 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 */
 /* eslint-disable import/no-unassigned-import */
 
-import "chrome://global/content/certviewer/pvutils_bundle.jsm";
-import "chrome://global/content/certviewer/asn1js_bundle.jsm";
-import "chrome://global/content/certviewer/pkijs_bundle.jsm";
-import "chrome://global/content/certviewer/certDecoder.jsm";
-
-const { Integer, fromBER } = globalThis.asn1js.asn1js;
-const { Certificate } = globalThis.pkijs.pkijs;
-const { fromBase64, stringToArrayBuffer } = globalThis.pvutils.pvutils;
-const { parse, pemToDER } = globalThis.certDecoderInitializer(
-  Integer,
-  fromBER,
-  Certificate,
-  fromBase64,
-  stringToArrayBuffer,
-  crypto
-);
+import {
+  parse,
+  pemToDER,
+} from "chrome://global/content/certviewer/certDecoder.mjs";
 
 const formatter = new Intl.DateTimeFormat();
 
 const HOST_NAME = getHostName();
 
 function getHostName() {
   try {
     return new URL(RPMGetInnerMostURI(document.location.href)).hostname;
--- a/tools/esmify/map.json
+++ b/tools/esmify/map.json
@@ -1,19 +1,11 @@
 {
   "chrome://devtools-startup/content/DevToolsShim.jsm":
     "devtools/startup/DevToolsShim.jsm",
-  "chrome://global/content/certviewer/asn1js_bundle.jsm":
-    "toolkit/components/certviewer/content/vendor/asn1js_bundle.jsm",
-  "chrome://global/content/certviewer/certDecoder.jsm":
-    "toolkit/components/certviewer/content/certDecoder.jsm",
-  "chrome://global/content/certviewer/pkijs_bundle.jsm":
-    "toolkit/components/certviewer/content/vendor/pkijs_bundle.jsm",
-  "chrome://global/content/certviewer/pvutils_bundle.jsm":
-    "toolkit/components/certviewer/content/vendor/pvutils_bundle.jsm",
   "chrome://global/content/tabprompts.jsm":
     "toolkit/components/prompts/content/tabprompts.jsm",
   "chrome://mochikit/content/ShutdownLeaksCollector.jsm":
     "testing/mochitest/ShutdownLeaksCollector.jsm",
   "chrome://mochikit/content/tests/SimpleTest/StructuredLog.jsm":
     "testing/modules/StructuredLog.jsm",
   "chrome://mochitests/content/browser/accessible/tests/browser/Common.jsm":
     "accessible/tests/browser/Common.jsm",