UNPKG

asn1-ts

Version:

ASN.1 encoding and decoding, including BER, CER, and DER.

353 lines (352 loc) 16.3 kB
import * as errors from "./errors.mjs"; import { ASN1Construction, ASN1TagClass, ASN1UniversalType } from "./values.mjs"; import packBits from "./utils/packBits.mjs"; import { Buffer } from "node:buffer"; class ASN1Element { constructor() { this.recursionCount = 0; this.name = ""; this.tagClass = ASN1TagClass.universal; this.construction = ASN1Construction.primitive; this._tagNumber = 0; } get tagNumber() { return this._tagNumber; } set tagNumber(value) { if (!Number.isSafeInteger(value) || (value < 0)) { throw new errors.ASN1Error(`Tag ${value} was not a non-negative integer.`); } this._tagNumber = value; } tagLength() { if (this.tagNumber < 31) { return 1; } let n = this.tagNumber; let i = 0; while (n !== 0) { n >>>= 7; i++; } return i; } toBytes() { return Buffer.concat(this.toBuffers()); } get length() { return this.value.length; } validateSize(name, units, actualSize, min, max) { const effectiveMax = (typeof max === "undefined" ? Infinity : max); if (actualSize < min) { throw new errors.ASN1SizeError(`${name} encoded ${actualSize} ${units} when the ` + `minimum permissible is ${min} ${units}.`); } if (actualSize > effectiveMax) { throw new errors.ASN1SizeError(`${name} encoded ${actualSize} ${units} when the ` + `maximum permissible is ${effectiveMax} ${units}.`); } } validateRange(name, actualValue, min, max) { if (actualValue < min) { throw new errors.ASN1OverflowError(`${name} was ${actualValue} when the ` + `minimum permissible is ${min}.`); } if (max === undefined) { return; } if (actualValue > max) { throw new errors.ASN1OverflowError(`${name} was ${actualValue} when the ` + `maximum permissible is ${max}.`); } } sizeConstrainedBitString(min, max) { const ret = this.bitString; this.validateSize(this.name || "BIT STRING", "bits", ret.length, min, max); return ret; } sizeConstrainedOctetString(min, max) { const ret = this.octetString; this.validateSize(this.name || "OCTET STRING", "octets", ret.length, min, max); return ret; } sizeConstrainedObjectDescriptor(min, max) { const ret = this.objectDescriptor; this.validateSize(this.name || "ObjectDescriptor", "characters", ret.length, min, max); return ret; } sizeConstrainedUTF8String(min, max) { const ret = this.utf8String; this.validateSize(this.name || "UTF8String", "characters", ret.length, min, max); return ret; } sizeConstrainedSequenceOf(min, max) { const ret = this.sequenceOf; this.validateSize(this.name || "SEQUENCE OF", "elements", ret.length, min, max); return ret; } sizeConstrainedSetOf(min, max) { const ret = this.setOf; this.validateSize(this.name || "SET OF", "elements", ret.length, min, max); return ret; } sizeConstrainedNumericString(min, max) { const ret = this.numericString; this.validateSize(this.name || "NumericString", "characters", ret.length, min, max); return ret; } sizeConstrainedPrintableString(min, max) { const ret = this.printableString; this.validateSize(this.name || "PrintableString", "characters", ret.length, min, max); return ret; } sizeConstrainedTeletexString(min, max) { const ret = this.teletexString; this.validateSize(this.name || "TeletexString", "characters", ret.length, min, max); return ret; } sizeConstrainedVideotexString(min, max) { const ret = this.videotexString; this.validateSize(this.name || "VideotexString", "characters", ret.length, min, max); return ret; } sizeConstrainedIA5String(min, max) { const ret = this.ia5String; this.validateSize(this.name || "IA5String", "characters", ret.length, min, max); return ret; } sizeConstrainedGraphicString(min, max) { const ret = this.graphicString; this.validateSize(this.name || "GraphicString", "characters", ret.length, min, max); return ret; } sizeConstrainedVisibleString(min, max) { const ret = this.visibleString; this.validateSize(this.name || "VisibleString", "characters", ret.length, min, max); return ret; } sizeConstrainedGeneralString(min, max) { const ret = this.generalString; this.validateSize(this.name || "GeneralString", "characters", ret.length, min, max); return ret; } sizeConstrainedUniversalString(min, max) { const ret = this.universalString; this.validateSize(this.name || "UniversalString", "characters", ret.length, min, max); return ret; } sizeConstrainedBMPString(min, max) { const ret = this.bmpString; this.validateSize(this.name || "BMPString", "characters", ret.length, min, max); return ret; } rangeConstrainedInteger(min, max) { const ret = this.integer; this.validateRange(this.name || "INTEGER", ret, min, max); return ret; } rangeConstrainedReal(min, max) { const ret = this.real; this.validateRange(this.name || "REAL", ret, min, max); return ret; } validateTag(permittedClasses, permittedConstruction, permittedNumbers) { if (!permittedClasses.includes(this.tagClass)) return -1; if (!permittedConstruction.includes(this.construction)) return -2; if (!permittedNumbers.includes(this.tagNumber)) return -3; return 0; } toElement() { return this; } fromElement(el) { this.tagClass = el.tagClass; this.construction = el.construction; this.tagNumber = el.tagNumber; this.value = new Uint8Array(el.value); } toString() { if (this.tagClass === ASN1TagClass.universal) { switch (this.tagNumber) { case (ASN1UniversalType.endOfContent): return "END-OF-CONTENT"; case (ASN1UniversalType.boolean): return (this.boolean ? "TRUE" : "FALSE"); case (ASN1UniversalType.integer): return this.integer.toString(); case (ASN1UniversalType.bitString): return `'${Array .from(this.bitString) .map((num) => num.toString()) .join("")}'B`; case (ASN1UniversalType.octetString): return `'${Array .from(this.octetString) .map((byte) => byte.toString(16).padStart(2, "0")) .join("")}'H`; case (ASN1UniversalType.nill): return "NULL"; case (ASN1UniversalType.objectIdentifier): return this.objectIdentifier.asn1Notation; case (ASN1UniversalType.objectDescriptor): return `"${this.objectDescriptor}"`; case (ASN1UniversalType.external): return "EXTERNAL"; case (ASN1UniversalType.realNumber): return this.real.toString(); case (ASN1UniversalType.enumerated): return this.enumerated.toString(); case (ASN1UniversalType.embeddedPDV): return "EMBEDDED PDV"; case (ASN1UniversalType.utf8String): return `"${this.utf8String}"`; case (ASN1UniversalType.relativeOID): return "{ " + this.relativeObjectIdentifier .map((arc) => arc.toString()).join(".") + " }"; case (ASN1UniversalType.time): return `"${this.time}"`; case (ASN1UniversalType.sequence): return ("{ " + this.sequenceOf .map((el) => (el.name.length ? `${el.name} ${el.toString()}` : el.toString())) .join(" , ") + " }"); case (ASN1UniversalType.set): return ("{ " + this.setOf .map((el) => (el.name.length ? `${el.name} ${el.toString()}` : el.toString())) .join(" , ") + " }"); case (ASN1UniversalType.numericString): return `"${this.numericString}"`; case (ASN1UniversalType.printableString): return `"${this.printableString}"`; case (ASN1UniversalType.teletexString): return "TeletexString"; case (ASN1UniversalType.videotexString): return "VideotexString"; case (ASN1UniversalType.ia5String): return `"${this.ia5String}"`; case (ASN1UniversalType.utcTime): return `"${this.utcTime.toISOString()}"`; case (ASN1UniversalType.generalizedTime): return `"${this.generalizedTime.toISOString()}"`; case (ASN1UniversalType.graphicString): return `"${this.graphicString}"`; case (ASN1UniversalType.visibleString): return `"${this.visibleString}"`; case (ASN1UniversalType.generalString): return `"${this.generalString}"`; case (ASN1UniversalType.universalString): return `"${this.universalString}"`; case (ASN1UniversalType.characterString): return "CHARACTER STRING"; case (ASN1UniversalType.bmpString): return `"${this.bmpString}"`; case (ASN1UniversalType.date): return `"${this.date.toISOString()}"`; case (ASN1UniversalType.timeOfDay): { const tod = this.timeOfDay; return `"${tod.getUTCHours()}:${tod.getUTCMinutes()}:${tod.getUTCSeconds()}"`; } case (ASN1UniversalType.dateTime): return `"${this.dateTime.toISOString()}"`; case (ASN1UniversalType.duration): return this.duration.toString(); case (ASN1UniversalType.oidIRI): return this.oidIRI; case (ASN1UniversalType.roidIRI): return this.relativeOIDIRI; default: { return `[UNIV ${this.tagNumber}]: ${this.value.toString()}`; } } } else if (this.construction === ASN1Construction.constructed) { const inner = this.components; if (inner.length === 1) { return inner[0].toString(); } else { return "{ " + inner.map((el) => el.toString()).join(", ") + " }"; } } else if (this.tagClass === ASN1TagClass.context) { return `[CTXT ${this.tagNumber}]: ${this.value.toString()}`; } else if (this.tagClass === ASN1TagClass.private) { return `[PRIV ${this.tagNumber}]: ${this.value.toString()}`; } else { return `[APPL ${this.tagNumber}]: ${this.value.toString()}`; } } toJSON(recurse = true) { if (this.tagClass === ASN1TagClass.universal) { switch (this.tagNumber) { case (ASN1UniversalType.endOfContent): return undefined; case (ASN1UniversalType.boolean): return this.boolean; case (ASN1UniversalType.integer): { const ret = this.integer; if (typeof ret === "bigint") { return ret.toString(); } return ret; } case (ASN1UniversalType.bitString): { const bits = this.bitString; return { length: bits.length, value: Array.from(packBits(bits)).map((byte) => byte.toString(16)).join(""), }; } case (ASN1UniversalType.octetString): return Array.from(this.octetString) .map((byte) => byte.toString(16)).join(""); case (ASN1UniversalType.nill): return null; case (ASN1UniversalType.objectIdentifier): return this.objectIdentifier.toJSON(); case (ASN1UniversalType.objectDescriptor): return this.objectDescriptor; case (ASN1UniversalType.external): return this.external.toJSON(); case (ASN1UniversalType.realNumber): { const r = this.real; if (Object.is(r, -0)) { return "-0"; } if (r === -Infinity) { return "-INF"; } if (r === Infinity) { return "INF"; } if (Number.isNaN(r)) { return "NaN"; } return r.toString(); } case (ASN1UniversalType.enumerated): return this.enumerated.toString(); case (ASN1UniversalType.embeddedPDV): return this.embeddedPDV.toJSON(); case (ASN1UniversalType.utf8String): return this.utf8String; case (ASN1UniversalType.relativeOID): return this.relativeObjectIdentifier .map((arc) => arc.toString()).join("."); case (ASN1UniversalType.time): return this.time; case (ASN1UniversalType.sequence): { if (!recurse) { return null; } return this.sequenceOf.map((el) => el.toJSON()); } case (ASN1UniversalType.set): { if (!recurse) { return null; } return this.setOf.map((el) => el.toJSON()); } case (ASN1UniversalType.numericString): return this.numericString; case (ASN1UniversalType.printableString): return this.printableString; case (ASN1UniversalType.teletexString): return String.fromCodePoint(...Array.from(this.teletexString)); case (ASN1UniversalType.videotexString): return String.fromCodePoint(...Array.from(this.videotexString)); case (ASN1UniversalType.ia5String): return this.ia5String; case (ASN1UniversalType.utcTime): return this.utcTime.toISOString(); case (ASN1UniversalType.generalizedTime): return this.generalizedTime.toISOString(); case (ASN1UniversalType.graphicString): return this.graphicString; case (ASN1UniversalType.visibleString): return this.visibleString; case (ASN1UniversalType.generalString): return this.generalString; case (ASN1UniversalType.universalString): return this.universalString; case (ASN1UniversalType.characterString): return this.characterString.toJSON(); case (ASN1UniversalType.bmpString): return this.bmpString; case (ASN1UniversalType.date): return this.date.toISOString(); case (ASN1UniversalType.timeOfDay): { const tod = this.timeOfDay; return `${tod.getUTCHours()}:${tod.getUTCMinutes()}:${tod.getUTCSeconds()}`; } case (ASN1UniversalType.dateTime): return this.dateTime.toISOString(); case (ASN1UniversalType.duration): return this.duration.toString(); case (ASN1UniversalType.oidIRI): return this.oidIRI; case (ASN1UniversalType.roidIRI): return this.relativeOIDIRI; default: { return undefined; } } } else if ((this.construction === ASN1Construction.constructed) && recurse) { const inner = this.components; if (inner.length === 1) { return inner[0].toJSON(); } else { return inner.map((el) => el.toJSON()); } } else { return undefined; } } } ASN1Element.nestingRecursionLimit = 5; export default ASN1Element;