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