From 0e6d64d5496f15c20d89be13ee67f3c4613f9233 Mon Sep 17 00:00:00 2001 From: Carolina Date: Thu, 27 Jun 2019 19:03:50 -0300 Subject: [PATCH 1/4] Changed pemToBER name to pemToDER. Added some functions in der.js, to reduce the size of parse function --- src/viewer/js/der.js | 267 +++++++++++++++++++++++------------------ src/viewer/js/utils.js | 2 +- 2 files changed, 153 insertions(+), 116 deletions(-) diff --git a/src/viewer/js/der.js b/src/viewer/js/der.js index f2b5537..ae88cf1 100644 --- a/src/viewer/js/der.js +++ b/src/viewer/js/der.js @@ -1,111 +1,22 @@ -import * as asn1js from 'asn1js'; +import { fromBER } from 'asn1js'; import { Certificate } from 'pkijs'; import { ctLogNames } from './ctlognames.js'; import { strings } from '../../i18n/strings.js'; -import { b64urltodec, b64urltohex, getObjPath, hash, hashify, pemToBER } from './utils.js'; +import { b64urltodec, b64urltohex, getObjPath, hash, hashify } from './utils.js'; - - -const getX509Ext = (extensions, v) => { - for (var extension in extensions) { - if (extensions[extension].extnID === v) { - return extensions[extension]; - } - } - - return { - extnValue: undefined, - parsedValue: undefined, - }; - -}; - - -const parseSubsidiary = (distinguishedNames) => { - const subsidiary = { - cn: '', - dn: [], - entries: [], - }; - - distinguishedNames.forEach(dn => { - const name = strings.names[dn.type]; - const value = dn.value.valueBlock.value; - - 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]); - - // add the common name for tab display - if (name.short === 'cn') { - subsidiary.cn = value; - } - } - }); - - // turn path into a string - subsidiary.dn = subsidiary.dn.join(', '); - - return subsidiary; -}; - - -export const parse = async (certificate) => { - 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 the current time zone - note that there are some time zones that this doesn't easily - // match, for whatever reason. https://github.com/april/certainly-something/issues/21 +const getTimeZone = () => { let timeZone = new Date().toString().match(/\(([A-Za-z\s].*)\)/); - if (timeZone === null) { // America/Chicago + if (timeZone === null) { // America/Chicago timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; } else if (timeZone.length > 1) { - timeZone = timeZone[1]; // Central Daylight Time + timeZone = timeZone[1]; // Central Daylight Time } else { - timeZone = 'Local Time'; // not sure if this is right, but let's go with it for now + timeZone = 'Local Time'; // not sure if this is right, but let's go with it for now } + return timeZone; +} - // parse the certificate - const asn1 = asn1js.fromBER(certificate); - - let x509 = new Certificate({ schema: asn1.result }); - x509 = x509.toJSON() - - // convert the cert to PEM - const certBTOA = window.btoa(String.fromCharCode.apply(null, new Uint8Array(certificate))).match(/.{1,64}/g).join('\r\n'); - - // get which extensions are critical - const criticalExtensions = []; - x509.extensions.forEach(ext => { - if (ext.hasOwnProperty('critical') && ext.critical === true) { - criticalExtensions.push(ext.extnID); - } - }); - - // get the public key info +const getPublicKeyInfo = (x509) => { let spki = Object.assign({ crv: undefined, e: undefined, @@ -128,9 +39,23 @@ export const parse = async (certificate) => { spki.y = hashify(b64urltohex(spki.y)); // y coordinate spki.xy = `04:${spki.x}:${spki.y}`; // 04 (uncompressed) public key } + return spki; +} - // get the keyUsages - const keyUsages = { +const getX509Ext = (extensions, v) => { + for (var extension in extensions) { + if (extensions[extension].extnID === v) { + return extensions[extension]; + } + } + return { + extnValue: undefined, + parsedValue: undefined, + }; +}; + +const getKeyUsages = (x509, criticalExtensions) => { + let keyUsages = { critical: criticalExtensions.includes('2.5.29.15'), purposes: [], }; @@ -148,13 +73,50 @@ export const parse = async (certificate) => { } keyUsagesBS = keyUsagesBS >> 1; - }) + }); // reverse the order for legibility keyUsages.purposes.reverse(); }; - // get the subjectAltNames + return keyUsages; +} + +const parseSubsidiary = (distinguishedNames) => { + const subsidiary = { + cn: '', + dn: [], + entries: [], + }; + + distinguishedNames.forEach(dn => { + const name = strings.names[dn.type]; + const value = dn.value.valueBlock.value; + + 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]); + + // add the common name for tab display + if (name.short === 'cn') { + subsidiary.cn = value; + } + } + }); + + // 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 => { @@ -180,13 +142,14 @@ export const parse = async (certificate) => { } else { san = []; } - san = { altNames: san, critical: criticalExtensions.includes('2.5.29.17'), }; + return san; +} - // get the basic constraints +const getBasicConstraints = (x509, criticalExtensions) => { let basicConstraints; const basicConstraintsExt = getX509Ext(x509.extensions, '2.5.29.19'); if (basicConstraintsExt && basicConstraintsExt.parsedValue) { @@ -195,8 +158,10 @@ export const parse = async (certificate) => { critical: criticalExtensions.includes('2.5.29.19'), }; } + return basicConstraints; +} - // get the extended key usages +const getEKeyUsages = (x509, criticalExtensions) => { let eKeyUsages = getX509Ext(x509.extensions, '2.5.29.37').parsedValue; if (eKeyUsages) { eKeyUsages = { @@ -204,8 +169,10 @@ export const parse = async (certificate) => { purposes: eKeyUsages.keyPurposes.map(x => strings.eKU[x] || x), } } + return eKeyUsages; +} - // get the subject key identifier +const getSubjectKeyID = (x509, criticalExtensions) => { let sKID = getX509Ext(x509.extensions, '2.5.29.14').parsedValue; if (sKID) { sKID = { @@ -213,8 +180,10 @@ export const parse = async (certificate) => { id: hashify(sKID.valueBlock.valueHex), } } + return sKID; +} - // get the authority key identifier +const getAuthorityKeyID = (x509, criticalExtensions) => { let aKID = getX509Ext(x509.extensions, '2.5.29.35').parsedValue; if (aKID) { aKID = { @@ -222,8 +191,10 @@ export const parse = async (certificate) => { id: hashify(aKID.keyIdentifier.valueBlock.valueHex), } } + return aKID; +} - // get the CRL points +const getCRLPoints = (x509, criticalExtensions) => { let crlPoints = getX509Ext(x509.extensions, '2.5.29.31').parsedValue; if (crlPoints) { crlPoints = { @@ -231,7 +202,10 @@ export const parse = async (certificate) => { points: crlPoints.distributionPoints.map(x => x.distributionPoint[0].value), }; } + return crlPoints; +} +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 = { @@ -244,8 +218,10 @@ export const parse = async (certificate) => { required: false, } } + return ocspStaple; +} - // get the Authority Information Access +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 => { @@ -260,8 +236,10 @@ export const parse = async (certificate) => { descriptions: aia, critical: criticalExtensions.includes('1.3.6.1.5.5.7.1.1'), } + return aia; +} - // get the embedded SCTs +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 => { @@ -270,7 +248,7 @@ export const parse = async (certificate) => { logId: hashify(logId), name: ctLogNames.hasOwnProperty(logId) ? ctLogNames[logId] : undefined, signatureAlgorithm: `${scts.timestamps[x].hashAlgorithm.replace('sha', 'SHA-')} ${scts.timestamps[x].signatureAlgorithm.toUpperCase()}`, - timestamp: `${scts.timestamps[x].timestamp.toLocaleString()} (${timeZone})`, + timestamp: `${scts.timestamps[x].timestamp.toLocaleString()} (${getTimeZone()})`, version: scts.timestamps[x].version + 1, } }); @@ -282,8 +260,10 @@ export const parse = async (certificate) => { critical: criticalExtensions.includes('1.3.6.1.4.1.11129.2.4.2'), timestamps: scts, } + return scts; +} - // Certificate Policies, this stuff is really messy +const getCertificatePolicies = (x509, criticalExtensions) => { let cp = getX509Ext(x509.extensions, '2.5.29.32').parsedValue; if (cp && cp.hasOwnProperty('certificatePolicies')) { cp = cp.certificatePolicies.map(x => { @@ -341,7 +321,10 @@ export const parse = async (certificate) => { critical: criticalExtensions.includes('2.5.29.32'), policies: cp, } + return cp; +} +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, @@ -398,6 +381,62 @@ export const parse = async (certificate) => { msCrypto.certificateType || msCrypto.previousHash) ? true : false; + return msCrypto; +} + +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 + ]; + + let timeZone = getTimeZone(); + + // parse the certificate + const asn1 = fromBER(certificate); + + const x509 = new Certificate({ schema: asn1.result }).toJSON(); + + // convert the cert to PEM + const certBTOA = window.btoa(String.fromCharCode.apply(null, new Uint8Array(certificate))).match(/.{1,64}/g).join('\r\n'); + + // get which extensions are critical + const criticalExtensions = []; + 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); + // determine which extensions weren't supported let unsupportedExtensions = []; x509.extensions.forEach(ext => { @@ -406,8 +445,6 @@ export const parse = async (certificate) => { } }); - // console.log('returning from parse() for cert', x509); - // the output shell return { ext: { @@ -425,7 +462,7 @@ export const parse = async (certificate) => { san, }, files: { - der: undefined, + der: undefined, // TODO: implement! pem: encodeURI(`-----BEGIN CERTIFICATE-----\r\n${certBTOA}\r\n-----END CERTIFICATE-----\r\n`), }, fingerprint: { diff --git a/src/viewer/js/utils.js b/src/viewer/js/utils.js index 5db76d4..7688b72 100644 --- a/src/viewer/js/utils.js +++ b/src/viewer/js/utils.js @@ -44,6 +44,6 @@ export const hashify = (hash) => { } }; -export const pemToBER = (pem) => { +export const pemToDER = (pem) => { return stringToArrayBuffer(window.atob(pem)); }; From 469241256830850a6b5457a6b580dd48e6cec0e1 Mon Sep 17 00:00:00 2001 From: Carolina Date: Fri, 28 Jun 2019 08:54:07 -0300 Subject: [PATCH 2/4] Changed the name of der.js, and changed if statement to match '2.16.840.' instead of '2.16.840' --- src/viewer/js/{der.js => certDecoder.js} | 2 +- src/viewer/js/render.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/viewer/js/{der.js => certDecoder.js} (99%) diff --git a/src/viewer/js/der.js b/src/viewer/js/certDecoder.js similarity index 99% rename from src/viewer/js/der.js rename to src/viewer/js/certDecoder.js index ae88cf1..99d4e7c 100644 --- a/src/viewer/js/der.js +++ b/src/viewer/js/certDecoder.js @@ -273,7 +273,7 @@ const getCertificatePolicies = (x509, criticalExtensions) => { let value = strings.cps.hasOwnProperty(id) ? strings.cps[id].value : undefined; // ansi organization identifiers - if (id.startsWith('2.16.840')) { + if (id.startsWith('2.16.840.')) { value = id; id = '2.16.840'; name = strings.cps['2.16.840'].name; diff --git a/src/viewer/js/render.js b/src/viewer/js/render.js index c48cd3b..f2f09f0 100644 --- a/src/viewer/js/render.js +++ b/src/viewer/js/render.js @@ -1,4 +1,4 @@ -import { parse } from './der.js'; +import { parse } from './certDecoder.js'; import { pemToBER } from './utils.js'; let template = require('../index.handlebars'); From 213b044ea205635c029dc2f9150e8e0b89a8220c Mon Sep 17 00:00:00 2001 From: Carolina Date: Wed, 31 Jul 2019 09:27:15 -0300 Subject: [PATCH 3/4] Calling the functions in the return object instead of assigning the result in a variable and then return that variable --- src/viewer/js/certDecoder.js | 40 ++++++++++++------------------------ 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/src/viewer/js/certDecoder.js b/src/viewer/js/certDecoder.js index 99d4e7c..eacbaef 100644 --- a/src/viewer/js/certDecoder.js +++ b/src/viewer/js/certDecoder.js @@ -423,20 +423,6 @@ export const parse = async (certificate) => { // certificate could be an array o } }); - 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); - // determine which extensions weren't supported let unsupportedExtensions = []; x509.extensions.forEach(ext => { @@ -448,18 +434,18 @@ export const parse = async (certificate) => { // certificate could be an array o // the output shell return { ext: { - aia, - aKID, - basicConstraints, - crlPoints, - cp, - eKeyUsages, - keyUsages, - msCrypto, - ocspStaple, - scts: scts, - sKID, - san, + aia: getAuthorityInfoAccess(x509, criticalExtensions), + aKID: getAuthorityKeyID(x509, criticalExtensions), + basicConstraints: getBasicConstraints(x509, criticalExtensions), + crlPoints: getCRLPoints(x509, criticalExtensions), + cp: getCertificatePolicies(x509, criticalExtensions), + eKeyUsages: getEKeyUsages(x509, criticalExtensions), + keyUsages: getKeyUsages(x509, criticalExtensions), + msCrypto: getMicrosoftCryptographicExtensions(x509, criticalExtensions), + ocspStaple: getOcspStaple(x509, criticalExtensions), + scts: getSCTs(x509, criticalExtensions), + sKID: getSubjectKeyID(x509, criticalExtensions), + san: getSubjectAltNames(x509, criticalExtensions), }, files: { der: undefined, // TODO: implement! @@ -478,7 +464,7 @@ export const parse = async (certificate) => { // certificate could be an array o name: strings.signature[getObjPath(x509, 'signature.algorithmId')], type: getObjPath(x509, 'signature.algorithmId'), }, - subjectPublicKeyInfo: spki, + subjectPublicKeyInfo: getPublicKeyInfo(x509), unsupportedExtensions, version: (x509.version + 1).toString(), } From f8c53ac371479f5f64fbafbe16c19641aacfe427 Mon Sep 17 00:00:00 2001 From: Carolina Date: Wed, 31 Jul 2019 09:46:58 -0300 Subject: [PATCH 4/4] Fixes error calling pemToBER function, instead pemToDER is being call now --- src/viewer/js/render.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/viewer/js/render.js b/src/viewer/js/render.js index f2f09f0..104ce92 100644 --- a/src/viewer/js/render.js +++ b/src/viewer/js/render.js @@ -1,5 +1,5 @@ import { parse } from './certDecoder.js'; -import { pemToBER } from './utils.js'; +import { pemToDER } from './utils.js'; let template = require('../index.handlebars'); @@ -16,7 +16,7 @@ const buildChain = async (chain) => { .split(/-----BEGIN CERTIFICATE-----|-----END CERTIFICATE-----/g) .filter(v => v.startsWith('MII')); - builtChain = builtChain.map(cert => { return pemToBER(cert) }); + builtChain = builtChain.map(cert => { return pemToDER(cert) }); } else if (chain.buffer) { // DER encoded builtChain = [ chain.buffer ]; } else if (typeof chain === 'object' && Array.isArray(chain)) {