Buckets:
| import { bigIntFromTwosComplementBytes, bigIntTwosComplementBytes, variableLengthQuantityBytes, variableLengthQuantityFromBytes } from "./integer.js"; | |
| import { decodeASCII } from "./string.js"; | |
| import { bigIntBytes, bigIntFromBytes, compareBytes, DynamicBuffer } from "@oslojs/binary"; | |
| export function parseASN1NoLeftoverBytes(data) { | |
| const [decoded, size] = parseASN1(data); | |
| if (data.byteLength !== size) { | |
| throw new ASN1LeftoverBytesError(data.byteLength - size); | |
| } | |
| return decoded; | |
| } | |
| export function parseASN1(data) { | |
| if (data.byteLength < 2) { | |
| throw new ASN1ParseError(); | |
| } | |
| let asn1Class; | |
| if (data[0] >> 6 === 0b00) { | |
| asn1Class = ASN1Class.Universal; | |
| } | |
| else if (data[0] >> 6 === 0b01) { | |
| asn1Class = ASN1Class.Application; | |
| } | |
| else if (data[0] >> 6 === 0b10) { | |
| asn1Class = ASN1Class.ContextSpecific; | |
| } | |
| else if (data[0] >> 6 === 0b11) { | |
| asn1Class = ASN1Class.Private; | |
| } | |
| else { | |
| // unreachable | |
| throw new ASN1ParseError(); | |
| } | |
| let encodingForm; | |
| if (((data[0] >> 5) & 0x01) === 0) { | |
| encodingForm = ASN1Form.Primitive; | |
| } | |
| else if (((data[0] >> 5) & 0x01) === 1) { | |
| encodingForm = ASN1Form.Constructed; | |
| } | |
| else { | |
| // unreachable | |
| throw new ASN1ParseError(); | |
| } | |
| let offset = 0; | |
| let tag; | |
| if ((data[0] & 0x1f) < 31) { | |
| tag = data[0] & 0x1f; | |
| offset++; | |
| } | |
| else { | |
| offset++; | |
| let decodedTag; | |
| let tagSize; | |
| try { | |
| [decodedTag, tagSize] = variableLengthQuantityFromBytes(data.slice(offset), 2); | |
| } | |
| catch { | |
| throw new ASN1ParseError(); | |
| } | |
| if (decodedTag > 16384n) { | |
| throw new ASN1ParseError(); | |
| } | |
| tag = Number(decodedTag); | |
| offset += tagSize; | |
| } | |
| if (data.byteLength < offset) { | |
| throw new ASN1ParseError(); | |
| } | |
| if (data[offset] === 0x80) { | |
| // indefinite form | |
| throw new ASN1ParseError(); | |
| } | |
| let contentLength = 0; | |
| if (data[offset] >> 7 === 0) { | |
| contentLength = data[offset] & 0x7f; | |
| offset++; | |
| } | |
| else { | |
| const contentLengthSize = data[offset] & 0x7f; | |
| offset++; | |
| if (contentLengthSize < 1 || data.byteLength < offset + contentLengthSize) { | |
| throw new ASN1ParseError(); | |
| } | |
| const decodedContentLength = bigIntFromBytes(data.slice(offset, offset + contentLengthSize)); | |
| offset += contentLengthSize; | |
| contentLength = Number(decodedContentLength); | |
| } | |
| if (data.length < offset + contentLength) { | |
| throw new ASN1ParseError(); | |
| } | |
| const value = data.slice(offset, offset + contentLength); | |
| const result = new ASN1Value(asn1Class, encodingForm, tag, value); | |
| return [result, offset + contentLength]; | |
| } | |
| export function encodeASN1(value) { | |
| const encodedContents = value.contents(); | |
| let firstByte = 0x00; | |
| if (value.class === ASN1Class.Universal) { | |
| firstByte |= 0x00; | |
| } | |
| else if (value.class === ASN1Class.Application) { | |
| firstByte |= 0x40; | |
| } | |
| else if (value.class === ASN1Class.ContextSpecific) { | |
| firstByte |= 0x80; | |
| } | |
| else if (value.class === ASN1Class.Private) { | |
| firstByte |= 0xc0; | |
| } | |
| if (value.form === ASN1Form.Primitive) { | |
| firstByte |= 0x00; | |
| } | |
| else if (value.form === ASN1Form.Constructed) { | |
| firstByte |= 0x20; | |
| } | |
| const buffer = new DynamicBuffer(1); | |
| if (value.tag < 0x1f) { | |
| firstByte |= value.tag; | |
| buffer.writeByte(firstByte); | |
| } | |
| else { | |
| firstByte |= 0x1f; | |
| buffer.writeByte(firstByte); | |
| const encodedTagNumber = variableLengthQuantityBytes(BigInt(value.tag)); | |
| buffer.write(encodedTagNumber); | |
| } | |
| if (encodedContents.byteLength < 128) { | |
| buffer.writeByte(encodedContents.byteLength); | |
| } | |
| else { | |
| const encodedContentsLength = bigIntBytes(BigInt(encodedContents.byteLength)); | |
| if (encodedContentsLength.byteLength > 126) { | |
| throw new ASN1EncodeError(); | |
| } | |
| buffer.writeByte(encodedContentsLength.byteLength | 0x80); | |
| buffer.write(encodedContentsLength); | |
| } | |
| buffer.write(encodedContents); | |
| return buffer.bytes(); | |
| } | |
| export class ASN1Value { | |
| class; | |
| form; | |
| tag; | |
| _contents; | |
| constructor(asn1Class, form, tag, value) { | |
| this.class = asn1Class; | |
| this.form = form; | |
| this.tag = tag; | |
| this._contents = value; | |
| } | |
| universalType() { | |
| if (this.class === ASN1Class.Universal && this.tag in ASN1_UNIVERSAL_TAG_MAP) { | |
| return ASN1_UNIVERSAL_TAG_MAP[this.tag]; | |
| } | |
| throw new ASN1DecodeError(); | |
| } | |
| contents() { | |
| return this._contents; | |
| } | |
| boolean() { | |
| if (this.universalType() !== ASN1UniversalType.Boolean) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this.form !== ASN1Form.Primitive) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this._contents.byteLength !== 1) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this._contents[0] === 0x00) { | |
| return new ASN1Boolean(false); | |
| } | |
| if (this._contents[0] === 0xff) { | |
| return new ASN1Boolean(true); | |
| } | |
| throw new ASN1DecodeError(); | |
| } | |
| integer() { | |
| if (this.universalType() !== ASN1UniversalType.Integer) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this.form !== ASN1Form.Primitive) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this._contents.byteLength < 1) { | |
| throw new ASN1DecodeError(); | |
| } | |
| return new ASN1Integer(bigIntFromTwosComplementBytes(this._contents)); | |
| } | |
| bitString() { | |
| if (this.universalType() !== ASN1UniversalType.BitString) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this.form !== ASN1Form.Primitive) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this._contents.byteLength < 1) { | |
| throw new ASN1DecodeError(); | |
| } | |
| const unusedBits = this._contents[0]; | |
| if (unusedBits > 7) { | |
| throw new ASN1DecodeError(); | |
| } | |
| const value = this._contents.slice(1); | |
| if (unusedBits > 0 && value.byteLength === 0) { | |
| throw new ASN1DecodeError(); | |
| } | |
| return new ASN1BitString(value, value.byteLength * 8 - unusedBits); | |
| } | |
| octetString() { | |
| if (this.universalType() !== ASN1UniversalType.OctetString) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this.form !== ASN1Form.Primitive) { | |
| throw new ASN1DecodeError(); | |
| } | |
| return new ASN1OctetString(this._contents); | |
| } | |
| null() { | |
| if (this.universalType() !== ASN1UniversalType.Null) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this.form !== ASN1Form.Primitive) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this._contents.byteLength > 0) { | |
| throw new ASN1DecodeError(); | |
| } | |
| return new ASN1Null(); | |
| } | |
| objectIdentifier() { | |
| if (this.universalType() !== ASN1UniversalType.ObjectIdentifier) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this.form !== ASN1Form.Primitive) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this._contents.byteLength < 1) { | |
| throw new ASN1DecodeError(); | |
| } | |
| return new ASN1ObjectIdentifier(this._contents); | |
| } | |
| real() { | |
| if (this.universalType() !== ASN1UniversalType.Real) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this.form !== ASN1Form.Primitive) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this._contents.length === 0) { | |
| return new ASN1RealZero(); | |
| } | |
| if (this._contents[0] >> 7 === 1) { | |
| // Binary | |
| let base; | |
| if (((this._contents[0] >> 4) & 0x03) === 0x00) { | |
| base = RealBinaryEncodingBase.Base2; | |
| } | |
| else if (((this._contents[0] >> 4) & 0x03) === 0x01) { | |
| base = RealBinaryEncodingBase.Base8; | |
| } | |
| else if (((this._contents[0] >> 4) & 0x03) === 0x02) { | |
| base = RealBinaryEncodingBase.Base16; | |
| } | |
| else { | |
| throw new ASN1DecodeError(); | |
| } | |
| const scalingFactor = (this._contents[0] >> 2) & 0x03; | |
| let exponent; | |
| let encodedExponentSize; | |
| if ((this._contents[0] & 0x03) === 0x00) { | |
| if (this._contents.byteLength < 2) { | |
| throw new ASN1DecodeError(); | |
| } | |
| exponent = bigIntFromTwosComplementBytes(this._contents.slice(1, 2)); | |
| encodedExponentSize = 1; | |
| } | |
| else if ((this._contents[0] & 0x03) === 0x01) { | |
| if (this._contents.byteLength < 3) { | |
| throw new ASN1DecodeError(); | |
| } | |
| exponent = bigIntFromTwosComplementBytes(this._contents.slice(1, 3)); | |
| encodedExponentSize = 2; | |
| } | |
| else if ((this._contents[0] & 0x03) === 0x02) { | |
| if (this._contents.byteLength < 4) { | |
| throw new ASN1DecodeError(); | |
| } | |
| exponent = bigIntFromTwosComplementBytes(this._contents.slice(1, 4)); | |
| encodedExponentSize = 3; | |
| } | |
| else if ((this._contents[0] & 0x03) === 0x03) { | |
| if (this._contents.byteLength < 2) { | |
| throw new ASN1DecodeError(); | |
| } | |
| const exponentSize = this._contents[1]; | |
| // in DER, it should really be at least 4 | |
| if (exponentSize < 1) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this._contents.byteLength < 2 + exponentSize) { | |
| throw new ASN1DecodeError(); | |
| } | |
| exponent = bigIntFromTwosComplementBytes(this._contents.slice(2, 2 + exponentSize)); | |
| encodedExponentSize = 1 + exponentSize; | |
| } | |
| else { | |
| // unreachable | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this._contents.byteLength === 1 + encodedExponentSize) { | |
| throw new ASN1DecodeError(); | |
| } | |
| const N = bigIntFromBytes(this._contents.slice(1 + encodedExponentSize)); | |
| let mantissa = N * BigInt(2 ** scalingFactor); | |
| if (((this._contents[0] >> 6) & 0x01) === 0x01) { | |
| mantissa = mantissa * -1n; | |
| } | |
| return new ASN1RealBinaryEncoding(mantissa, base, exponent); | |
| } | |
| if (this._contents[0] == 0x01) { | |
| return new ASN1RealDecimalEncoding(RealDecimalEncodingFormat.ISO6093NR1, this._contents.slice(1)); | |
| } | |
| if (this._contents[0] == 0x02) { | |
| return new ASN1RealDecimalEncoding(RealDecimalEncodingFormat.ISO6093NR2, this._contents.slice(1)); | |
| } | |
| if (this._contents[0] == 0x03) { | |
| return new ASN1RealDecimalEncoding(RealDecimalEncodingFormat.ISO6093NR3, this._contents.slice(1)); | |
| } | |
| if (this._contents[0] === 0x40) { | |
| return new ASN1SpecialReal(SpecialReal.PlusInfinity); | |
| } | |
| if (this._contents[0] === 0x41) { | |
| return new ASN1SpecialReal(SpecialReal.MinusInfinity); | |
| } | |
| // unreachable | |
| throw new ASN1DecodeError(); | |
| } | |
| enumerated() { | |
| if (this.universalType() !== ASN1UniversalType.Enumerated) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this.form !== ASN1Form.Primitive) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this._contents.byteLength < 1) { | |
| throw new ASN1DecodeError(); | |
| } | |
| return new ASN1Enumerated(bigIntFromTwosComplementBytes(this._contents)); | |
| } | |
| utf8String() { | |
| if (this.universalType() !== ASN1UniversalType.UTF8String) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this.form !== ASN1Form.Primitive) { | |
| throw new ASN1DecodeError(); | |
| } | |
| return new ASN1UTF8String(this._contents); | |
| } | |
| sequence() { | |
| if (this.universalType() !== ASN1UniversalType.Sequence) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this.form !== ASN1Form.Constructed) { | |
| throw new ASN1DecodeError(); | |
| } | |
| const elements = []; | |
| let readBytes = 0; | |
| while (readBytes !== this._contents.byteLength) { | |
| const [parsedElement, parsedElementSize] = parseASN1(this._contents.slice(readBytes)); | |
| elements.push(parsedElement); | |
| readBytes += parsedElementSize; | |
| } | |
| return new ASN1Sequence(elements); | |
| } | |
| set() { | |
| if (this.universalType() !== ASN1UniversalType.Set) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this.form !== ASN1Form.Constructed) { | |
| throw new ASN1DecodeError(); | |
| } | |
| const elements = []; | |
| let readBytes = 0; | |
| while (readBytes !== this._contents.byteLength) { | |
| const [parsedElement, parsedElementSize] = parseASN1(this._contents.slice(readBytes)); | |
| elements.push(parsedElement); | |
| readBytes += parsedElementSize; | |
| } | |
| return new ASN1Set(elements); | |
| } | |
| numericString() { | |
| if (this.universalType() !== ASN1UniversalType.NumericString) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this.form !== ASN1Form.Primitive) { | |
| throw new ASN1DecodeError(); | |
| } | |
| return new ASN1NumericString(this._contents); | |
| } | |
| printableString() { | |
| if (this.universalType() !== ASN1UniversalType.PrintableString) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this.form !== ASN1Form.Primitive) { | |
| throw new ASN1DecodeError(); | |
| } | |
| return new ASN1PrintableString(this._contents); | |
| } | |
| ia5String() { | |
| if (this.universalType() !== ASN1UniversalType.IA5String) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this.form !== ASN1Form.Primitive) { | |
| throw new ASN1DecodeError(); | |
| } | |
| return new ASN1IA5String(this._contents); | |
| } | |
| utcTime() { | |
| if (this.universalType() !== ASN1UniversalType.UTCTime) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this.form !== ASN1Form.Primitive) { | |
| throw new ASN1DecodeError(); | |
| } | |
| let decodedString = decodeASCII(this._contents); | |
| if (decodedString.length !== 13 || !decodedString.endsWith("Z")) { | |
| throw new ASN1DecodeError(); | |
| } | |
| decodedString = decodedString.replace("Z", ""); | |
| return new ASN1UTCTime(Number(decodedString.slice(0, 2)), Number(decodedString.slice(2, 4)), Number(decodedString.slice(4, 6)), Number(decodedString.slice(6, 8)), Number(decodedString.slice(8, 10)), Number(decodedString.slice(10, 12))); | |
| } | |
| generalizedTime() { | |
| if (this.universalType() !== ASN1UniversalType.GeneralizedTime) { | |
| throw new ASN1DecodeError(); | |
| } | |
| if (this.form !== ASN1Form.Primitive) { | |
| throw new ASN1DecodeError(); | |
| } | |
| let decodedString = decodeASCII(this._contents); | |
| if (!decodedString.endsWith("Z")) { | |
| throw new ASN1DecodeError(); | |
| } | |
| decodedString = decodedString.replace("Z", ""); | |
| let wholePart; | |
| let decimalPart = null; | |
| if (decodedString.includes(".")) { | |
| [wholePart, decimalPart] = decodedString.split("."); | |
| } | |
| else { | |
| wholePart = decodedString; | |
| } | |
| if (wholePart.length !== 14) { | |
| throw new ASN1DecodeError(); | |
| } | |
| let milliseconds = 0; | |
| if (decimalPart !== null) { | |
| if (decimalPart.length > 3) { | |
| throw new ASN1DecodeError(); | |
| } | |
| milliseconds = Number(decimalPart.padEnd(3, "0")); | |
| } | |
| return new ASN1GeneralizedTime(Number(wholePart.slice(0, 4)), Number(wholePart.slice(4, 6)), Number(wholePart.slice(6, 8)), Number(wholePart.slice(8, 10)), Number(wholePart.slice(10, 12)), Number(wholePart.slice(12, 14)), milliseconds); | |
| } | |
| } | |
| export class ASN1Boolean { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Primitive; | |
| tag = ASN1_UNIVERSAL_TAG.BOOLEAN; | |
| value; | |
| constructor(value) { | |
| this.value = value; | |
| } | |
| contents() { | |
| if (this.value) { | |
| return new Uint8Array([0xff]); | |
| } | |
| return new Uint8Array([0x00]); | |
| } | |
| } | |
| export class ASN1Integer { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Primitive; | |
| tag = ASN1_UNIVERSAL_TAG.INTEGER; | |
| value; | |
| constructor(value) { | |
| this.value = value; | |
| } | |
| contents() { | |
| return bigIntTwosComplementBytes(this.value); | |
| } | |
| } | |
| export class ASN1BitString { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Primitive; | |
| tag = ASN1_UNIVERSAL_TAG.BIT_STRING; | |
| bytes; | |
| length; | |
| constructor(bytes, length) { | |
| if (length > bytes.byteLength * 8) { | |
| throw new TypeError("Data too small"); | |
| } | |
| if (length <= (bytes.byteLength - 1) * 8) { | |
| throw new TypeError("Data too large"); | |
| } | |
| this.bytes = bytes; | |
| this.length = length; | |
| } | |
| contents() { | |
| let remainingBitsInLastByte = 8 - (this.length % 8); | |
| if (remainingBitsInLastByte === 8) { | |
| remainingBitsInLastByte = 0; | |
| } | |
| const encoded = new Uint8Array(this.bytes.byteLength + 1); | |
| encoded[0] = remainingBitsInLastByte; | |
| encoded.set(this.bytes, 1); | |
| return encoded; | |
| } | |
| } | |
| export class ASN1Enumerated { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Primitive; | |
| tag = ASN1_UNIVERSAL_TAG.ENUMERATED; | |
| value; | |
| constructor(value) { | |
| this.value = value; | |
| } | |
| contents() { | |
| return bigIntTwosComplementBytes(this.value); | |
| } | |
| } | |
| export class ASN1RealBinaryEncoding { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Primitive; | |
| tag = ASN1_UNIVERSAL_TAG.REAL; | |
| mantissa; | |
| base; | |
| exponent; | |
| constructor(mantissa, base, exponent) { | |
| if (mantissa === 0n) { | |
| throw new TypeError("The mantissa cannot be zero"); | |
| } | |
| this.mantissa = mantissa; | |
| this.base = base; | |
| this.exponent = exponent; | |
| } | |
| contents() { | |
| let N, scalingFactor; | |
| if (this.mantissa % 8n === 0n) { | |
| N = absBigInt(this.mantissa) / 8n; | |
| scalingFactor = 3; | |
| } | |
| else if (this.mantissa % 4n === 0n) { | |
| N = absBigInt(this.mantissa) / 4n; | |
| scalingFactor = 2; | |
| } | |
| else if (this.mantissa % 2n === 0n) { | |
| N = absBigInt(this.mantissa) / 2n; | |
| scalingFactor = 1; | |
| } | |
| else { | |
| N = absBigInt(this.mantissa); | |
| scalingFactor = 0; | |
| } | |
| let firstByte = 0x80; | |
| if (this.mantissa < 0) { | |
| firstByte |= 0x40; | |
| } | |
| if (this.base === RealBinaryEncodingBase.Base8) { | |
| firstByte |= 0x10; | |
| } | |
| else if (this.base === RealBinaryEncodingBase.Base16) { | |
| firstByte |= 0x20; | |
| } | |
| firstByte |= scalingFactor << 2; | |
| let encodedExponent; | |
| const exponentBytes = bigIntTwosComplementBytes(this.exponent); | |
| if (exponentBytes.byteLength === 1) { | |
| encodedExponent = new Uint8Array(1); | |
| encodedExponent.set(exponentBytes); | |
| } | |
| else if (exponentBytes.byteLength === 2) { | |
| firstByte |= 0x01; | |
| encodedExponent = new Uint8Array(2); | |
| encodedExponent.set(exponentBytes); | |
| } | |
| else if (exponentBytes.byteLength === 3) { | |
| firstByte |= 0x02; | |
| encodedExponent = new Uint8Array(3); | |
| encodedExponent.set(exponentBytes); | |
| } | |
| else { | |
| if (exponentBytes.byteLength > 255) { | |
| throw new ASN1DecodeError(); | |
| } | |
| firstByte |= 0x03; | |
| encodedExponent = new Uint8Array(exponentBytes.byteLength + 1); | |
| encodedExponent[0] = exponentBytes.byteLength; | |
| encodedExponent.set(exponentBytes, 1); | |
| } | |
| const nBytes = bigIntBytes(N); | |
| const encoded = new Uint8Array(1 + encodedExponent.byteLength + nBytes.byteLength); | |
| encoded[0] = firstByte; | |
| encoded.set(encodedExponent, 1); | |
| encoded.set(nBytes, 1 + encodedExponent.byteLength); | |
| return encoded; | |
| } | |
| } | |
| function absBigInt(value) { | |
| if (value < 0) { | |
| return value * -1n; | |
| } | |
| return value; | |
| } | |
| export var RealBinaryEncodingBase; | |
| (function (RealBinaryEncodingBase) { | |
| RealBinaryEncodingBase[RealBinaryEncodingBase["Base2"] = 0] = "Base2"; | |
| RealBinaryEncodingBase[RealBinaryEncodingBase["Base8"] = 1] = "Base8"; | |
| RealBinaryEncodingBase[RealBinaryEncodingBase["Base16"] = 2] = "Base16"; | |
| })(RealBinaryEncodingBase || (RealBinaryEncodingBase = {})); | |
| export class ASN1RealDecimalEncoding { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Primitive; | |
| tag = ASN1_UNIVERSAL_TAG.REAL; | |
| encodingFormat; | |
| value; | |
| constructor(encodingFormat, value) { | |
| this.encodingFormat = encodingFormat; | |
| this.value = value; | |
| } | |
| contents() { | |
| const encoded = new Uint8Array(1 + this.value.byteLength); | |
| if (this.encodingFormat === RealDecimalEncodingFormat.ISO6093NR1) { | |
| encoded[0] = 0x01; | |
| } | |
| else if (this.encodingFormat === RealDecimalEncodingFormat.ISO6093NR2) { | |
| encoded[0] = 0x02; | |
| } | |
| else if (this.encodingFormat === RealDecimalEncodingFormat.ISO6093NR3) { | |
| encoded[0] = 0x03; | |
| } | |
| encoded.set(this.value, 1); | |
| return encoded; | |
| } | |
| decodeText() { | |
| return new TextDecoder().decode(this.value); | |
| } | |
| decodeNumber() { | |
| return Number(this.decodeText()); | |
| } | |
| } | |
| export var RealDecimalEncodingFormat; | |
| (function (RealDecimalEncodingFormat) { | |
| RealDecimalEncodingFormat[RealDecimalEncodingFormat["ISO6093NR1"] = 0] = "ISO6093NR1"; | |
| RealDecimalEncodingFormat[RealDecimalEncodingFormat["ISO6093NR2"] = 1] = "ISO6093NR2"; | |
| RealDecimalEncodingFormat[RealDecimalEncodingFormat["ISO6093NR3"] = 2] = "ISO6093NR3"; | |
| })(RealDecimalEncodingFormat || (RealDecimalEncodingFormat = {})); | |
| export class ASN1SpecialReal { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Primitive; | |
| tag = ASN1_UNIVERSAL_TAG.REAL; | |
| value; | |
| constructor(value) { | |
| this.value = value; | |
| } | |
| contents() { | |
| switch (this.value) { | |
| case SpecialReal.PlusInfinity: | |
| return new Uint8Array([0x40]); | |
| case SpecialReal.MinusInfinity: | |
| return new Uint8Array([0x41]); | |
| } | |
| } | |
| } | |
| export var SpecialReal; | |
| (function (SpecialReal) { | |
| SpecialReal[SpecialReal["PlusInfinity"] = 0] = "PlusInfinity"; | |
| SpecialReal[SpecialReal["MinusInfinity"] = 1] = "MinusInfinity"; | |
| })(SpecialReal || (SpecialReal = {})); | |
| export class ASN1RealZero { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Primitive; | |
| tag = ASN1_UNIVERSAL_TAG.REAL; | |
| value = 0; | |
| contents() { | |
| return new Uint8Array(0); | |
| } | |
| } | |
| export class ASN1OctetString { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Primitive; | |
| tag = ASN1_UNIVERSAL_TAG.OCTET_STRING; | |
| value; | |
| constructor(value) { | |
| this.value = value; | |
| } | |
| contents() { | |
| return this.value; | |
| } | |
| } | |
| export class ASN1Null { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Primitive; | |
| tag = ASN1_UNIVERSAL_TAG.NULL; | |
| contents() { | |
| return new Uint8Array(0); | |
| } | |
| } | |
| export class ASN1Sequence { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Constructed; | |
| tag = ASN1_UNIVERSAL_TAG.SEQUENCE; | |
| elements; | |
| constructor(elements) { | |
| this.elements = elements; | |
| } | |
| contents() { | |
| const buffer = new DynamicBuffer(0); | |
| for (const element of this.elements) { | |
| buffer.write(encodeASN1(element)); | |
| } | |
| return buffer.bytes(); | |
| } | |
| isSequenceOfSingleType(asn1Class, form, tag) { | |
| for (const element of this.elements) { | |
| if (element.class !== asn1Class || element.form !== form || element.tag !== tag) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| at(index) { | |
| if (index < this.elements.length) { | |
| return this.elements[index]; | |
| } | |
| throw new Error("Invalid index"); | |
| } | |
| } | |
| export class ASN1EncodableSequence { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Constructed; | |
| tag = ASN1_UNIVERSAL_TAG.SEQUENCE; | |
| elements; | |
| constructor(elements) { | |
| this.elements = elements; | |
| } | |
| contents() { | |
| const buffer = new DynamicBuffer(0); | |
| for (const element of this.elements) { | |
| buffer.write(encodeASN1(element)); | |
| } | |
| return buffer.bytes(); | |
| } | |
| } | |
| export class ASN1Set { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Constructed; | |
| tag = ASN1_UNIVERSAL_TAG.SET; | |
| elements; | |
| constructor(elements) { | |
| this.elements = elements; | |
| } | |
| contents() { | |
| const buffer = new DynamicBuffer(0); | |
| for (const element of this.elements) { | |
| buffer.write(encodeASN1(element)); | |
| } | |
| return buffer.bytes(); | |
| } | |
| isSetOfSingleType(asn1Class, form, tag) { | |
| for (const element of this.elements) { | |
| if (element.class !== asn1Class || element.form !== form || element.tag !== tag) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| at(index) { | |
| if (index < this.elements.length) { | |
| return this.elements[index]; | |
| } | |
| throw new Error("Invalid index"); | |
| } | |
| } | |
| export class ASN1EncodableSet { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Constructed; | |
| tag = ASN1_UNIVERSAL_TAG.SET; | |
| elements; | |
| constructor(elements) { | |
| this.elements = elements; | |
| } | |
| contents() { | |
| const buffer = new DynamicBuffer(0); | |
| for (const element of this.elements) { | |
| buffer.write(encodeASN1(element)); | |
| } | |
| return buffer.bytes(); | |
| } | |
| } | |
| export class ASN1ObjectIdentifier { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Primitive; | |
| tag = ASN1_UNIVERSAL_TAG.OBJECT_IDENTIFIER; | |
| encoded; | |
| constructor(encoded) { | |
| this.encoded = encoded; | |
| } | |
| contents() { | |
| return this.encoded; | |
| } | |
| is(objectIdentifier) { | |
| return compareBytes(encodeObjectIdentifier(objectIdentifier), this.encoded); | |
| } | |
| } | |
| // TODO?: relative object identifier | |
| export class ASN1NumericString { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Primitive; | |
| tag = ASN1_UNIVERSAL_TAG.NUMERIC_STRING; | |
| value; | |
| constructor(value) { | |
| this.value = value; | |
| } | |
| contents() { | |
| return this.value; | |
| } | |
| decodeText() { | |
| for (let i = 0; i < this.value.byteLength; i++) { | |
| if (this.value[i] === 0x20) { | |
| continue; | |
| } | |
| if (this.value[i] >= 0x30 && this.value[i] <= 0x39) { | |
| continue; | |
| } | |
| throw new TypeError("Invalid numeric string"); | |
| } | |
| return new TextDecoder().decode(this.value); | |
| } | |
| } | |
| export class ASN1PrintableString { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Primitive; | |
| tag = ASN1_UNIVERSAL_TAG.PRINTABLE_STRING; | |
| value; | |
| constructor(value) { | |
| this.value = value; | |
| } | |
| contents() { | |
| return this.value; | |
| } | |
| decodeText() { | |
| for (let i = 0; i < this.value.byteLength; i++) { | |
| if (this.value[i] === 0x20) { | |
| continue; | |
| } | |
| if (this.value[i] >= 0x27 && this.value[i] >= 0x29) { | |
| continue; | |
| } | |
| if (this.value[i] >= 0x2b && this.value[i] >= 0x2f) { | |
| continue; | |
| } | |
| if (this.value[i] >= 0x30 && this.value[i] <= 0x39) { | |
| continue; | |
| } | |
| if (this.value[i] === 0x3d) { | |
| continue; | |
| } | |
| if (this.value[i] === 0x3f) { | |
| continue; | |
| } | |
| if (this.value[i] >= 0x41 && this.value[i] <= 0x5a) { | |
| continue; | |
| } | |
| if (this.value[i] >= 0x61 && this.value[i] <= 0x7a) { | |
| continue; | |
| } | |
| throw new TypeError("Invalid printable string"); | |
| } | |
| return new TextDecoder().decode(this.value); | |
| } | |
| } | |
| export class ASN1UTF8String { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Primitive; | |
| tag = ASN1_UNIVERSAL_TAG.UTF8_STRING; | |
| value; | |
| constructor(value) { | |
| this.value = value; | |
| } | |
| contents() { | |
| return this.value; | |
| } | |
| decodeText() { | |
| return new TextDecoder().decode(this.value); | |
| } | |
| } | |
| export class ASN1IA5String { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Primitive; | |
| tag = ASN1_UNIVERSAL_TAG.IA5_STRING; | |
| value; | |
| constructor(value) { | |
| this.value = value; | |
| } | |
| contents() { | |
| return this.value; | |
| } | |
| decodeText() { | |
| return decodeASCII(this.value); | |
| } | |
| } | |
| export class ASN1GeneralizedTime { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Primitive; | |
| tag = ASN1_UNIVERSAL_TAG.GENERALIZED_TIME; | |
| year; | |
| month; | |
| date; | |
| hours; | |
| minutes; | |
| seconds; | |
| milliseconds; | |
| constructor(year, month, date, hours, minutes, seconds, milliseconds) { | |
| if (!Number.isInteger(year) || year < 0 || year > 9999) { | |
| throw new TypeError("Invalid year"); | |
| } | |
| if (!Number.isInteger(month) || month < 1 || month > 12) { | |
| throw new TypeError("Invalid month"); | |
| } | |
| if (!Number.isInteger(date) || date < 1 || date > 99) { | |
| throw new TypeError("Invalid date"); | |
| } | |
| if (!Number.isInteger(hours) || hours < 0 || hours > 23) { | |
| throw new TypeError("Invalid hours"); | |
| } | |
| if (!Number.isInteger(minutes) || minutes < 0 || minutes > 59) { | |
| throw new TypeError("Invalid minutes"); | |
| } | |
| if (!Number.isInteger(seconds) || seconds < 0 || seconds > 59) { | |
| throw new TypeError("Invalid seconds"); | |
| } | |
| if (!Number.isInteger(milliseconds) || milliseconds < 0 || milliseconds > 999) { | |
| throw new TypeError("Invalid milliseconds"); | |
| } | |
| this.year = year; | |
| this.month = month; | |
| this.date = date; | |
| this.hours = hours; | |
| this.minutes = minutes; | |
| this.seconds = seconds; | |
| this.milliseconds = milliseconds; | |
| } | |
| contents() { | |
| let text = this.year.toString().padStart(4, "0"); | |
| text += this.month.toString().padStart(2, "0"); | |
| text += this.date.toString().padStart(2, "0"); | |
| text += this.hours.toString().padStart(2, "0"); | |
| text += this.minutes.toString().padStart(2, "0"); | |
| text += this.seconds.toString().padStart(2, "0"); | |
| if (this.milliseconds > 0) { | |
| text += (this.milliseconds / 1000).toString().replace("0", ""); | |
| } | |
| text += "Z"; | |
| return new TextEncoder().encode(text); | |
| } | |
| toDate() { | |
| const date = new Date(); | |
| date.setUTCFullYear(this.year); | |
| date.setUTCMonth(this.month - 1); | |
| date.setUTCDate(this.date); | |
| date.setUTCHours(this.hours); | |
| date.setUTCMinutes(this.minutes); | |
| date.setUTCSeconds(this.seconds); | |
| date.setUTCMilliseconds(this.milliseconds); | |
| return date; | |
| } | |
| } | |
| export class ASN1UTCTime { | |
| class = ASN1Class.Universal; | |
| form = ASN1Form.Primitive; | |
| tag = ASN1_UNIVERSAL_TAG.UTC_TIME; | |
| year; | |
| month; | |
| date; | |
| hours; | |
| minutes; | |
| seconds; | |
| constructor(year, month, date, hours, minutes, seconds) { | |
| if (!Number.isInteger(year) || year < 0 || year > 99) { | |
| throw new TypeError("Invalid year"); | |
| } | |
| if (!Number.isInteger(month) || month < 1 || month > 12) { | |
| throw new TypeError("Invalid month"); | |
| } | |
| if (!Number.isInteger(date) || date < 1 || date > 99) { | |
| throw new TypeError("Invalid date"); | |
| } | |
| if (!Number.isInteger(hours) || hours < 0 || hours > 23) { | |
| throw new TypeError("Invalid hours"); | |
| } | |
| if (!Number.isInteger(minutes) || minutes < 0 || minutes > 59) { | |
| throw new TypeError("Invalid minutes"); | |
| } | |
| if (!Number.isInteger(seconds) || seconds < 0 || seconds > 59) { | |
| throw new TypeError("Invalid seconds"); | |
| } | |
| this.year = year; | |
| this.month = month; | |
| this.date = date; | |
| this.hours = hours; | |
| this.minutes = minutes; | |
| this.seconds = seconds; | |
| } | |
| contents() { | |
| let text = this.year.toString().padStart(2, "0"); | |
| text += this.month.toString().padStart(2, "0"); | |
| text += this.date.toString().padStart(2, "0"); | |
| text += this.hours.toString().padStart(2, "0"); | |
| text += this.minutes.toString().padStart(2, "0"); | |
| text += this.seconds.toString().padStart(2, "0"); | |
| text += "Z"; | |
| return new TextEncoder().encode(text); | |
| } | |
| toDate(century) { | |
| if (century < 0 || century > 99) { | |
| throw new TypeError("Invalid century"); | |
| } | |
| const date = new Date(); | |
| date.setUTCFullYear(century * 100 + this.year); | |
| date.setUTCMonth(this.month - 1); | |
| date.setUTCDate(this.date); | |
| date.setUTCHours(this.hours); | |
| date.setUTCMinutes(this.minutes); | |
| date.setUTCSeconds(this.seconds); | |
| date.setUTCMilliseconds(0); | |
| return date; | |
| } | |
| } | |
| export function encodeObjectIdentifier(oid) { | |
| const parts = oid.split("."); | |
| const components = []; | |
| for (let i = 0; i < parts.length; i++) { | |
| const parsed = Number(parts[i]); | |
| if (!Number.isInteger(parsed) || parsed < 0) { | |
| throw new TypeError("Invalid object identifier"); | |
| } | |
| components[i] = parsed; | |
| } | |
| if (components.length < 2) { | |
| throw new TypeError("Invalid object identifier"); | |
| } | |
| const firstSubidentifier = components[0] * 40 + components[1]; | |
| const buffer = new DynamicBuffer(0); | |
| buffer.write(variableLengthQuantityBytes(BigInt(firstSubidentifier))); | |
| for (let i = 2; i < components.length; i++) { | |
| buffer.write(variableLengthQuantityBytes(BigInt(components[i]))); | |
| } | |
| return buffer.bytes(); | |
| } | |
| export var ASN1UniversalType; | |
| (function (ASN1UniversalType) { | |
| ASN1UniversalType[ASN1UniversalType["Boolean"] = 0] = "Boolean"; | |
| ASN1UniversalType[ASN1UniversalType["Integer"] = 1] = "Integer"; | |
| ASN1UniversalType[ASN1UniversalType["BitString"] = 2] = "BitString"; | |
| ASN1UniversalType[ASN1UniversalType["OctetString"] = 3] = "OctetString"; | |
| ASN1UniversalType[ASN1UniversalType["Null"] = 4] = "Null"; | |
| ASN1UniversalType[ASN1UniversalType["ObjectIdentifier"] = 5] = "ObjectIdentifier"; | |
| ASN1UniversalType[ASN1UniversalType["ObjectDescriptor"] = 6] = "ObjectDescriptor"; | |
| ASN1UniversalType[ASN1UniversalType["External"] = 7] = "External"; | |
| ASN1UniversalType[ASN1UniversalType["Real"] = 8] = "Real"; | |
| ASN1UniversalType[ASN1UniversalType["Enumerated"] = 9] = "Enumerated"; | |
| ASN1UniversalType[ASN1UniversalType["EmbeddedPDV"] = 10] = "EmbeddedPDV"; | |
| ASN1UniversalType[ASN1UniversalType["UTF8String"] = 11] = "UTF8String"; | |
| ASN1UniversalType[ASN1UniversalType["RelativeObjectIdentifier"] = 12] = "RelativeObjectIdentifier"; | |
| ASN1UniversalType[ASN1UniversalType["Time"] = 13] = "Time"; | |
| ASN1UniversalType[ASN1UniversalType["Sequence"] = 14] = "Sequence"; | |
| ASN1UniversalType[ASN1UniversalType["Set"] = 15] = "Set"; | |
| ASN1UniversalType[ASN1UniversalType["NumericString"] = 16] = "NumericString"; | |
| ASN1UniversalType[ASN1UniversalType["PrintableString"] = 17] = "PrintableString"; | |
| ASN1UniversalType[ASN1UniversalType["TeletexString"] = 18] = "TeletexString"; | |
| ASN1UniversalType[ASN1UniversalType["VideotextString"] = 19] = "VideotextString"; | |
| ASN1UniversalType[ASN1UniversalType["IA5String"] = 20] = "IA5String"; | |
| ASN1UniversalType[ASN1UniversalType["UTCTime"] = 21] = "UTCTime"; | |
| ASN1UniversalType[ASN1UniversalType["GeneralizedTime"] = 22] = "GeneralizedTime"; | |
| ASN1UniversalType[ASN1UniversalType["GraphicString"] = 23] = "GraphicString"; | |
| ASN1UniversalType[ASN1UniversalType["VisibleString"] = 24] = "VisibleString"; | |
| ASN1UniversalType[ASN1UniversalType["GeneralString"] = 25] = "GeneralString"; | |
| ASN1UniversalType[ASN1UniversalType["UniversalString"] = 26] = "UniversalString"; | |
| ASN1UniversalType[ASN1UniversalType["CharacterString"] = 27] = "CharacterString"; | |
| ASN1UniversalType[ASN1UniversalType["BMPString"] = 28] = "BMPString"; | |
| })(ASN1UniversalType || (ASN1UniversalType = {})); | |
| export var ASN1Class; | |
| (function (ASN1Class) { | |
| ASN1Class[ASN1Class["Universal"] = 0] = "Universal"; | |
| ASN1Class[ASN1Class["Application"] = 1] = "Application"; | |
| ASN1Class[ASN1Class["ContextSpecific"] = 2] = "ContextSpecific"; | |
| ASN1Class[ASN1Class["Private"] = 3] = "Private"; | |
| })(ASN1Class || (ASN1Class = {})); | |
| export var ASN1Form; | |
| (function (ASN1Form) { | |
| ASN1Form[ASN1Form["Primitive"] = 0] = "Primitive"; | |
| ASN1Form[ASN1Form["Constructed"] = 1] = "Constructed"; | |
| })(ASN1Form || (ASN1Form = {})); | |
| export const ASN1_UNIVERSAL_TAG = { | |
| BOOLEAN: 1, | |
| INTEGER: 2, | |
| BIT_STRING: 3, | |
| OCTET_STRING: 4, | |
| NULL: 5, | |
| OBJECT_IDENTIFIER: 6, | |
| OBJECT_DESCRIPTOR: 7, | |
| EXTERNAL: 8, | |
| REAL: 9, | |
| ENUMERATED: 10, | |
| EMBEDDED_PDV: 11, | |
| UTF8_STRING: 12, | |
| RELATIVE_OBJECT_IDENTIFIER: 13, | |
| TIME: 14, | |
| SEQUENCE: 16, | |
| SET: 17, | |
| NUMERIC_STRING: 18, | |
| PRINTABLE_STRING: 19, | |
| TELETEX_STRING: 20, | |
| VIDEOTEX_STRING: 21, | |
| IA5_STRING: 22, | |
| UTC_TIME: 23, | |
| GENERALIZED_TIME: 24, | |
| GRAPHIC_STRING: 25, | |
| VISIBLE_STRING: 26, | |
| GENERAL_STRING: 27, | |
| UNIVERSAL_STRING: 28, | |
| CHARACTER_STRING: 29, | |
| BMP_STRING: 30 | |
| }; | |
| const ASN1_UNIVERSAL_TAG_MAP = { | |
| 1: ASN1UniversalType.Boolean, | |
| 2: ASN1UniversalType.Integer, | |
| 3: ASN1UniversalType.BitString, | |
| 4: ASN1UniversalType.OctetString, | |
| 5: ASN1UniversalType.Null, | |
| 6: ASN1UniversalType.ObjectIdentifier, | |
| 7: ASN1UniversalType.ObjectDescriptor, | |
| 8: ASN1UniversalType.External, | |
| 9: ASN1UniversalType.Real, | |
| 10: ASN1UniversalType.Enumerated, | |
| 11: ASN1UniversalType.EmbeddedPDV, | |
| 12: ASN1UniversalType.UTF8String, | |
| 13: ASN1UniversalType.RelativeObjectIdentifier, | |
| 14: ASN1UniversalType.Time, | |
| 16: ASN1UniversalType.Sequence, | |
| 17: ASN1UniversalType.Set, | |
| 18: ASN1UniversalType.NumericString, | |
| 19: ASN1UniversalType.PrintableString, | |
| 20: ASN1UniversalType.TeletexString, | |
| 21: ASN1UniversalType.VideotextString, | |
| 22: ASN1UniversalType.IA5String, | |
| 23: ASN1UniversalType.UTCTime, | |
| 24: ASN1UniversalType.GeneralizedTime, | |
| 25: ASN1UniversalType.GraphicString, | |
| 26: ASN1UniversalType.VisibleString, | |
| 27: ASN1UniversalType.GeneralString, | |
| 28: ASN1UniversalType.UniversalString, | |
| 29: ASN1UniversalType.CharacterString, | |
| 30: ASN1UniversalType.BMPString | |
| }; | |
| export class ASN1ParseError extends Error { | |
| constructor() { | |
| super("Failed to parse ASN.1"); | |
| } | |
| } | |
| export class ASN1DecodeError extends Error { | |
| constructor() { | |
| super("Failed to decode ASN.1"); | |
| } | |
| } | |
| export class ASN1EncodeError extends Error { | |
| constructor() { | |
| super("Failed to encode ASN.1"); | |
| } | |
| } | |
| export class ASN1LeftoverBytesError extends Error { | |
| constructor(count) { | |
| super(`ASN.1 leftover bytes: ${count}`); | |
| } | |
| } | |
Xet Storage Details
- Size:
- 39.9 kB
- Xet hash:
- e2241374e08eb409666d0cbb20cf1a266e222764b6cfa1ed25575d7e1eaa6f5d
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.