@smartdcc/dccboxed-keystore
Version:
DCC Boxed server keystore exposed as json db.
237 lines • 8.5 kB
JavaScript
;
/*
* Created on Thu Aug 04 2022
*
* Copyright (c) 2022 Smart DCC Limited
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.KeyUsage = exports.EUI = void 0;
exports.normaliseEUI = normaliseEUI;
exports.parseOrganisationSubject = parseOrganisationSubject;
exports.extractExtension = extractExtension;
exports.parseKeyUsageFromExtensions = parseKeyUsageFromExtensions;
exports.assertKeyType = assertKeyType;
exports.buildOrgCertificateMetadata = buildOrgCertificateMetadata;
exports.parseSubjectAltNameFromExtensions = parseSubjectAltNameFromExtensions;
exports.buildDeviceCertificateMetadata = buildDeviceCertificateMetadata;
const asn1_ts_1 = require("asn1-ts");
const types_1 = require("node:util/types");
function normaliseEUI(eui) {
let result;
if (typeof eui === 'string') {
result = eui.toLowerCase().replace(/\s/g, '').replace(/-/g, '');
}
else if ((0, types_1.isUint8Array)(eui)) {
result = Buffer.from(eui).toString('hex');
}
else if (eui instanceof EUI) {
result = eui.valueOf();
}
else {
throw new TypeError('eui should be a string or Uint8Array');
}
if (result.match(/^[0-9a-f]{16}$/) === null) {
throw new Error(`not a valid eui: ${result}`);
}
return result;
}
class EUI {
constructor(eui) {
this.eui = normaliseEUI(eui);
}
toString() {
return this.eui;
}
valueOf() {
return this.eui;
}
equals(otherEui) {
if (typeof otherEui === 'string' || (0, types_1.isUint8Array)(otherEui)) {
try {
return this.eui === normaliseEUI(otherEui);
}
catch {
return false;
}
}
else {
return this.eui === otherEui.eui;
}
}
}
exports.EUI = EUI;
var KeyUsage;
(function (KeyUsage) {
KeyUsage[KeyUsage["digitalSignature"] = 0] = "digitalSignature";
KeyUsage[KeyUsage["nonRepudiation"] = 1] = "nonRepudiation";
KeyUsage[KeyUsage["keyEncipherment"] = 2] = "keyEncipherment";
KeyUsage[KeyUsage["dataEncipherment"] = 3] = "dataEncipherment";
KeyUsage[KeyUsage["keyAgreement"] = 4] = "keyAgreement";
KeyUsage[KeyUsage["keyCertSign"] = 5] = "keyCertSign";
KeyUsage[KeyUsage["cRLSign"] = 6] = "cRLSign";
KeyUsage[KeyUsage["encipherOnly"] = 7] = "encipherOnly";
KeyUsage[KeyUsage["decipherOnly"] = 8] = "decipherOnly";
})(KeyUsage || (exports.KeyUsage = KeyUsage = {}));
function parseOrganisationSubject(subjectRDNs) {
let role = undefined;
let eui = undefined;
for (const rdn of subjectRDNs) {
const attribs = rdn.set;
for (const att of attribs) {
const type = att.sequence[0].objectIdentifier;
/* organizationUnitName */
if (type.dotDelimitedNotation === '2.5.4.11') {
const hexRole = att.sequence[1].utf8String; /* should be a choice */
if (hexRole.match(/^[0-9a-fA-F]{2}$/) !== null) {
role = parseInt(hexRole, 16);
}
}
/* uniqueIdentifier */
if (type.dotDelimitedNotation === '2.5.4.45') {
const id = att.sequence[1].bitString;
if (id.length === 64) {
eui = Buffer.from((0, asn1_ts_1.packBits)(id)).toString('hex');
}
}
}
}
if (role === undefined || eui === undefined) {
throw new Error('invalid subject');
}
return {
role,
eui: new EUI(eui),
};
}
function extractExtension(tbsCertificate, oid) {
/* find and then iterate through extensions */
for (const el of tbsCertificate) {
if (el.tagNumber === 3 &&
el.tagClass === asn1_ts_1.ASN1TagClass.context &&
el.construction === asn1_ts_1.ASN1Construction.constructed) {
const extensions = el.sequence[0].sequence;
for (const ext of extensions) {
const extension = ext.sequence;
/* filter non-keyUsage */
if (extension[0].objectIdentifier.dotDelimitedNotation === oid) {
return extension[extension.length - 1].octetString;
}
}
}
}
return null;
}
/**
* Search for the keyUsage extension and extract its values. More info:
* https://datatracker.ietf.org/doc/html/rfc2459#section-4.1
*
* @param tbsCertificate
* @returns
*/
function parseKeyUsageFromExtensions(tbsCertificate) {
const keyUsage = [];
const e = extractExtension(tbsCertificate, '2.5.29.15');
if (e === null) {
throw new Error('keyUsage extension not found');
}
const usage = new asn1_ts_1.BERElement();
usage.fromBytes(e);
const usageString = usage.bitString;
for (let j = 0; j < usageString.length; j++) {
if (usageString[j]) {
keyUsage.push(j);
}
}
return keyUsage;
}
/**
* Given an algorithm identifier (as defined by RFC2459), throw an exception if
* its not ecdsa with sha256.
*
* @param algId
*/
function assertKeyType(algId) {
if (algId[0].objectIdentifier.dotDelimitedNotation !== '1.2.840.10045.4.3.2') {
throw new Error('expected ECDSA with SHA256');
}
}
/**
* parse metadata from a organisation certificate, throws exception if not
* correct format.
* @param cert
* @returns
*/
function buildOrgCertificateMetadata(cert) {
const root = new asn1_ts_1.BERElement();
root.fromBytes(cert.raw);
/* below assumes standard certificate structure, see RFC2459 section 4 */
const tbsCertificate = root.sequence[0].sequence;
const serial = tbsCertificate[1].integer;
assertKeyType(tbsCertificate[2].sequence);
const subjectRDNs = tbsCertificate[5].sequence;
const subject = parseOrganisationSubject(subjectRDNs);
const keyUsage = parseKeyUsageFromExtensions(tbsCertificate.slice(7));
return {
...subject,
serial,
keyUsage,
};
}
function parseSubjectAltNameFromExtensions(tbsCertificate) {
const e = extractExtension(tbsCertificate, '2.5.29.17');
if (e === null) {
throw new Error('subjectAltName extension not found');
}
const altName = new asn1_ts_1.BERElement();
altName.fromBytes(e);
const subjectAltNames = altName.sequence;
for (const generalName of subjectAltNames) {
/* search for an otherName which is a id-on-hardwareModuleName */
if (generalName.tagNumber === 0 &&
generalName.tagClass === asn1_ts_1.ASN1TagClass.context &&
generalName.construction === asn1_ts_1.ASN1Construction.constructed &&
generalName.sequence[0].objectIdentifier.dotDelimitedNotation ===
'1.3.6.1.5.5.7.8.4') {
const hardwareModuleName = generalName.sequence[1].sequence[0].sequence;
// below would be a manufacturer unique id
// hardwareModuleName[0].objectIdentifier.dotDelimitedNotation
return new EUI(hardwareModuleName[1].octetString);
}
}
throw new Error('hwSerialNum not found');
}
/**
* parse metadata from a device certificate, throws exception if not correct
* format.
* @param cert
* @returns
*/
function buildDeviceCertificateMetadata(cert) {
const root = new asn1_ts_1.BERElement();
root.fromBytes(cert.raw);
/* below assumes standard certificate structure, see RFC2459 section 4 */
const tbsCertificate = root.sequence[0].sequence;
const serial = tbsCertificate[1].integer;
assertKeyType(tbsCertificate[2].sequence);
const eui = parseSubjectAltNameFromExtensions(tbsCertificate);
const keyUsage = parseKeyUsageFromExtensions(tbsCertificate);
return {
eui,
serial,
keyUsage,
};
}
//# sourceMappingURL=certificateMetadata.js.map