UNPKG

@peculiar/x509

Version:

@peculiar/x509 is an easy to use TypeScript/Javascript library based on @peculiar/asn1-schema that makes generating X.509 Certificates and Certificate Requests as well as validating certificate chains easy

1,370 lines (1,358 loc) 117 kB
/*! * MIT License * * Copyright (c) Peculiar Ventures. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ import 'reflect-metadata'; import { AsnConvert, OctetString, AsnUtf8StringConverter } from '@peculiar/asn1-schema'; import * as asn1X509 from '@peculiar/asn1-x509'; import { AlgorithmIdentifier, Extension as Extension$1, Name as Name$1, RelativeDistinguishedName, AttributeTypeAndValue, SubjectPublicKeyInfo, BasicConstraints, id_ce_basicConstraints, KeyUsage, id_ce_keyUsage, Attribute as Attribute$1, Version, Extensions, Certificate, RevokedCertificate, Time, id_ce_invalidityDate, InvalidityDate, id_ce_cRLReasons, CRLReason, CertificateList } from '@peculiar/asn1-x509'; import { BufferSourceConverter, isEqual, Convert, combine } from 'pvtsutils'; import * as asn1Cms from '@peculiar/asn1-cms'; import * as asn1Ecc from '@peculiar/asn1-ecc'; import { id_ecPublicKey, ECDSASigValue } from '@peculiar/asn1-ecc'; import * as asn1Rsa from '@peculiar/asn1-rsa'; import { id_RSASSA_PSS, id_rsaEncryption, RSAPublicKey, id_sha512, id_sha384, id_sha256, id_sha1 } from '@peculiar/asn1-rsa'; import { __decorate } from 'tslib'; import { container, injectable } from 'tsyringe'; import * as asnPkcs9 from '@peculiar/asn1-pkcs9'; import { id_pkcs9_at_extensionRequest } from '@peculiar/asn1-pkcs9'; import { CertificationRequest, CertificationRequestInfo } from '@peculiar/asn1-csr'; const diAlgorithm = "crypto.algorithm"; class AlgorithmProvider { getAlgorithms() { return container.resolveAll(diAlgorithm); } toAsnAlgorithm(alg) { ({ ...alg }); for (const algorithm of this.getAlgorithms()) { const res = algorithm.toAsnAlgorithm(alg); if (res) { return res; } } if (/^[0-9.]+$/.test(alg.name)) { const res = new AlgorithmIdentifier({ algorithm: alg.name, }); if ("parameters" in alg) { const unknown = alg; res.parameters = unknown.parameters; } return res; } throw new Error("Cannot convert WebCrypto algorithm to ASN.1 algorithm"); } toWebAlgorithm(alg) { for (const algorithm of this.getAlgorithms()) { const res = algorithm.toWebAlgorithm(alg); if (res) { return res; } } const unknown = { name: alg.algorithm, parameters: alg.parameters, }; return unknown; } } const diAlgorithmProvider = "crypto.algorithmProvider"; container.registerSingleton(diAlgorithmProvider, AlgorithmProvider); var EcAlgorithm_1; const idVersionOne = "1.3.36.3.3.2.8.1.1"; const idBrainpoolP160r1 = `${idVersionOne}.1`; const idBrainpoolP160t1 = `${idVersionOne}.2`; const idBrainpoolP192r1 = `${idVersionOne}.3`; const idBrainpoolP192t1 = `${idVersionOne}.4`; const idBrainpoolP224r1 = `${idVersionOne}.5`; const idBrainpoolP224t1 = `${idVersionOne}.6`; const idBrainpoolP256r1 = `${idVersionOne}.7`; const idBrainpoolP256t1 = `${idVersionOne}.8`; const idBrainpoolP320r1 = `${idVersionOne}.9`; const idBrainpoolP320t1 = `${idVersionOne}.10`; const idBrainpoolP384r1 = `${idVersionOne}.11`; const idBrainpoolP384t1 = `${idVersionOne}.12`; const idBrainpoolP512r1 = `${idVersionOne}.13`; const idBrainpoolP512t1 = `${idVersionOne}.14`; const brainpoolP160r1 = "brainpoolP160r1"; const brainpoolP160t1 = "brainpoolP160t1"; const brainpoolP192r1 = "brainpoolP192r1"; const brainpoolP192t1 = "brainpoolP192t1"; const brainpoolP224r1 = "brainpoolP224r1"; const brainpoolP224t1 = "brainpoolP224t1"; const brainpoolP256r1 = "brainpoolP256r1"; const brainpoolP256t1 = "brainpoolP256t1"; const brainpoolP320r1 = "brainpoolP320r1"; const brainpoolP320t1 = "brainpoolP320t1"; const brainpoolP384r1 = "brainpoolP384r1"; const brainpoolP384t1 = "brainpoolP384t1"; const brainpoolP512r1 = "brainpoolP512r1"; const brainpoolP512t1 = "brainpoolP512t1"; const ECDSA = "ECDSA"; let EcAlgorithm = EcAlgorithm_1 = class EcAlgorithm { toAsnAlgorithm(alg) { switch (alg.name.toLowerCase()) { case ECDSA.toLowerCase(): if ("hash" in alg) { const hash = typeof alg.hash === "string" ? alg.hash : alg.hash.name; switch (hash.toLowerCase()) { case "sha-1": return asn1Ecc.ecdsaWithSHA1; case "sha-256": return asn1Ecc.ecdsaWithSHA256; case "sha-384": return asn1Ecc.ecdsaWithSHA384; case "sha-512": return asn1Ecc.ecdsaWithSHA512; } } else if ("namedCurve" in alg) { let parameters = ""; switch (alg.namedCurve) { case "P-256": parameters = asn1Ecc.id_secp256r1; break; case "K-256": parameters = EcAlgorithm_1.SECP256K1; break; case "P-384": parameters = asn1Ecc.id_secp384r1; break; case "P-521": parameters = asn1Ecc.id_secp521r1; break; case brainpoolP160r1: parameters = idBrainpoolP160r1; break; case brainpoolP160t1: parameters = idBrainpoolP160t1; break; case brainpoolP192r1: parameters = idBrainpoolP192r1; break; case brainpoolP192t1: parameters = idBrainpoolP192t1; break; case brainpoolP224r1: parameters = idBrainpoolP224r1; break; case brainpoolP224t1: parameters = idBrainpoolP224t1; break; case brainpoolP256r1: parameters = idBrainpoolP256r1; break; case brainpoolP256t1: parameters = idBrainpoolP256t1; break; case brainpoolP320r1: parameters = idBrainpoolP320r1; break; case brainpoolP320t1: parameters = idBrainpoolP320t1; break; case brainpoolP384r1: parameters = idBrainpoolP384r1; break; case brainpoolP384t1: parameters = idBrainpoolP384t1; break; case brainpoolP512r1: parameters = idBrainpoolP512r1; break; case brainpoolP512t1: parameters = idBrainpoolP512t1; break; } if (parameters) { return new AlgorithmIdentifier({ algorithm: asn1Ecc.id_ecPublicKey, parameters: AsnConvert.serialize(new asn1Ecc.ECParameters({ namedCurve: parameters })), }); } } } return null; } toWebAlgorithm(alg) { switch (alg.algorithm) { case asn1Ecc.id_ecdsaWithSHA1: return { name: ECDSA, hash: { name: "SHA-1" } }; case asn1Ecc.id_ecdsaWithSHA256: return { name: ECDSA, hash: { name: "SHA-256" } }; case asn1Ecc.id_ecdsaWithSHA384: return { name: ECDSA, hash: { name: "SHA-384" } }; case asn1Ecc.id_ecdsaWithSHA512: return { name: ECDSA, hash: { name: "SHA-512" } }; case asn1Ecc.id_ecPublicKey: { if (!alg.parameters) { throw new TypeError("Cannot get required parameters from EC algorithm"); } const parameters = AsnConvert.parse(alg.parameters, asn1Ecc.ECParameters); switch (parameters.namedCurve) { case asn1Ecc.id_secp256r1: return { name: ECDSA, namedCurve: "P-256" }; case EcAlgorithm_1.SECP256K1: return { name: ECDSA, namedCurve: "K-256" }; case asn1Ecc.id_secp384r1: return { name: ECDSA, namedCurve: "P-384" }; case asn1Ecc.id_secp521r1: return { name: ECDSA, namedCurve: "P-521" }; case idBrainpoolP160r1: return { name: ECDSA, namedCurve: brainpoolP160r1 }; case idBrainpoolP160t1: return { name: ECDSA, namedCurve: brainpoolP160t1 }; case idBrainpoolP192r1: return { name: ECDSA, namedCurve: brainpoolP192r1 }; case idBrainpoolP192t1: return { name: ECDSA, namedCurve: brainpoolP192t1 }; case idBrainpoolP224r1: return { name: ECDSA, namedCurve: brainpoolP224r1 }; case idBrainpoolP224t1: return { name: ECDSA, namedCurve: brainpoolP224t1 }; case idBrainpoolP256r1: return { name: ECDSA, namedCurve: brainpoolP256r1 }; case idBrainpoolP256t1: return { name: ECDSA, namedCurve: brainpoolP256t1 }; case idBrainpoolP320r1: return { name: ECDSA, namedCurve: brainpoolP320r1 }; case idBrainpoolP320t1: return { name: ECDSA, namedCurve: brainpoolP320t1 }; case idBrainpoolP384r1: return { name: ECDSA, namedCurve: brainpoolP384r1 }; case idBrainpoolP384t1: return { name: ECDSA, namedCurve: brainpoolP384t1 }; case idBrainpoolP512r1: return { name: ECDSA, namedCurve: brainpoolP512r1 }; case idBrainpoolP512t1: return { name: ECDSA, namedCurve: brainpoolP512t1 }; } } } return null; } }; EcAlgorithm.SECP256K1 = "1.3.132.0.10"; EcAlgorithm = EcAlgorithm_1 = __decorate([ injectable() ], EcAlgorithm); container.registerSingleton(diAlgorithm, EcAlgorithm); const NAME = Symbol("name"); const VALUE = Symbol("value"); class TextObject { constructor(name, items = {}, value = "") { this[NAME] = name; this[VALUE] = value; for (const key in items) { this[key] = items[key]; } } } TextObject.NAME = NAME; TextObject.VALUE = VALUE; class DefaultAlgorithmSerializer { static toTextObject(alg) { const obj = new TextObject("Algorithm Identifier", {}, OidSerializer.toString(alg.algorithm)); if (alg.parameters) { switch (alg.algorithm) { case asn1Ecc.id_ecPublicKey: { const ecAlg = new EcAlgorithm().toWebAlgorithm(alg); if (ecAlg && "namedCurve" in ecAlg) { obj["Named Curve"] = ecAlg.namedCurve; } else { obj["Parameters"] = alg.parameters; } break; } default: obj["Parameters"] = alg.parameters; } } return obj; } } class OidSerializer { static toString(oid) { const name = this.items[oid]; if (name) { return name; } return oid; } } OidSerializer.items = { [asn1Rsa.id_sha1]: "sha1", [asn1Rsa.id_sha224]: "sha224", [asn1Rsa.id_sha256]: "sha256", [asn1Rsa.id_sha384]: "sha384", [asn1Rsa.id_sha512]: "sha512", [asn1Rsa.id_rsaEncryption]: "rsaEncryption", [asn1Rsa.id_sha1WithRSAEncryption]: "sha1WithRSAEncryption", [asn1Rsa.id_sha224WithRSAEncryption]: "sha224WithRSAEncryption", [asn1Rsa.id_sha256WithRSAEncryption]: "sha256WithRSAEncryption", [asn1Rsa.id_sha384WithRSAEncryption]: "sha384WithRSAEncryption", [asn1Rsa.id_sha512WithRSAEncryption]: "sha512WithRSAEncryption", [asn1Ecc.id_ecPublicKey]: "ecPublicKey", [asn1Ecc.id_ecdsaWithSHA1]: "ecdsaWithSHA1", [asn1Ecc.id_ecdsaWithSHA224]: "ecdsaWithSHA224", [asn1Ecc.id_ecdsaWithSHA256]: "ecdsaWithSHA256", [asn1Ecc.id_ecdsaWithSHA384]: "ecdsaWithSHA384", [asn1Ecc.id_ecdsaWithSHA512]: "ecdsaWithSHA512", [asn1X509.id_kp_serverAuth]: "TLS WWW server authentication", [asn1X509.id_kp_clientAuth]: "TLS WWW client authentication", [asn1X509.id_kp_codeSigning]: "Code Signing", [asn1X509.id_kp_emailProtection]: "E-mail Protection", [asn1X509.id_kp_timeStamping]: "Time Stamping", [asn1X509.id_kp_OCSPSigning]: "OCSP Signing", [asn1Cms.id_signedData]: "Signed Data", }; class TextConverter { static serialize(obj) { return this.serializeObj(obj).join("\n"); } static pad(deep = 0) { return "".padStart(2 * deep, " "); } static serializeObj(obj, deep = 0) { const res = []; let pad = this.pad(deep++); let value = ""; const objValue = obj[TextObject.VALUE]; if (objValue) { value = ` ${objValue}`; } res.push(`${pad}${obj[TextObject.NAME]}:${value}`); pad = this.pad(deep); for (const key in obj) { if (typeof key === "symbol") { continue; } const value = obj[key]; const keyValue = key ? `${key}: ` : ""; if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { res.push(`${pad}${keyValue}${value}`); } else if (value instanceof Date) { res.push(`${pad}${keyValue}${value.toUTCString()}`); } else if (Array.isArray(value)) { for (const obj of value) { obj[TextObject.NAME] = key; res.push(...this.serializeObj(obj, deep)); } } else if (value instanceof TextObject) { value[TextObject.NAME] = key; res.push(...this.serializeObj(value, deep)); } else if (BufferSourceConverter.isBufferSource(value)) { if (key) { res.push(`${pad}${keyValue}`); res.push(...this.serializeBufferSource(value, deep + 1)); } else { res.push(...this.serializeBufferSource(value, deep)); } } else if ("toTextObject" in value) { const obj = value.toTextObject(); obj[TextObject.NAME] = key; res.push(...this.serializeObj(obj, deep)); } else { throw new TypeError("Cannot serialize data in text format. Unsupported type."); } } return res; } static serializeBufferSource(buffer, deep = 0) { const pad = this.pad(deep); const view = BufferSourceConverter.toUint8Array(buffer); const res = []; for (let i = 0; i < view.length;) { const row = []; for (let j = 0; j < 16 && i < view.length; j++) { if (j === 8) { row.push(""); } const hex = view[i++].toString(16).padStart(2, "0"); row.push(hex); } res.push(`${pad}${row.join(" ")}`); } return res; } static serializeAlgorithm(alg) { return this.algorithmSerializer.toTextObject(alg); } } TextConverter.oidSerializer = OidSerializer; TextConverter.algorithmSerializer = DefaultAlgorithmSerializer; class AsnData { constructor(...args) { if (args.length === 1) { const asn = args[0]; this.rawData = AsnConvert.serialize(asn); this.onInit(asn); } else { const asn = AsnConvert.parse(args[0], args[1]); this.rawData = BufferSourceConverter.toArrayBuffer(args[0]); this.onInit(asn); } } equal(data) { if (data instanceof AsnData) { return isEqual(data.rawData, this.rawData); } return false; } toString(format = "text") { switch (format) { case "asn": return AsnConvert.toString(this.rawData); case "text": return TextConverter.serialize(this.toTextObject()); case "hex": return Convert.ToHex(this.rawData); case "base64": return Convert.ToBase64(this.rawData); case "base64url": return Convert.ToBase64Url(this.rawData); default: throw TypeError("Argument 'format' is unsupported value"); } } getTextName() { const constructor = this.constructor; return constructor.NAME; } toTextObject() { const obj = this.toTextObjectEmpty(); obj[""] = this.rawData; return obj; } toTextObjectEmpty(value) { return new TextObject(this.getTextName(), {}, value); } } AsnData.NAME = "ASN"; class Extension extends AsnData { constructor(...args) { let raw; if (BufferSourceConverter.isBufferSource(args[0])) { raw = BufferSourceConverter.toArrayBuffer(args[0]); } else { raw = AsnConvert.serialize(new Extension$1({ extnID: args[0], critical: args[1], extnValue: new OctetString(BufferSourceConverter.toArrayBuffer(args[2])), })); } super(raw, Extension$1); } onInit(asn) { this.type = asn.extnID; this.critical = asn.critical; this.value = asn.extnValue.buffer; } toTextObject() { const obj = this.toTextObjectWithoutValue(); obj[""] = this.value; return obj; } toTextObjectWithoutValue() { const obj = this.toTextObjectEmpty(this.critical ? "critical" : undefined); if (obj[TextObject.NAME] === Extension.NAME) { obj[TextObject.NAME] = OidSerializer.toString(this.type); } return obj; } } var _a; class CryptoProvider { static isCryptoKeyPair(data) { return data && data.privateKey && data.publicKey; } static isCryptoKey(data) { return data && data.usages && data.type && data.algorithm && data.extractable !== undefined; } constructor() { this.items = new Map(); this[_a] = "CryptoProvider"; if (typeof self !== "undefined" && typeof crypto !== "undefined") { this.set(CryptoProvider.DEFAULT, crypto); } else if (typeof global !== "undefined" && global.crypto && global.crypto.subtle) { this.set(CryptoProvider.DEFAULT, global.crypto); } } clear() { this.items.clear(); } delete(key) { return this.items.delete(key); } forEach(callbackfn, thisArg) { return this.items.forEach(callbackfn, thisArg); } has(key) { return this.items.has(key); } get size() { return this.items.size; } entries() { return this.items.entries(); } keys() { return this.items.keys(); } values() { return this.items.values(); } [Symbol.iterator]() { return this.items[Symbol.iterator](); } get(key = CryptoProvider.DEFAULT) { const crypto = this.items.get(key.toLowerCase()); if (!crypto) { throw new Error(`Cannot get Crypto by name '${key}'`); } return crypto; } set(key, value) { if (typeof key === "string") { if (!value) { throw new TypeError("Argument 'value' is required"); } this.items.set(key.toLowerCase(), value); } else { this.items.set(CryptoProvider.DEFAULT, key); } return this; } } _a = Symbol.toStringTag; CryptoProvider.DEFAULT = "default"; const cryptoProvider = new CryptoProvider(); const OID_REGEX = /^[0-2](?:\.[1-9][0-9]*)+$/; function isOID(id) { return new RegExp(OID_REGEX).test(id); } class NameIdentifier { constructor(names = {}) { this.items = {}; for (const id in names) { this.register(id, names[id]); } } get(idOrName) { return this.items[idOrName] || null; } findId(idOrName) { if (!isOID(idOrName)) { return this.get(idOrName); } return idOrName; } register(id, name) { this.items[id] = name; this.items[name] = id; } } const names = new NameIdentifier(); names.register("CN", "2.5.4.3"); names.register("L", "2.5.4.7"); names.register("ST", "2.5.4.8"); names.register("O", "2.5.4.10"); names.register("OU", "2.5.4.11"); names.register("C", "2.5.4.6"); names.register("DC", "0.9.2342.19200300.100.1.25"); names.register("E", "1.2.840.113549.1.9.1"); names.register("G", "2.5.4.42"); names.register("I", "2.5.4.43"); names.register("SN", "2.5.4.4"); names.register("T", "2.5.4.12"); function replaceUnknownCharacter(text, char) { return `\\${Convert.ToHex(Convert.FromUtf8String(char)).toUpperCase()}`; } function escape(data) { return data .replace(/([,+"\\<>;])/g, "\\$1") .replace(/^([ #])/, "\\$1") .replace(/([ ]$)/, "\\$1") .replace(/([\r\n\t])/, replaceUnknownCharacter); } class Name { static isASCII(text) { for (let i = 0; i < text.length; i++) { const code = text.charCodeAt(i); if (code > 0xFF) { return false; } } return true; } static isPrintableString(text) { return /^[A-Za-z0-9 '()+,-./:=?]*$/g.test(text); } constructor(data, extraNames = {}) { this.extraNames = new NameIdentifier(); this.asn = new Name$1(); for (const key in extraNames) { if (Object.prototype.hasOwnProperty.call(extraNames, key)) { const value = extraNames[key]; this.extraNames.register(key, value); } } if (typeof data === "string") { this.asn = this.fromString(data); } else if (data instanceof Name$1) { this.asn = data; } else if (BufferSourceConverter.isBufferSource(data)) { this.asn = AsnConvert.parse(data, Name$1); } else { this.asn = this.fromJSON(data); } } getField(idOrName) { const id = this.extraNames.findId(idOrName) || names.findId(idOrName); const res = []; for (const name of this.asn) { for (const rdn of name) { if (rdn.type === id) { res.push(rdn.value.toString()); } } } return res; } getName(idOrName) { return this.extraNames.get(idOrName) || names.get(idOrName); } toString() { return this.asn.map(rdn => rdn.map(o => { const type = this.getName(o.type) || o.type; const value = o.value.anyValue ? `#${Convert.ToHex(o.value.anyValue)}` : escape(o.value.toString()); return `${type}=${value}`; }) .join("+")) .join(", "); } toJSON() { var _a; const json = []; for (const rdn of this.asn) { const jsonItem = {}; for (const attr of rdn) { const type = this.getName(attr.type) || attr.type; (_a = jsonItem[type]) !== null && _a !== void 0 ? _a : (jsonItem[type] = []); jsonItem[type].push(attr.value.anyValue ? `#${Convert.ToHex(attr.value.anyValue)}` : attr.value.toString()); } json.push(jsonItem); } return json; } fromString(data) { const asn = new Name$1(); const regex = /(\d\.[\d.]*\d|[A-Za-z]+)=((?:"")|(?:".*?[^\\]")|(?:[^,+"\\](?=[,+]|$))|(?:[^,+].*?(?:[^\\][,+]))|(?:))([,+])?/g; let matches = null; let level = ","; while (matches = regex.exec(`${data},`)) { let [, type, value] = matches; const lastChar = value[value.length - 1]; if (lastChar === "," || lastChar === "+") { value = value.slice(0, value.length - 1); matches[3] = lastChar; } const next = matches[3]; type = this.getTypeOid(type); const attr = this.createAttribute(type, value); if (level === "+") { asn[asn.length - 1].push(attr); } else { asn.push(new RelativeDistinguishedName([attr])); } level = next; } return asn; } fromJSON(data) { const asn = new Name$1(); for (const item of data) { const asnRdn = new RelativeDistinguishedName(); for (const type in item) { const typeId = this.getTypeOid(type); const values = item[type]; for (const value of values) { const asnAttr = this.createAttribute(typeId, value); asnRdn.push(asnAttr); } } asn.push(asnRdn); } return asn; } getTypeOid(type) { if (!/[\d.]+/.test(type)) { type = this.getName(type) || ""; } if (!type) { throw new Error(`Cannot get OID for name type '${type}'`); } return type; } createAttribute(type, value) { const attr = new AttributeTypeAndValue({ type }); if (typeof value === "object") { for (const key in value) { switch (key) { case "ia5String": attr.value.ia5String = value[key]; break; case "utf8String": attr.value.utf8String = value[key]; break; case "universalString": attr.value.universalString = value[key]; break; case "bmpString": attr.value.bmpString = value[key]; break; case "printableString": attr.value.printableString = value[key]; break; } } } else if (value[0] === "#") { attr.value.anyValue = Convert.FromHex(value.slice(1)); } else { const processedValue = this.processStringValue(value); if (type === this.getName("E") || type === this.getName("DC")) { attr.value.ia5String = processedValue; } else { if (Name.isPrintableString(processedValue)) { attr.value.printableString = processedValue; } else { attr.value.utf8String = processedValue; } } } return attr; } processStringValue(value) { const quotedMatches = /"(.*?[^\\])?"/.exec(value); if (quotedMatches) { value = quotedMatches[1]; } return value .replace(/\\0a/ig, "\n") .replace(/\\0d/ig, "\r") .replace(/\\0g/ig, "\t") .replace(/\\(.)/g, "$1"); } toArrayBuffer() { return AsnConvert.serialize(this.asn); } async getThumbprint(...args) { var _a; let crypto; let algorithm = "SHA-1"; if (args.length >= 1 && !((_a = args[0]) === null || _a === void 0 ? void 0 : _a.subtle)) { algorithm = args[0] || algorithm; crypto = args[1] || cryptoProvider.get(); } else { crypto = args[0] || cryptoProvider.get(); } return await crypto.subtle.digest(algorithm, this.toArrayBuffer()); } } const ERR_GN_CONSTRUCTOR = "Cannot initialize GeneralName from ASN.1 data."; const ERR_GN_STRING_FORMAT = `${ERR_GN_CONSTRUCTOR} Unsupported string format in use.`; const ERR_GUID = `${ERR_GN_CONSTRUCTOR} Value doesn't match to GUID regular expression.`; const GUID_REGEX = /^([0-9a-f]{8})-?([0-9a-f]{4})-?([0-9a-f]{4})-?([0-9a-f]{4})-?([0-9a-f]{12})$/i; const id_GUID = "1.3.6.1.4.1.311.25.1"; const id_UPN = "1.3.6.1.4.1.311.20.2.3"; const DNS = "dns"; const DN = "dn"; const EMAIL = "email"; const IP = "ip"; const URL = "url"; const GUID = "guid"; const UPN = "upn"; const REGISTERED_ID = "id"; class GeneralName extends AsnData { constructor(...args) { let name; if (args.length === 2) { switch (args[0]) { case DN: { const derName = new Name(args[1]).toArrayBuffer(); const asnName = AsnConvert.parse(derName, asn1X509.Name); name = new asn1X509.GeneralName({ directoryName: asnName }); break; } case DNS: name = new asn1X509.GeneralName({ dNSName: args[1] }); break; case EMAIL: name = new asn1X509.GeneralName({ rfc822Name: args[1] }); break; case GUID: { const matches = new RegExp(GUID_REGEX, "i").exec(args[1]); if (!matches) { throw new Error("Cannot parse GUID value. Value doesn't match to regular expression"); } const hex = matches .slice(1) .map((o, i) => { if (i < 3) { return Convert.ToHex(new Uint8Array(Convert.FromHex(o)).reverse()); } return o; }) .join(""); name = new asn1X509.GeneralName({ otherName: new asn1X509.OtherName({ typeId: id_GUID, value: AsnConvert.serialize(new OctetString(Convert.FromHex(hex))), }), }); break; } case IP: name = new asn1X509.GeneralName({ iPAddress: args[1] }); break; case REGISTERED_ID: name = new asn1X509.GeneralName({ registeredID: args[1] }); break; case UPN: { name = new asn1X509.GeneralName({ otherName: new asn1X509.OtherName({ typeId: id_UPN, value: AsnConvert.serialize(AsnUtf8StringConverter.toASN(args[1])), }) }); break; } case URL: name = new asn1X509.GeneralName({ uniformResourceIdentifier: args[1] }); break; default: throw new Error("Cannot create GeneralName. Unsupported type of the name"); } } else if (BufferSourceConverter.isBufferSource(args[0])) { name = AsnConvert.parse(args[0], asn1X509.GeneralName); } else { name = args[0]; } super(name); } onInit(asn) { if (asn.dNSName != undefined) { this.type = DNS; this.value = asn.dNSName; } else if (asn.rfc822Name != undefined) { this.type = EMAIL; this.value = asn.rfc822Name; } else if (asn.iPAddress != undefined) { this.type = IP; this.value = asn.iPAddress; } else if (asn.uniformResourceIdentifier != undefined) { this.type = URL; this.value = asn.uniformResourceIdentifier; } else if (asn.registeredID != undefined) { this.type = REGISTERED_ID; this.value = asn.registeredID; } else if (asn.directoryName != undefined) { this.type = DN; this.value = new Name(asn.directoryName).toString(); } else if (asn.otherName != undefined) { if (asn.otherName.typeId === id_GUID) { this.type = GUID; const guid = AsnConvert.parse(asn.otherName.value, OctetString); const matches = new RegExp(GUID_REGEX, "i").exec(Convert.ToHex(guid)); if (!matches) { throw new Error(ERR_GUID); } this.value = matches .slice(1) .map((o, i) => { if (i < 3) { return Convert.ToHex(new Uint8Array(Convert.FromHex(o)).reverse()); } return o; }) .join("-"); } else if (asn.otherName.typeId === id_UPN) { this.type = UPN; this.value = AsnConvert.parse(asn.otherName.value, asn1X509.DirectoryString).toString(); } else { throw new Error(ERR_GN_STRING_FORMAT); } } else { throw new Error(ERR_GN_STRING_FORMAT); } } toJSON() { return { type: this.type, value: this.value, }; } toTextObject() { let type; switch (this.type) { case DN: case DNS: case GUID: case IP: case REGISTERED_ID: case UPN: case URL: type = this.type.toUpperCase(); break; case EMAIL: type = "Email"; break; default: throw new Error("Unsupported GeneralName type"); } let value = this.value; if (this.type === REGISTERED_ID) { value = OidSerializer.toString(value); } return new TextObject(type, undefined, value); } } class GeneralNames extends AsnData { constructor(params) { let names; if (params instanceof asn1X509.GeneralNames) { names = params; } else if (Array.isArray(params)) { const items = []; for (const name of params) { if (name instanceof asn1X509.GeneralName) { items.push(name); } else { const asnName = AsnConvert.parse(new GeneralName(name.type, name.value).rawData, asn1X509.GeneralName); items.push(asnName); } } names = new asn1X509.GeneralNames(items); } else if (BufferSourceConverter.isBufferSource(params)) { names = AsnConvert.parse(params, asn1X509.GeneralNames); } else { throw new Error("Cannot initialize GeneralNames. Incorrect incoming arguments"); } super(names); } onInit(asn) { const items = []; for (const asnName of asn) { let name = null; try { name = new GeneralName(asnName); } catch { continue; } items.push(name); } this.items = items; } toJSON() { return this.items.map(o => o.toJSON()); } toTextObject() { const res = super.toTextObjectEmpty(); for (const name of this.items) { const nameObj = name.toTextObject(); let field = res[nameObj[TextObject.NAME]]; if (!Array.isArray(field)) { field = []; res[nameObj[TextObject.NAME]] = field; } field.push(nameObj); } return res; } } GeneralNames.NAME = "GeneralNames"; const rPaddingTag = "-{5}"; const rEolChars = "\\n"; const rNameTag = `[^${rEolChars}]+`; const rBeginTag = `${rPaddingTag}BEGIN (${rNameTag}(?=${rPaddingTag}))${rPaddingTag}`; const rEndTag = `${rPaddingTag}END \\1${rPaddingTag}`; const rEolGroup = "\\n"; const rHeaderKey = `[^:${rEolChars}]+`; const rHeaderValue = `(?:[^${rEolChars}]+${rEolGroup}(?: +[^${rEolChars}]+${rEolGroup})*)`; const rBase64Chars = "[a-zA-Z0-9=+/]+"; const rBase64 = `(?:${rBase64Chars}${rEolGroup})+`; const rPem = `${rBeginTag}${rEolGroup}(?:((?:${rHeaderKey}: ${rHeaderValue})+))?${rEolGroup}?(${rBase64})${rEndTag}`; class PemConverter { static isPem(data) { return typeof data === "string" && new RegExp(rPem, "g").test(data.replace(/\r/g, "")); } static decodeWithHeaders(pem) { pem = pem.replace(/\r/g, ""); const pattern = new RegExp(rPem, "g"); const res = []; let matches = null; while (matches = pattern.exec(pem)) { const base64 = matches[3] .replace(new RegExp(`[${rEolChars}]+`, "g"), ""); const pemStruct = { type: matches[1], headers: [], rawData: Convert.FromBase64(base64), }; const headersString = matches[2]; if (headersString) { const headers = headersString.split(new RegExp(rEolGroup, "g")); let lastHeader = null; for (const header of headers) { const [key, value] = header.split(/:(.*)/); if (value === undefined) { if (!lastHeader) { throw new Error("Cannot parse PEM string. Incorrect header value"); } lastHeader.value += key.trim(); } else { if (lastHeader) { pemStruct.headers.push(lastHeader); } lastHeader = { key, value: value.trim() }; } } if (lastHeader) { pemStruct.headers.push(lastHeader); } } res.push(pemStruct); } return res; } static decode(pem) { const blocks = this.decodeWithHeaders(pem); return blocks.map(o => o.rawData); } static decodeFirst(pem) { const items = this.decode(pem); if (!items.length) { throw new RangeError("PEM string doesn't contain any objects"); } return items[0]; } static encode(rawData, tag) { if (Array.isArray(rawData)) { const raws = new Array(); if (tag) { rawData.forEach(element => { if (!BufferSourceConverter.isBufferSource(element)) { throw new TypeError("Cannot encode array of BufferSource in PEM format. Not all items of the array are BufferSource"); } raws.push(this.encodeStruct({ type: tag, rawData: BufferSourceConverter.toArrayBuffer(element), })); }); } else { rawData.forEach(element => { if (!("type" in element)) { throw new TypeError("Cannot encode array of PemStruct in PEM format. Not all items of the array are PemStrut"); } raws.push(this.encodeStruct(element)); }); } return raws.join("\n"); } else { if (!tag) { throw new Error("Required argument 'tag' is missed"); } return this.encodeStruct({ type: tag, rawData: BufferSourceConverter.toArrayBuffer(rawData), }); } } static encodeStruct(pem) { var _a; const upperCaseType = pem.type.toLocaleUpperCase(); const res = []; res.push(`-----BEGIN ${upperCaseType}-----`); if ((_a = pem.headers) === null || _a === void 0 ? void 0 : _a.length) { for (const header of pem.headers) { res.push(`${header.key}: ${header.value}`); } res.push(""); } const base64 = Convert.ToBase64(pem.rawData); let sliced; let offset = 0; const rows = Array(); while (offset < base64.length) { if (base64.length - offset < 64) { sliced = base64.substring(offset); } else { sliced = base64.substring(offset, offset + 64); offset += 64; } if (sliced.length !== 0) { rows.push(sliced); if (sliced.length < 64) { break; } } else { break; } } res.push(...rows); res.push(`-----END ${upperCaseType}-----`); return res.join("\n"); } } PemConverter.CertificateTag = "CERTIFICATE"; PemConverter.CrlTag = "CRL"; PemConverter.CertificateRequestTag = "CERTIFICATE REQUEST"; PemConverter.PublicKeyTag = "PUBLIC KEY"; PemConverter.PrivateKeyTag = "PRIVATE KEY"; class PemData extends AsnData { static isAsnEncoded(data) { return BufferSourceConverter.isBufferSource(data) || typeof data === "string"; } static toArrayBuffer(raw) { if (typeof raw === "string") { if (PemConverter.isPem(raw)) { return PemConverter.decode(raw)[0]; } else if (Convert.isHex(raw)) { return Convert.FromHex(raw); } else if (Convert.isBase64(raw)) { return Convert.FromBase64(raw); } else if (Convert.isBase64Url(raw)) { return Convert.FromBase64Url(raw); } else { throw new TypeError("Unsupported format of 'raw' argument. Must be one of DER, PEM, HEX, Base64, or Base4Url"); } } else { const stringRaw = Convert.ToBinary(raw); if (PemConverter.isPem(stringRaw)) { return PemConverter.decode(stringRaw)[0]; } else if (Convert.isHex(stringRaw)) { return Convert.FromHex(stringRaw); } else if (Convert.isBase64(stringRaw)) { return Convert.FromBase64(stringRaw); } else if (Convert.isBase64Url(stringRaw)) { return Convert.FromBase64Url(stringRaw); } return BufferSourceConverter.toArrayBuffer(raw); } } constructor(...args) { if (PemData.isAsnEncoded(args[0])) { super(PemData.toArrayBuffer(args[0]), args[1]); } else { super(args[0]); } } toString(format = "pem") { switch (format) { case "pem": return PemConverter.encode(this.rawData, this.tag); default: return super.toString(format); } } } class PublicKey extends PemData { static async create(data, crypto = cryptoProvider.get()) { if (data instanceof PublicKey) { return data; } else if (CryptoProvider.isCryptoKey(data)) { if (data.type !== "public") { throw new TypeError("Public key is required"); } const spki = await crypto.subtle.exportKey("spki", data); return new PublicKey(spki); } else if (data.publicKey) { return data.publicKey; } else if (BufferSourceConverter.isBufferSource(data)) { return new PublicKey(data); } else { throw new TypeError("Unsupported PublicKeyType"); } } constructor(param) { if (PemData.isAsnEncoded(param)) { super(param, SubjectPublicKeyInfo); } else { super(param); } this.tag = PemConverter.PublicKeyTag; } async export(...args) { let crypto; let keyUsages = ["verify"]; let algorithm = { hash: "SHA-256", ...this.algorithm }; if (args.length > 1) { algorithm = args[0] || algorithm; keyUsages = args[1] || keyUsages; crypto = args[2] || cryptoProvider.get(); } else { crypto = args[0] || cryptoProvider.get(); } let raw = this.rawData; const asnSpki = AsnConvert.parse(this.rawData, SubjectPublicKeyInfo); if (asnSpki.algorithm.algorithm === id_RSASSA_PSS) { raw = convertSpkiToRsaPkcs1(asnSpki, raw); } return crypto.subtle.importKey("spki", raw, algorithm, true, keyUsages); } onInit(asn) { const algProv = container.resolve(diAlgorithmProvider); const algorithm = this.algorithm = algProv.toWebAlgorithm(asn.algorithm); switch (asn.algorithm.algorithm) { case id_rsaEncryption: { const rsaPublicKey = AsnConvert.parse(asn.subjectPublicKey, RSAPublicKey); const modulus = BufferSourceConverter.toUint8Array(rsaPublicKey.modulus); algorithm.publicExponent = BufferSourceConverter.toUint8Array(rsaPublicKey.publicExponent); algorithm.modulusLength = (!modulus[0] ? modulus.slice(1) : modulus).byteLength << 3; break; } } } async getThumbprint(...args) { var _a; let crypto; let algorithm = "SHA-1"; if (args.length >= 1 && !((_a = args[0]) === null || _a === void 0 ? void 0 : _a.subtle)) { algorithm = args[0] || algorithm; crypto = args[1] || cryptoProvider.get(); } else { crypto = args[0] || cryptoProvider.get(); } return await crypto.subtle.digest(algorithm, this.rawData); } async getKeyIdentifier(...args) { let crypto; let algorithm = "SHA-1"; if (args.length === 1) { if (typeof args[0] === "string") { algorithm = args[0]; crypto = cryptoProvider.get(); } else { crypto = args[0]; } } else if (args.length === 2) { algorithm = args[0]; crypto = args[1]; } else { crypto = cryptoProvider.get(); } const asn = AsnConvert.parse(this.rawData, SubjectPublicKeyInfo); return await crypto.subtle.digest(algorithm, asn.subjectPublicKey); } toTextObject() { const obj = this.toTextObjectEmpty(); const asn = AsnConvert.parse(this.rawData, SubjectPublicKeyInfo); obj["Algorithm"] = TextConverter.serializeAlgorithm(asn.algorithm); switch (asn.algorithm.algorithm) { case id_ecPublicKey: obj["EC Point"] = asn.subjectPublicKey; break; case id_rsaEncryption: default: obj["Raw Data"] = asn.subjectPublicKey; } return obj; } } function convertSpkiToRsaPkcs1(asnSpki, raw) { asnSpki.algorithm = new AlgorithmIdentifier({ algorithm: id_rsaEncryption, parameters: null, }); raw = AsnConvert.serialize(asnSpki); return raw; } class AuthorityKeyIdentifierExtension extends Extension { static async create