UNPKG

asn1-ts

Version:

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

711 lines (710 loc) 29.1 kB
import ASN1Element from "../asn1.mjs"; import * as errors from "../errors.mjs"; import { ASN1Construction, ASN1TagClass, ASN1UniversalType, LengthEncodingPreference, } from "../values.mjs"; import X690Element from "../x690.mjs"; import CharacterString from "../types/CharacterString.mjs"; import convertBytesToText from "../utils/convertBytesToText.mjs"; import convertTextToBytes from "../utils/convertTextToBytes.mjs"; import ObjectIdentifier from "../types/ObjectIdentifier.mjs"; import encodeBoolean from "./x690/encoders/encodeBoolean.mjs"; import decodeBoolean from "./ber/decoders/decodeBoolean.mjs"; import encodeBitString from "./x690/encoders/encodeBitString.mjs"; import decodeBitString from "./ber/decoders/decodeBitString.mjs"; import encodeReal from "./x690/encoders/encodeReal.mjs"; import decodeReal from "./ber/decoders/decodeReal.mjs"; import encodeSequence from "./x690/encoders/encodeSequence.mjs"; import decodeSequence from "./ber/decoders/decodeSequence.mjs"; import encodeUTCTime from "./x690/encoders/encodeUTCTime.mjs"; import decodeUTCTime from "./ber/decoders/decodeUTCTime.mjs"; import encodeGeneralizedTime from "./x690/encoders/encodeGeneralizedTime.mjs"; import decodeGeneralizedTime from "./ber/decoders/decodeGeneralizedTime.mjs"; import encodeExternal from "../codecs/x690/encoders/encodeExternal.mjs"; import encodeEmbeddedPDV from "../codecs/x690/encoders/encodeEmbeddedPDV.mjs"; import encodeCharacterString from "../codecs/x690/encoders/encodeCharacterString.mjs"; import decodeExternal from "../codecs/x690/decoders/decodeExternal.mjs"; import decodeEmbeddedPDV from "../codecs/x690/decoders/decodeEmbeddedPDV.mjs"; import decodeCharacterString from "../codecs/x690/decoders/decodeCharacterString.mjs"; import encodeGraphicString from "../codecs/ber/encoders/encodeGraphicString.mjs"; import encodeNumericString from "../codecs/ber/encoders/encodeNumericString.mjs"; import encodeObjectDescriptor from "../codecs/ber/encoders/encodeObjectDescriptor.mjs"; import encodePrintableString from "../codecs/ber/encoders/encodePrintableString.mjs"; import encodeVisibleString from "../codecs/ber/encoders/encodeVisibleString.mjs"; import encodeGeneralString from "../codecs/ber/encoders/encodeGeneralString.mjs"; import decodeGraphicString from "../codecs/x690/decoders/decodeGraphicString.mjs"; import decodeNumericString from "../codecs/x690/decoders/decodeNumericString.mjs"; import decodeObjectDescriptor from "../codecs/x690/decoders/decodeObjectDescriptor.mjs"; import decodePrintableString from "../codecs/x690/decoders/decodePrintableString.mjs"; import decodeVisibleString from "../codecs/x690/decoders/decodeVisibleString.mjs"; import decodeGeneralString from "../codecs/x690/decoders/decodeGeneralString.mjs"; import encodeDuration from "../codecs/x690/encoders/encodeDuration.mjs"; import decodeDuration from "../codecs/ber/decoders/decodeDuration.mjs"; import { FALSE_BIT } from "../macros.mjs"; import { isUniquelyTagged } from "../utils/index.mjs"; import { Buffer } from "node:buffer"; class BERElement extends X690Element { get value() { if (this._value instanceof Uint8Array) { return this._value; } const bytes = encodeSequence(this._value); this._value = bytes; return bytes; } set value(v) { this._currentValueLength = v.length; this._value = v; } construct(els) { this._currentValueLength = undefined; this._value = els; } set boolean(value) { this.value = encodeBoolean(value); } get boolean() { if (this.construction !== ASN1Construction.primitive) { throw new errors.ASN1ConstructionError("BOOLEAN cannot be constructed.", this); } return decodeBoolean(this.value); } set bitString(value) { this.value = encodeBitString(value); } get bitString() { if (this.construction === ASN1Construction.primitive) { return decodeBitString(this.value); } if ((this.recursionCount + 1) > BERElement.nestingRecursionLimit) { throw new errors.ASN1RecursionError(); } const appendy = []; const substrings = this.sequence; for (const substring of substrings.slice(0, (substrings.length - 1))) { if (substring.construction === ASN1Construction.primitive && substring.value.length > 0 && substring.value[0] !== 0x00) { throw new errors.ASN1Error("Only the last subelement of a constructed BIT STRING may have a non-zero first value byte.", this); } } for (const substring of substrings) { if (substring.tagClass !== this.tagClass) { throw new errors.ASN1ConstructionError("Invalid tag class in recursively-encoded BIT STRING.", this); } if (substring.tagNumber !== this.tagNumber) { throw new errors.ASN1ConstructionError("Invalid tag class in recursively-encoded BIT STRING.", this); } substring.recursionCount = (this.recursionCount + 1); appendy.push(...Array.from(substring.bitString).map((b) => b !== FALSE_BIT)); } return new Uint8ClampedArray(appendy.map((b) => (b ? 1 : 0))); } set octetString(value) { this.value = new Uint8Array(value); } get octetString() { return this.deconstruct("OCTET STRING"); } set objectDescriptor(value) { this.value = encodeObjectDescriptor(value); } get objectDescriptor() { const bytes = this.deconstruct("ObjectDescriptor"); return decodeObjectDescriptor(bytes); } set external(value) { this.value = encodeExternal(value); this.construction = ASN1Construction.constructed; } get external() { return decodeExternal(this.value); } set real(value) { this.value = encodeReal(value); } get real() { if (this.construction !== ASN1Construction.primitive) { throw new errors.ASN1ConstructionError("REAL cannot be constructed."); } return decodeReal(this.value); } set embeddedPDV(value) { this.value = encodeEmbeddedPDV(value); this.construction = ASN1Construction.constructed; } get embeddedPDV() { return decodeEmbeddedPDV(this.value); } set utf8String(value) { this.value = convertTextToBytes(value); } get utf8String() { return convertBytesToText(this.deconstruct("UTF8String")); } set sequence(value) { this.construct(value); this.construction = ASN1Construction.constructed; } get sequence() { if (this.construction !== ASN1Construction.constructed) { throw new errors.ASN1ConstructionError("SET or SEQUENCE cannot be primitively constructed.", this); } if (Array.isArray(this._value)) { return this._value; } return decodeSequence(this.value); } set set(value) { this.sequence = value; } get set() { const ret = this.sequence; if (!isUniquelyTagged(ret)) { throw new errors.ASN1ConstructionError("Duplicate tag in SET.", this); } return ret; } set sequenceOf(value) { this.construct(value); this.construction = ASN1Construction.constructed; } get sequenceOf() { if (this.construction !== ASN1Construction.constructed) { throw new errors.ASN1ConstructionError("SET or SEQUENCE cannot be primitively constructed.", this); } if (Array.isArray(this._value)) { return this._value; } return decodeSequence(this.value); } set setOf(value) { this.sequence = value; } get setOf() { return this.sequence; } set numericString(value) { this.value = encodeNumericString(value); } get numericString() { const bytes = this.deconstruct("NumericString"); return decodeNumericString(bytes); } set printableString(value) { this.value = encodePrintableString(value); } get printableString() { const bytes = this.deconstruct("PrintableString"); return decodePrintableString(bytes); } set teletexString(value) { this.value = new Uint8Array(value); } get teletexString() { return this.deconstruct("TeletexString"); } set videotexString(value) { this.value = new Uint8Array(value); } get videotexString() { return this.deconstruct("VideotexString"); } set ia5String(value) { this.value = convertTextToBytes(value); } get ia5String() { return convertBytesToText(this.deconstruct("IA5String")); } set utcTime(value) { this.value = encodeUTCTime(value); } get utcTime() { return decodeUTCTime(this.deconstruct("UTCTime")); } set generalizedTime(value) { this.value = encodeGeneralizedTime(value); } get generalizedTime() { return decodeGeneralizedTime(this.deconstruct("GeneralizedTime")); } set graphicString(value) { this.value = encodeGraphicString(value); } get graphicString() { const bytes = this.deconstruct("GraphicString"); return decodeGraphicString(bytes); } set visibleString(value) { this.value = encodeVisibleString(value); } get visibleString() { return decodeVisibleString(this.value); } set generalString(value) { this.value = encodeGeneralString(value); } get generalString() { const bytes = this.deconstruct("GeneralString"); return decodeGeneralString(bytes); } set characterString(value) { this.value = encodeCharacterString(value); this.construction = ASN1Construction.constructed; } get characterString() { return decodeCharacterString(this.value); } set universalString(value) { const buf = new Uint8Array(value.length << 2); for (let i = 0; i < value.length; i++) { buf[(i << 2)] = value.charCodeAt(i) >>> 24; buf[(i << 2) + 1] = value.charCodeAt(i) >>> 16; buf[(i << 2) + 2] = value.charCodeAt(i) >>> 8; buf[(i << 2) + 3] = value.charCodeAt(i); } this.value = buf; } get universalString() { const valueBytes = this.deconstruct("UniversalString"); if (valueBytes.length % 4) { throw new errors.ASN1Error("UniversalString encoded on non-mulitple of four bytes.", this); } let ret = ""; for (let i = 0; i < valueBytes.length; i += 4) { ret += String.fromCharCode((valueBytes[i + 0] << 24) + (valueBytes[i + 1] << 16) + (valueBytes[i + 2] << 8) + (valueBytes[i + 3] << 0)); } return ret; } set bmpString(value) { const buf = new Uint8Array(value.length << 1); for (let i = 0, strLen = value.length; i < strLen; i++) { buf[(i << 1)] = value.charCodeAt(i) >>> 8; buf[(i << 1) + 1] = value.charCodeAt(i); } this.value = buf; } get bmpString() { const valueBytes = this.deconstruct("BMPString"); if (valueBytes.length % 2) throw new errors.ASN1Error("BMPString encoded on non-mulitple of two bytes.", this); if (typeof Buffer !== "undefined") { const swappedEndianness = Buffer.allocUnsafe(valueBytes.length); for (let i = 0; i < valueBytes.length; i += 2) { swappedEndianness[i] = valueBytes[i + 1]; swappedEndianness[i + 1] = valueBytes[i]; } return swappedEndianness.toString("utf16le"); } else if (typeof TextEncoder !== "undefined") { return (new TextDecoder("utf-16be")).decode(valueBytes); } else { throw new errors.ASN1Error("Neither TextDecoder nor Buffer are defined to decode bytes into text.", this); } } set duration(value) { this.value = encodeDuration(value); } get duration() { return decodeDuration(this.value); } encode(value) { switch (typeof value) { case ("undefined"): { this.value = new Uint8Array(0); break; } case ("boolean"): { this.tagNumber = ASN1UniversalType.boolean; this.boolean = value; break; } case ("number"): { if (Number.isInteger(value)) { this.tagNumber = ASN1UniversalType.integer; this.integer = value; } else { this.tagNumber = ASN1UniversalType.realNumber; this.real = value; } break; } case ("bigint"): { this.tagNumber = ASN1UniversalType.integer; this.integer = value; break; } case ("string"): { this.tagNumber = ASN1UniversalType.utf8String; this.utf8String = value; break; } case ("object"): { if (!value) { this.tagNumber = ASN1UniversalType.nill; this.value = new Uint8Array(0); } else if (value instanceof Uint8Array) { this.tagNumber = ASN1UniversalType.octetString; this.octetString = value; } else if (value instanceof Uint8ClampedArray) { this.tagNumber = ASN1UniversalType.bitString; this.bitString = value; } else if (value instanceof ASN1Element) { this.construction = ASN1Construction.constructed; this.sequence = [value]; } else if (value instanceof Set) { this.construction = ASN1Construction.constructed; this.set = Array.from(value).map((v) => { if (typeof v === "object" && v instanceof ASN1Element) { return v; } else { const e = new BERElement(); e.encode(v); return e; } }); } else if ((value instanceof ObjectIdentifier) || (value.constructor?.name === "ObjectIdentifier")) { this.tagNumber = ASN1UniversalType.objectIdentifier; this.objectIdentifier = value; } else if (Array.isArray(value)) { this.construction = ASN1Construction.constructed; this.tagNumber = ASN1UniversalType.sequence; this.sequence = value.map((sub) => { const ret = new BERElement(); ret.encode(sub); return ret; }); } else if (value instanceof Date) { this.generalizedTime = value; } else { throw new errors.ASN1Error(`Cannot encode value of type ${value.constructor.name}.`, this); } break; } default: { throw new errors.ASN1Error(`Cannot encode value of type ${typeof value}.`, this); } } } static fromSequence(sequence) { const ret = new BERElement(ASN1TagClass.universal, ASN1Construction.constructed, ASN1UniversalType.sequence); ret.sequence = sequence.filter((element) => Boolean(element)); return ret; } static fromSet(set) { const ret = new BERElement(ASN1TagClass.universal, ASN1Construction.constructed, ASN1UniversalType.set); ret.set = set.filter((element) => Boolean(element)); return ret; } static fromSetOf(set) { const ret = new BERElement(ASN1TagClass.universal, ASN1Construction.constructed, ASN1UniversalType.set); ret.setOf = set.filter((element) => Boolean(element)); return ret; } get inner() { if (this.construction !== ASN1Construction.constructed) { throw new errors.ASN1ConstructionError("An explicitly-encoded element cannot be encoded using " + "primitive construction.", this); } if (Array.isArray(this._value)) { if (this._value.length !== 1) { throw new errors.ASN1ConstructionError(`An explicitly-encoding element contained ${this._value.length} encoded elements.`, this); } return this._value[0]; } const ret = new BERElement(); const readBytes = ret.fromBytes(this._value); if (readBytes !== this._value.length) { throw new errors.ASN1ConstructionError("An explicitly-encoding element contained more than one single " + "encoded element. The tag number of the first decoded " + `element was ${ret.tagNumber}, and it was encoded on ` + `${readBytes} bytes.`, this); } return ret; } set inner(value) { this.construction = ASN1Construction.constructed; this._value = [value]; } constructor(tagClass = ASN1TagClass.universal, construction = ASN1Construction.primitive, tagNumber = ASN1UniversalType.endOfContent, value = undefined) { super(); this._value = new Uint8Array(0); this.encode(value); this.tagClass = tagClass; this.construction = construction; this.tagNumber = tagNumber; } fromBytes(bytes) { if (bytes.length < 2) { throw new errors.ASN1TruncationError("Tried to decode a BER element that is less than two bytes.", this); } if ((this.recursionCount + 1) > BERElement.nestingRecursionLimit) { throw new errors.ASN1RecursionError(); } let cursor = 0; switch (bytes[cursor] & 0b11000000) { case (0b00000000): this.tagClass = ASN1TagClass.universal; break; case (0b01000000): this.tagClass = ASN1TagClass.application; break; case (0b10000000): this.tagClass = ASN1TagClass.context; break; case (0b11000000): this.tagClass = ASN1TagClass.private; break; default: this.tagClass = ASN1TagClass.universal; } this.construction = ((bytes[cursor] & 0b00100000) ? ASN1Construction.constructed : ASN1Construction.primitive); this.tagNumber = (bytes[cursor] & 0b00011111); cursor++; if (this.tagNumber >= 31) { if (bytes[cursor] === 0b10000000) { throw new errors.ASN1PaddingError("Leading padding byte on long tag number encoding.", this); } this.tagNumber = 0; const limit = (((bytes.length - 1) >= 4) ? 4 : (bytes.length - 1)); while (cursor < limit) { if (!(bytes[cursor++] & 0b10000000)) break; } if (bytes[cursor - 1] & 0b10000000) { if (limit === (bytes.length - 1)) { throw new errors.ASN1TruncationError("ASN.1 tag number appears to have been truncated.", this); } else { throw new errors.ASN1OverflowError("ASN.1 tag number too large.", this); } } for (let i = 1; i < cursor; i++) { this.tagNumber <<= 7; this.tagNumber |= (bytes[i] & 0x7F); } } if ((bytes[cursor] & 0b10000000) === 0b10000000) { const numberOfLengthOctets = (bytes[cursor] & 0x7F); if (numberOfLengthOctets) { if (numberOfLengthOctets === 0b01111111) { throw new errors.ASN1UndefinedError("Length byte with undefined meaning encountered.", this); } if (numberOfLengthOctets > 4) { throw new errors.ASN1OverflowError(`Element length too long to decode to an integer. Content octets occupied ${numberOfLengthOctets} bytes.`, this); } if (cursor + numberOfLengthOctets >= bytes.length) { throw new errors.ASN1TruncationError("Element length bytes appear to have been truncated.", this); } cursor++; const lengthNumberOctets = new Uint8Array(4); for (let i = numberOfLengthOctets; i > 0; i--) { lengthNumberOctets[(4 - i)] = bytes[(cursor + numberOfLengthOctets - i)]; } let length = 0; for (const octet of lengthNumberOctets) { length <<= 8; length += octet; } if ((cursor + length) < cursor) { throw new errors.ASN1OverflowError("ASN.1 element too large.", this); } cursor += (numberOfLengthOctets); if ((cursor + length) > bytes.length) { throw new errors.ASN1TruncationError("ASN.1 element truncated.", this); } this.value = bytes.slice(cursor, (cursor + length)); return (cursor + length); } else { if (this.construction !== ASN1Construction.constructed) { throw new errors.ASN1ConstructionError("Indefinite length ASN.1 element was not of constructed construction.", this); } const startOfValue = ++cursor; let sentinel = cursor; while (sentinel < bytes.length) { const child = new BERElement(); sentinel += child.fromBytes(bytes.subarray(sentinel)); if (child.tagClass === ASN1TagClass.universal && child.construction === ASN1Construction.primitive && child.tagNumber === ASN1UniversalType.endOfContent && child.value.length === 0) break; } if (sentinel === bytes.length && (bytes[sentinel - 1] !== 0x00 || bytes[sentinel - 2] !== 0x00)) { throw new errors.ASN1TruncationError("No END OF CONTENT element found at the end of indefinite length ASN.1 element.", this); } this.value = bytes.slice(startOfValue, (sentinel - 2)); return sentinel; } } else { const length = (bytes[cursor++] & 0x7F); if ((cursor + length) > bytes.length) { throw new errors.ASN1TruncationError("ASN.1 element was truncated.", this); } this.value = bytes.slice(cursor, (cursor + length)); return (cursor + length); } } lengthLength(valueLength) { if (BERElement.lengthEncodingPreference === LengthEncodingPreference.indefinite) { return 1; } const len = valueLength ?? this.valueLength(); if (len < 127) { return 1; } let lengthOctets = [0, 0, 0, 0]; for (let i = 0; i < 4; i++) { lengthOctets[i] = ((len >>> ((3 - i) << 3)) & 0xFF); } let startOfNonPadding = 0; for (let i = 0; i < (lengthOctets.length - 1); i++) { if (lengthOctets[i] === 0x00) startOfNonPadding++; } return 5 - startOfNonPadding; } valueLength() { if (this._currentValueLength !== undefined) { return this._currentValueLength; } if (!Array.isArray(this._value)) { return this._value.length; } let len = 0; for (const el of this._value) { len += el.tlvLength(); } this._currentValueLength = len; return len; } tlvLength() { const eoc_bytes = (BERElement.lengthEncodingPreference === LengthEncodingPreference.indefinite) ? 2 : 0; const value_len = this.valueLength(); return (this.tagLength() + this.lengthLength(value_len) + value_len + eoc_bytes); } tagAndLengthBytes() { const tagBytes = [0x00]; tagBytes[0] |= (this.tagClass << 6); tagBytes[0] |= ((BERElement.lengthEncodingPreference === LengthEncodingPreference.indefinite) || this.construction === ASN1Construction.constructed) ? (1 << 5) : 0; if (this.tagNumber < 31) { tagBytes[0] |= this.tagNumber; } else { tagBytes[0] |= 0b00011111; let number = this.tagNumber; const encodedNumber = []; while (number !== 0) { encodedNumber.unshift(number & 0x7F); number >>>= 7; encodedNumber[0] |= 0b10000000; } encodedNumber[encodedNumber.length - 1] &= 0b01111111; tagBytes.push(...encodedNumber); } let lengthOctets = [0x00]; const value_len = this.valueLength(); switch (BERElement.lengthEncodingPreference) { case (LengthEncodingPreference.definite): { if (value_len < 127) { lengthOctets[0] = value_len; } else { lengthOctets = [0, 0, 0, 0]; for (let i = 0; i < 4; i++) { lengthOctets[i] = ((value_len >>> ((3 - i) << 3)) & 0xFF); } let startOfNonPadding = 0; for (let i = 0; i < (lengthOctets.length - 1); i++) { if (lengthOctets[i] === 0x00) startOfNonPadding++; } lengthOctets = lengthOctets.slice(startOfNonPadding); lengthOctets.unshift(0b10000000 | lengthOctets.length); } break; } case (LengthEncodingPreference.indefinite): { lengthOctets = [0b10000000]; break; } default: throw new errors.ASN1UndefinedError("Invalid LengthEncodingPreference encountered!", this); } const ret = new Uint8Array(tagBytes.length + lengthOctets.length); ret.set(tagBytes, 0); ret.set(lengthOctets, tagBytes.length); return ret; } toBuffers() { return [ this.tagAndLengthBytes(), ...(Array.isArray(this._value) ? this._value.flatMap((el) => el.toBuffers()) : [this._value]), ...(BERElement.lengthEncodingPreference === LengthEncodingPreference.indefinite ? [new Uint8Array(2)] : []), ]; } deconstruct(dataType) { if (this.construction === ASN1Construction.primitive) { return this.value; } else { if ((this.recursionCount + 1) > BERElement.nestingRecursionLimit) throw new errors.ASN1RecursionError(); const appendy = []; const substrings = this.sequence; for (const substring of substrings) { if (substring.tagClass !== ASN1TagClass.universal) { throw new errors.ASN1ConstructionError(`Invalid tag class in constructed ${dataType}. Must be UNIVERSAL`, this); } if (substring.tagNumber !== ASN1UniversalType.octetString) { throw new errors.ASN1ConstructionError(`Invalid tag number in constructed ${dataType}. Must be 4 (OCTET STRING).`, this); } substring.recursionCount = (this.recursionCount + 1); const deconstructed = substring.deconstruct(dataType); appendy.push(deconstructed); } return Buffer.concat(appendy); } } get components() { if (Array.isArray(this._value)) { return this._value; } const encodedElements = []; let i = 0; while (i < this._value.length) { const next = new BERElement(); i += next.fromBytes(this.value.subarray(i)); encodedElements.push(next); } return encodedElements; } } BERElement.lengthEncodingPreference = LengthEncodingPreference.definite; export default BERElement;