electron-root-ssl-pinning
Version:
Pinning root CA certificates into your Electron app
90 lines (89 loc) • 3.83 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const CertificateChainValidationEngine_1 = __importDefault(require("pkijs/build/CertificateChainValidationEngine"));
const utils_1 = require("./utils");
const types_1 = require("./types");
const utils_2 = require("./utils");
function createChainVerifier(caStore) {
return async (request) => {
try {
const chain = createCertificatesChainFromRequest(request);
const isHostnameAllowed = validateHostname(request.hostname, chain[0]);
if (!isHostnameAllowed) {
return types_1.VerificationResult.INVALID;
}
const result = await verifyChain(chain, caStore);
return result;
}
catch (err) {
console.error(err);
return types_1.VerificationResult.INTERNAL_ERROR;
}
};
}
exports.createChainVerifier = createChainVerifier;
function createCertificatesChainFromRequest(request) {
const chain = [];
function getCert(requestCert) {
const cert = utils_1.createPKICertificate(requestCert.data);
if (!utils_1.isRootCertificate(cert)) {
chain.push(cert);
if (requestCert.issuerCert) {
getCert(requestCert.issuerCert);
}
}
}
getCert(request.certificate);
return chain;
}
exports.createCertificatesChainFromRequest = createCertificatesChainFromRequest;
async function verifyChain(chain, caStore) {
if (chain.every(cert => utils_2.isValidityPeriodCorrect(cert) && !utils_2.isWeakEncryption(cert))) {
const lastIntermediateCert = chain[chain.length - 1];
if (lastIntermediateCert) {
const rootCaKey = utils_1.findDistinguishedName(lastIntermediateCert, "issuer");
if (caStore[rootCaKey] !== undefined) {
const validator = new CertificateChainValidationEngine_1.default({
certs: [...chain, caStore[rootCaKey]],
trustedCerts: [caStore[rootCaKey]],
});
const { result } = await validator.verify();
if (result) {
return types_1.VerificationResult.VALID;
}
}
}
}
return types_1.VerificationResult.INVALID;
}
exports.verifyChain = verifyChain;
function validateHostname(hostname, leafCert) {
if (leafCert) {
const foundSubjectAlternativeNames = leafCert.extensions && leafCert.extensions.find(({ extnID }) => extnID === utils_2.subjectAlternativeNameOid);
const foundSubjectCommonName = leafCert.subject.typesAndValues.find(({ type }) => String(type) === utils_2.commonNameOid);
if (foundSubjectAlternativeNames) {
return foundSubjectAlternativeNames.parsedValue.altNames.some((allowedName) => isAllowedDomain(hostname, allowedName.value));
}
if (foundSubjectCommonName) {
return isAllowedDomain(hostname, foundSubjectCommonName.value.valueBlock.value);
}
}
return false;
}
exports.validateHostname = validateHostname;
function isAllowedDomain(hostname, allowedName) {
const hostNameDomains = hostname.split(".").reverse();
const allowedDomains = allowedName.split(".").reverse();
const isTooFarSubdomain = hostNameDomains.length > allowedDomains.length;
if (isTooFarSubdomain) {
return false;
}
return allowedDomains.every((domain, index) => {
const correspondingHostNameDomain = hostNameDomains[index] || "";
return domain.toLowerCase() === correspondingHostNameDomain.toLowerCase() || domain === "*";
});
}
exports.isAllowedDomain = isAllowedDomain;