"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.fromBER = fromBER; exports.compareSchema = compareSchema; exports.verifySchema = verifySchema; exports.fromJSON = fromJSON; exports.RawData = exports.Repeated = exports.Any = exports.Choice = exports.TIME = exports.Duration = exports.DateTime = exports.TimeOfDay = exports.DATE = exports.GeneralizedTime = exports.UTCTime = exports.CharacterString = exports.GeneralString = exports.VisibleString = exports.GraphicString = exports.IA5String = exports.VideotexString = exports.TeletexString = exports.PrintableString = exports.NumericString = exports.UniversalString = exports.BmpString = exports.RelativeObjectIdentifier = exports.Utf8String = exports.ObjectIdentifier = exports.Enumerated = exports.Integer = exports.BitString = exports.OctetString = exports.Null = exports.Set = exports.Sequence = exports.Boolean = exports.EndOfContent = exports.Constructed = exports.Primitive = exports.BaseBlock = exports.ValueBlock = exports.HexBlock = void 0; var _pvutils = require("pvutils"); /* eslint-disable indent */ /* * Copyright (c) 2016-2018, Peculiar Ventures * All rights reserved. * * Author 2016-2018, Yury Strozhevsky . * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * */ //************************************************************************************** //************************************************************************************** //region Declaration of global variables //************************************************************************************** const powers2 = [new Uint8Array([1])]; const digitsString = "0123456789"; //************************************************************************************** //endregion //************************************************************************************** //region Declaration for "LocalBaseBlock" class //************************************************************************************** /** * Class used as a base block for all remaining ASN.1 classes * @typedef LocalBaseBlock * @interface * @property {number} blockLength * @property {string} error * @property {Array.} warnings * @property {ArrayBuffer} valueBeforeDecode */ class LocalBaseBlock { //********************************************************************************** /** * Constructor for "LocalBaseBlock" class * @param {Object} [parameters={}] * @property {ArrayBuffer} [valueBeforeDecode] */ constructor(parameters = {}) { /** * @type {number} blockLength */ this.blockLength = (0, _pvutils.getParametersValue)(parameters, "blockLength", 0); /** * @type {string} error */ this.error = (0, _pvutils.getParametersValue)(parameters, "error", ""); /** * @type {Array.} warnings */ this.warnings = (0, _pvutils.getParametersValue)(parameters, "warnings", []); //noinspection JSCheckFunctionSignatures /** * @type {ArrayBuffer} valueBeforeDecode */ if ("valueBeforeDecode" in parameters) this.valueBeforeDecode = parameters.valueBeforeDecode.slice(0);else this.valueBeforeDecode = new ArrayBuffer(0); } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "baseBlock"; } //********************************************************************************** /** * Convertion for the block to JSON object * @returns {{blockName: string, blockLength: number, error: string, warnings: Array., valueBeforeDecode: string}} */ toJSON() { return { blockName: this.constructor.blockName(), blockLength: this.blockLength, error: this.error, warnings: this.warnings, valueBeforeDecode: (0, _pvutils.bufferToHexCodes)(this.valueBeforeDecode, 0, this.valueBeforeDecode.byteLength) }; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Description for "HexBlock" class //************************************************************************************** /** * Class used as a base block for all remaining ASN.1 classes * @extends LocalBaseBlock * @typedef HexBlock * @property {number} blockLength * @property {string} error * @property {Array.} warnings * @property {ArrayBuffer} valueBeforeDecode * @property {boolean} isHexOnly * @property {ArrayBuffer} valueHex */ //noinspection JSUnusedLocalSymbols const HexBlock = BaseClass => class LocalHexBlockMixin extends BaseClass { //********************************************************************************** //noinspection JSUnusedGlobalSymbols /** * Constructor for "HexBlock" class * @param {Object} [parameters={}] * @property {ArrayBuffer} [valueHex] */ constructor(parameters = {}) { super(parameters); /** * @type {boolean} */ this.isHexOnly = (0, _pvutils.getParametersValue)(parameters, "isHexOnly", false); /** * @type {ArrayBuffer} */ if ("valueHex" in parameters) this.valueHex = parameters.valueHex.slice(0);else this.valueHex = new ArrayBuffer(0); } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "hexBlock"; } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} Offset after least decoded byte */ fromBER(inputBuffer, inputOffset, inputLength) { //region Basic check for parameters //noinspection JSCheckFunctionSignatures if ((0, _pvutils.checkBufferParams)(this, inputBuffer, inputOffset, inputLength) === false) return -1; //endregion //region Getting Uint8Array from ArrayBuffer const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength); //endregion //region Initial checks if (intBuffer.length === 0) { this.warnings.push("Zero buffer length"); return inputOffset; } //endregion //region Copy input buffer to internal buffer this.valueHex = inputBuffer.slice(inputOffset, inputOffset + inputLength); //endregion this.blockLength = inputLength; return inputOffset + inputLength; } //********************************************************************************** /** * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules) * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes * @returns {ArrayBuffer} */ toBER(sizeOnly = false) { if (this.isHexOnly !== true) { this.error = "Flag \"isHexOnly\" is not set, abort"; return new ArrayBuffer(0); } if (sizeOnly === true) return new ArrayBuffer(this.valueHex.byteLength); //noinspection JSCheckFunctionSignatures return this.valueHex.slice(0); } //********************************************************************************** /** * Convertion for the block to JSON object * @returns {Object} */ toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.blockName = this.constructor.blockName(); object.isHexOnly = this.isHexOnly; object.valueHex = (0, _pvutils.bufferToHexCodes)(this.valueHex, 0, this.valueHex.byteLength); return object; } //********************************************************************************** }; //************************************************************************************** //endregion //************************************************************************************** //region Declaration of identification block class //************************************************************************************** exports.HexBlock = HexBlock; class LocalIdentificationBlock extends HexBlock(LocalBaseBlock) { //********************************************************************************** /** * Constructor for "LocalBaseBlock" class * @param {Object} [parameters={}] * @property {Object} [idBlock] */ constructor(parameters = {}) { super(); if ("idBlock" in parameters) { //region Properties from hexBlock class this.isHexOnly = (0, _pvutils.getParametersValue)(parameters.idBlock, "isHexOnly", false); this.valueHex = (0, _pvutils.getParametersValue)(parameters.idBlock, "valueHex", new ArrayBuffer(0)); //endregion this.tagClass = (0, _pvutils.getParametersValue)(parameters.idBlock, "tagClass", -1); this.tagNumber = (0, _pvutils.getParametersValue)(parameters.idBlock, "tagNumber", -1); this.isConstructed = (0, _pvutils.getParametersValue)(parameters.idBlock, "isConstructed", false); } else { this.tagClass = -1; this.tagNumber = -1; this.isConstructed = false; } } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "identificationBlock"; } //********************************************************************************** /** * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules) * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes * @returns {ArrayBuffer} */ toBER(sizeOnly = false) { //region Initial variables let firstOctet = 0; let retBuf; let retView; //endregion switch (this.tagClass) { case 1: firstOctet |= 0x00; // UNIVERSAL break; case 2: firstOctet |= 0x40; // APPLICATION break; case 3: firstOctet |= 0x80; // CONTEXT-SPECIFIC break; case 4: firstOctet |= 0xC0; // PRIVATE break; default: this.error = "Unknown tag class"; return new ArrayBuffer(0); } if (this.isConstructed) firstOctet |= 0x20; if (this.tagNumber < 31 && !this.isHexOnly) { retBuf = new ArrayBuffer(1); retView = new Uint8Array(retBuf); if (!sizeOnly) { let number = this.tagNumber; number &= 0x1F; firstOctet |= number; retView[0] = firstOctet; } return retBuf; } if (this.isHexOnly === false) { const encodedBuf = (0, _pvutils.utilToBase)(this.tagNumber, 7); const encodedView = new Uint8Array(encodedBuf); const size = encodedBuf.byteLength; retBuf = new ArrayBuffer(size + 1); retView = new Uint8Array(retBuf); retView[0] = firstOctet | 0x1F; if (!sizeOnly) { for (let i = 0; i < size - 1; i++) retView[i + 1] = encodedView[i] | 0x80; retView[size] = encodedView[size - 1]; } return retBuf; } retBuf = new ArrayBuffer(this.valueHex.byteLength + 1); retView = new Uint8Array(retBuf); retView[0] = firstOctet | 0x1F; if (sizeOnly === false) { const curView = new Uint8Array(this.valueHex); for (let i = 0; i < curView.length - 1; i++) retView[i + 1] = curView[i] | 0x80; retView[this.valueHex.byteLength] = curView[curView.length - 1]; } return retBuf; } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} */ fromBER(inputBuffer, inputOffset, inputLength) { //region Basic check for parameters //noinspection JSCheckFunctionSignatures if ((0, _pvutils.checkBufferParams)(this, inputBuffer, inputOffset, inputLength) === false) return -1; //endregion //region Getting Uint8Array from ArrayBuffer const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength); //endregion //region Initial checks if (intBuffer.length === 0) { this.error = "Zero buffer length"; return -1; } //endregion //region Find tag class const tagClassMask = intBuffer[0] & 0xC0; switch (tagClassMask) { case 0x00: this.tagClass = 1; // UNIVERSAL break; case 0x40: this.tagClass = 2; // APPLICATION break; case 0x80: this.tagClass = 3; // CONTEXT-SPECIFIC break; case 0xC0: this.tagClass = 4; // PRIVATE break; default: this.error = "Unknown tag class"; return -1; } //endregion //region Find it's constructed or not this.isConstructed = (intBuffer[0] & 0x20) === 0x20; //endregion //region Find tag number this.isHexOnly = false; const tagNumberMask = intBuffer[0] & 0x1F; //region Simple case (tag number < 31) if (tagNumberMask !== 0x1F) { this.tagNumber = tagNumberMask; this.blockLength = 1; } //endregion //region Tag number bigger or equal to 31 else { let count = 1; this.valueHex = new ArrayBuffer(255); let tagNumberBufferMaxLength = 255; let intTagNumberBuffer = new Uint8Array(this.valueHex); //noinspection JSBitwiseOperatorUsage while (intBuffer[count] & 0x80) { intTagNumberBuffer[count - 1] = intBuffer[count] & 0x7F; count++; if (count >= intBuffer.length) { this.error = "End of input reached before message was fully decoded"; return -1; } //region In case if tag number length is greater than 255 bytes (rare but possible case) if (count === tagNumberBufferMaxLength) { tagNumberBufferMaxLength += 255; const tempBuffer = new ArrayBuffer(tagNumberBufferMaxLength); const tempBufferView = new Uint8Array(tempBuffer); for (let i = 0; i < intTagNumberBuffer.length; i++) tempBufferView[i] = intTagNumberBuffer[i]; this.valueHex = new ArrayBuffer(tagNumberBufferMaxLength); intTagNumberBuffer = new Uint8Array(this.valueHex); } //endregion } this.blockLength = count + 1; intTagNumberBuffer[count - 1] = intBuffer[count] & 0x7F; // Write last byte to buffer //region Cut buffer const tempBuffer = new ArrayBuffer(count); const tempBufferView = new Uint8Array(tempBuffer); for (let i = 0; i < count; i++) tempBufferView[i] = intTagNumberBuffer[i]; this.valueHex = new ArrayBuffer(count); intTagNumberBuffer = new Uint8Array(this.valueHex); intTagNumberBuffer.set(tempBufferView); //endregion //region Try to convert long tag number to short form if (this.blockLength <= 9) this.tagNumber = (0, _pvutils.utilFromBase)(intTagNumberBuffer, 7);else { this.isHexOnly = true; this.warnings.push("Tag too long, represented as hex-coded"); } //endregion } //endregion //endregion //region Check if constructed encoding was using for primitive type if (this.tagClass === 1 && this.isConstructed) { switch (this.tagNumber) { case 1: // Boolean case 2: // REAL case 5: // Null case 6: // OBJECT IDENTIFIER case 9: // REAL case 13: // RELATIVE OBJECT IDENTIFIER case 14: // Time case 23: case 24: case 31: case 32: case 33: case 34: this.error = "Constructed encoding used for primitive type"; return -1; default: } } //endregion return inputOffset + this.blockLength; // Return current offset in input buffer } //********************************************************************************** /** * Convertion for the block to JSON object * @returns {{blockName: string, * tagClass: number, * tagNumber: number, * isConstructed: boolean, * isHexOnly: boolean, * valueHex: ArrayBuffer, * blockLength: number, * error: string, warnings: Array., * valueBeforeDecode: string}} */ toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.blockName = this.constructor.blockName(); object.tagClass = this.tagClass; object.tagNumber = this.tagNumber; object.isConstructed = this.isConstructed; return object; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of length block class //************************************************************************************** class LocalLengthBlock extends LocalBaseBlock { //********************************************************************************** /** * Constructor for "LocalLengthBlock" class * @param {Object} [parameters={}] * @property {Object} [lenBlock] */ constructor(parameters = {}) { super(); if ("lenBlock" in parameters) { this.isIndefiniteForm = (0, _pvutils.getParametersValue)(parameters.lenBlock, "isIndefiniteForm", false); this.longFormUsed = (0, _pvutils.getParametersValue)(parameters.lenBlock, "longFormUsed", false); this.length = (0, _pvutils.getParametersValue)(parameters.lenBlock, "length", 0); } else { this.isIndefiniteForm = false; this.longFormUsed = false; this.length = 0; } } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "lengthBlock"; } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} */ fromBER(inputBuffer, inputOffset, inputLength) { //region Basic check for parameters //noinspection JSCheckFunctionSignatures if ((0, _pvutils.checkBufferParams)(this, inputBuffer, inputOffset, inputLength) === false) return -1; //endregion //region Getting Uint8Array from ArrayBuffer const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength); //endregion //region Initial checks if (intBuffer.length === 0) { this.error = "Zero buffer length"; return -1; } if (intBuffer[0] === 0xFF) { this.error = "Length block 0xFF is reserved by standard"; return -1; } //endregion //region Check for length form type this.isIndefiniteForm = intBuffer[0] === 0x80; //endregion //region Stop working in case of indefinite length form if (this.isIndefiniteForm === true) { this.blockLength = 1; return inputOffset + this.blockLength; } //endregion //region Check is long form of length encoding using this.longFormUsed = !!(intBuffer[0] & 0x80); //endregion //region Stop working in case of short form of length value if (this.longFormUsed === false) { this.length = intBuffer[0]; this.blockLength = 1; return inputOffset + this.blockLength; } //endregion //region Calculate length value in case of long form const count = intBuffer[0] & 0x7F; if (count > 8) // Too big length value { this.error = "Too big integer"; return -1; } if (count + 1 > intBuffer.length) { this.error = "End of input reached before message was fully decoded"; return -1; } const lengthBufferView = new Uint8Array(count); for (let i = 0; i < count; i++) lengthBufferView[i] = intBuffer[i + 1]; if (lengthBufferView[count - 1] === 0x00) this.warnings.push("Needlessly long encoded length"); this.length = (0, _pvutils.utilFromBase)(lengthBufferView, 8); if (this.longFormUsed && this.length <= 127) this.warnings.push("Unneccesary usage of long length form"); this.blockLength = count + 1; //endregion return inputOffset + this.blockLength; // Return current offset in input buffer } //********************************************************************************** /** * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules) * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes * @returns {ArrayBuffer} */ toBER(sizeOnly = false) { //region Initial variables let retBuf; let retView; //endregion if (this.length > 127) this.longFormUsed = true; if (this.isIndefiniteForm) { retBuf = new ArrayBuffer(1); if (sizeOnly === false) { retView = new Uint8Array(retBuf); retView[0] = 0x80; } return retBuf; } if (this.longFormUsed === true) { const encodedBuf = (0, _pvutils.utilToBase)(this.length, 8); if (encodedBuf.byteLength > 127) { this.error = "Too big length"; return new ArrayBuffer(0); } retBuf = new ArrayBuffer(encodedBuf.byteLength + 1); if (sizeOnly === true) return retBuf; const encodedView = new Uint8Array(encodedBuf); retView = new Uint8Array(retBuf); retView[0] = encodedBuf.byteLength | 0x80; for (let i = 0; i < encodedBuf.byteLength; i++) retView[i + 1] = encodedView[i]; return retBuf; } retBuf = new ArrayBuffer(1); if (sizeOnly === false) { retView = new Uint8Array(retBuf); retView[0] = this.length; } return retBuf; } //********************************************************************************** /** * Convertion for the block to JSON object * @returns {{blockName, blockLength, error, warnings, valueBeforeDecode}|{blockName: string, blockLength: number, error: string, warnings: Array., valueBeforeDecode: string}} */ toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.blockName = this.constructor.blockName(); object.isIndefiniteForm = this.isIndefiniteForm; object.longFormUsed = this.longFormUsed; object.length = this.length; return object; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of value block class //************************************************************************************** class ValueBlock extends LocalBaseBlock { //********************************************************************************** /** * Constructor for "ValueBlock" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "valueBlock"; } //********************************************************************************** //noinspection JSUnusedLocalSymbols,JSUnusedLocalSymbols,JSUnusedLocalSymbols /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} */ fromBER(inputBuffer, inputOffset, inputLength) { //region Throw an exception for a function which needs to be specified in extended classes throw TypeError("User need to make a specific function in a class which extends \"ValueBlock\""); //endregion } //********************************************************************************** //noinspection JSUnusedLocalSymbols /** * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules) * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes * @returns {ArrayBuffer} */ toBER(sizeOnly = false) { //region Throw an exception for a function which needs to be specified in extended classes throw TypeError("User need to make a specific function in a class which extends \"ValueBlock\""); //endregion } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of basic ASN.1 block class //************************************************************************************** exports.ValueBlock = ValueBlock; class BaseBlock extends LocalBaseBlock { //********************************************************************************** /** * Constructor for "BaseBlock" class * @param {Object} [parameters={}] * @property {Object} [primitiveSchema] * @property {string} [name] * @property {boolean} [optional] * @param valueBlockType Type of value block */ constructor(parameters = {}, valueBlockType = ValueBlock) { super(parameters); if ("name" in parameters) this.name = parameters.name; if ("optional" in parameters) this.optional = parameters.optional; if ("primitiveSchema" in parameters) this.primitiveSchema = parameters.primitiveSchema; this.idBlock = new LocalIdentificationBlock(parameters); this.lenBlock = new LocalLengthBlock(parameters); this.valueBlock = new valueBlockType(parameters); } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "BaseBlock"; } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} */ fromBER(inputBuffer, inputOffset, inputLength) { const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, this.lenBlock.isIndefiniteForm === true ? inputLength : this.lenBlock.length); if (resultOffset === -1) { this.error = this.valueBlock.error; return resultOffset; } if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength; if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength; if (this.valueBlock.error.length === 0) this.blockLength += this.valueBlock.blockLength; return resultOffset; } //********************************************************************************** /** * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules) * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes * @returns {ArrayBuffer} */ toBER(sizeOnly = false) { let retBuf; const idBlockBuf = this.idBlock.toBER(sizeOnly); const valueBlockSizeBuf = this.valueBlock.toBER(true); this.lenBlock.length = valueBlockSizeBuf.byteLength; const lenBlockBuf = this.lenBlock.toBER(sizeOnly); retBuf = (0, _pvutils.utilConcatBuf)(idBlockBuf, lenBlockBuf); let valueBlockBuf; if (sizeOnly === false) valueBlockBuf = this.valueBlock.toBER(sizeOnly);else valueBlockBuf = new ArrayBuffer(this.lenBlock.length); retBuf = (0, _pvutils.utilConcatBuf)(retBuf, valueBlockBuf); if (this.lenBlock.isIndefiniteForm === true) { const indefBuf = new ArrayBuffer(2); if (sizeOnly === false) { const indefView = new Uint8Array(indefBuf); indefView[0] = 0x00; indefView[1] = 0x00; } retBuf = (0, _pvutils.utilConcatBuf)(retBuf, indefBuf); } return retBuf; } //********************************************************************************** /** * Convertion for the block to JSON object * @returns {{blockName, blockLength, error, warnings, valueBeforeDecode}|{blockName: string, blockLength: number, error: string, warnings: Array., valueBeforeDecode: string}} */ toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.idBlock = this.idBlock.toJSON(); object.lenBlock = this.lenBlock.toJSON(); object.valueBlock = this.valueBlock.toJSON(); if ("name" in this) object.name = this.name; if ("optional" in this) object.optional = this.optional; if ("primitiveSchema" in this) object.primitiveSchema = this.primitiveSchema.toJSON(); return object; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of basic block for all PRIMITIVE types //************************************************************************************** exports.BaseBlock = BaseBlock; class LocalPrimitiveValueBlock extends ValueBlock { //********************************************************************************** /** * Constructor for "LocalPrimitiveValueBlock" class * @param {Object} [parameters={}] * @property {ArrayBuffer} [valueBeforeDecode] */ constructor(parameters = {}) { super(parameters); //region Variables from "hexBlock" class if ("valueHex" in parameters) this.valueHex = parameters.valueHex.slice(0);else this.valueHex = new ArrayBuffer(0); this.isHexOnly = (0, _pvutils.getParametersValue)(parameters, "isHexOnly", true); //endregion } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} */ fromBER(inputBuffer, inputOffset, inputLength) { //region Basic check for parameters //noinspection JSCheckFunctionSignatures if ((0, _pvutils.checkBufferParams)(this, inputBuffer, inputOffset, inputLength) === false) return -1; //endregion //region Getting Uint8Array from ArrayBuffer const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength); //endregion //region Initial checks if (intBuffer.length === 0) { this.warnings.push("Zero buffer length"); return inputOffset; } //endregion //region Copy input buffer into internal buffer this.valueHex = new ArrayBuffer(intBuffer.length); const valueHexView = new Uint8Array(this.valueHex); for (let i = 0; i < intBuffer.length; i++) valueHexView[i] = intBuffer[i]; //endregion this.blockLength = inputLength; return inputOffset + inputLength; } //********************************************************************************** //noinspection JSUnusedLocalSymbols /** * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules) * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes * @returns {ArrayBuffer} */ toBER(sizeOnly = false) { return this.valueHex.slice(0); } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "PrimitiveValueBlock"; } //********************************************************************************** /** * Convertion for the block to JSON object * @returns {{blockName, blockLength, error, warnings, valueBeforeDecode}|{blockName: string, blockLength: number, error: string, warnings: Array., valueBeforeDecode: string}} */ toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.valueHex = (0, _pvutils.bufferToHexCodes)(this.valueHex, 0, this.valueHex.byteLength); object.isHexOnly = this.isHexOnly; return object; } //********************************************************************************** } //************************************************************************************** class Primitive extends BaseBlock { //********************************************************************************** /** * Constructor for "Primitive" class * @param {Object} [parameters={}] * @property {ArrayBuffer} [valueHex] */ constructor(parameters = {}) { super(parameters, LocalPrimitiveValueBlock); this.idBlock.isConstructed = false; } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "PRIMITIVE"; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of basic block for all CONSTRUCTED types //************************************************************************************** exports.Primitive = Primitive; class LocalConstructedValueBlock extends ValueBlock { //********************************************************************************** /** * Constructor for "LocalConstructedValueBlock" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.value = (0, _pvutils.getParametersValue)(parameters, "value", []); this.isIndefiniteForm = (0, _pvutils.getParametersValue)(parameters, "isIndefiniteForm", false); } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} */ fromBER(inputBuffer, inputOffset, inputLength) { //region Store initial offset and length const initialOffset = inputOffset; const initialLength = inputLength; //endregion //region Basic check for parameters //noinspection JSCheckFunctionSignatures if ((0, _pvutils.checkBufferParams)(this, inputBuffer, inputOffset, inputLength) === false) return -1; //endregion //region Getting Uint8Array from ArrayBuffer const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength); //endregion //region Initial checks if (intBuffer.length === 0) { this.warnings.push("Zero buffer length"); return inputOffset; } //endregion //region Aux function function checkLen(indefiniteLength, length) { if (indefiniteLength === true) return 1; return length; } //endregion let currentOffset = inputOffset; while (checkLen(this.isIndefiniteForm, inputLength) > 0) { const returnObject = LocalFromBER(inputBuffer, currentOffset, inputLength); if (returnObject.offset === -1) { this.error = returnObject.result.error; this.warnings.concat(returnObject.result.warnings); return -1; } currentOffset = returnObject.offset; this.blockLength += returnObject.result.blockLength; inputLength -= returnObject.result.blockLength; this.value.push(returnObject.result); if (this.isIndefiniteForm === true && returnObject.result.constructor.blockName() === EndOfContent.blockName()) break; } if (this.isIndefiniteForm === true) { if (this.value[this.value.length - 1].constructor.blockName() === EndOfContent.blockName()) this.value.pop();else this.warnings.push("No EndOfContent block encoded"); } //region Copy "inputBuffer" to "valueBeforeDecode" this.valueBeforeDecode = inputBuffer.slice(initialOffset, initialOffset + initialLength); //endregion return currentOffset; } //********************************************************************************** /** * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules) * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes * @returns {ArrayBuffer} */ toBER(sizeOnly = false) { let retBuf = new ArrayBuffer(0); for (let i = 0; i < this.value.length; i++) { const valueBuf = this.value[i].toBER(sizeOnly); retBuf = (0, _pvutils.utilConcatBuf)(retBuf, valueBuf); } return retBuf; } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "ConstructedValueBlock"; } //********************************************************************************** /** * Convertion for the block to JSON object * @returns {{blockName, blockLength, error, warnings, valueBeforeDecode}|{blockName: string, blockLength: number, error: string, warnings: Array., valueBeforeDecode: string}} */ toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.isIndefiniteForm = this.isIndefiniteForm; object.value = []; for (let i = 0; i < this.value.length; i++) object.value.push(this.value[i].toJSON()); return object; } //********************************************************************************** } //************************************************************************************** class Constructed extends BaseBlock { //********************************************************************************** /** * Constructor for "Constructed" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters, LocalConstructedValueBlock); this.idBlock.isConstructed = true; } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "CONSTRUCTED"; } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} */ fromBER(inputBuffer, inputOffset, inputLength) { this.valueBlock.isIndefiniteForm = this.lenBlock.isIndefiniteForm; const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, this.lenBlock.isIndefiniteForm === true ? inputLength : this.lenBlock.length); if (resultOffset === -1) { this.error = this.valueBlock.error; return resultOffset; } if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength; if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength; if (this.valueBlock.error.length === 0) this.blockLength += this.valueBlock.blockLength; return resultOffset; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of ASN.1 EndOfContent type class //************************************************************************************** exports.Constructed = Constructed; class LocalEndOfContentValueBlock extends ValueBlock { //********************************************************************************** /** * Constructor for "LocalEndOfContentValueBlock" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); } //********************************************************************************** //noinspection JSUnusedLocalSymbols,JSUnusedLocalSymbols /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} */ fromBER(inputBuffer, inputOffset, inputLength) { //region There is no "value block" for EndOfContent type and we need to return the same offset return inputOffset; //endregion } //********************************************************************************** //noinspection JSUnusedLocalSymbols /** * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules) * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes * @returns {ArrayBuffer} */ toBER(sizeOnly = false) { return new ArrayBuffer(0); } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "EndOfContentValueBlock"; } //********************************************************************************** } //************************************************************************************** class EndOfContent extends BaseBlock { //********************************************************************************** constructor(paramaters = {}) { super(paramaters, LocalEndOfContentValueBlock); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 0; // EndOfContent } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "EndOfContent"; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of ASN.1 Boolean type class //************************************************************************************** exports.EndOfContent = EndOfContent; class LocalBooleanValueBlock extends ValueBlock { //********************************************************************************** /** * Constructor for "LocalBooleanValueBlock" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.value = (0, _pvutils.getParametersValue)(parameters, "value", false); this.isHexOnly = (0, _pvutils.getParametersValue)(parameters, "isHexOnly", false); if ("valueHex" in parameters) this.valueHex = parameters.valueHex.slice(0);else { this.valueHex = new ArrayBuffer(1); if (this.value === true) { const view = new Uint8Array(this.valueHex); view[0] = 0xFF; } } } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} Offset after least decoded byte */ fromBER(inputBuffer, inputOffset, inputLength) { //region Basic check for parameters //noinspection JSCheckFunctionSignatures if ((0, _pvutils.checkBufferParams)(this, inputBuffer, inputOffset, inputLength) === false) return -1; //endregion //region Getting Uint8Array from ArrayBuffer const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength); //endregion if (inputLength > 1) this.warnings.push("Boolean value encoded in more then 1 octet"); this.isHexOnly = true; //region Copy input buffer to internal array this.valueHex = new ArrayBuffer(intBuffer.length); const view = new Uint8Array(this.valueHex); for (let i = 0; i < intBuffer.length; i++) view[i] = intBuffer[i]; //endregion if (_pvutils.utilDecodeTC.call(this) !== 0) this.value = true;else this.value = false; this.blockLength = inputLength; return inputOffset + inputLength; } //********************************************************************************** //noinspection JSUnusedLocalSymbols /** * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules) * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes * @returns {ArrayBuffer} */ toBER(sizeOnly = false) { return this.valueHex; } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "BooleanValueBlock"; } //********************************************************************************** /** * Convertion for the block to JSON object * @returns {{blockName, blockLength, error, warnings, valueBeforeDecode}|{blockName: string, blockLength: number, error: string, warnings: Array., valueBeforeDecode: string}} */ toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.value = this.value; object.isHexOnly = this.isHexOnly; object.valueHex = (0, _pvutils.bufferToHexCodes)(this.valueHex, 0, this.valueHex.byteLength); return object; } //********************************************************************************** } //************************************************************************************** class Boolean extends BaseBlock { //********************************************************************************** /** * Constructor for "Boolean" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters, LocalBooleanValueBlock); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 1; // Boolean } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "Boolean"; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of ASN.1 Sequence and Set type classes //************************************************************************************** exports.Boolean = Boolean; class Sequence extends Constructed { //********************************************************************************** /** * Constructor for "Sequence" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 16; // Sequence } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "Sequence"; } //********************************************************************************** } //************************************************************************************** exports.Sequence = Sequence; class Set extends Constructed { //********************************************************************************** /** * Constructor for "Set" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 17; // Set } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "Set"; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of ASN.1 Null type class //************************************************************************************** exports.Set = Set; class Null extends BaseBlock { //********************************************************************************** /** * Constructor for "Null" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters, LocalBaseBlock); // We will not have a call to "Null value block" because of specified "fromBER" and "toBER" functions this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 5; // Null } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "Null"; } //********************************************************************************** //noinspection JSUnusedLocalSymbols /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} Offset after least decoded byte */ fromBER(inputBuffer, inputOffset, inputLength) { if (this.lenBlock.length > 0) this.warnings.push("Non-zero length of value block for Null type"); if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength; if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength; this.blockLength += inputLength; if (inputOffset + inputLength > inputBuffer.byteLength) { this.error = "End of input reached before message was fully decoded (inconsistent offset and length values)"; return -1; } return inputOffset + inputLength; } //********************************************************************************** /** * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules) * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes * @returns {ArrayBuffer} */ toBER(sizeOnly = false) { const retBuf = new ArrayBuffer(2); if (sizeOnly === true) return retBuf; const retView = new Uint8Array(retBuf); retView[0] = 0x05; retView[1] = 0x00; return retBuf; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of ASN.1 OctetString type class //************************************************************************************** exports.Null = Null; class LocalOctetStringValueBlock extends HexBlock(LocalConstructedValueBlock) { //********************************************************************************** /** * Constructor for "LocalOctetStringValueBlock" class * @param {Object} [parameters={}] * @property {ArrayBuffer} [valueHex] */ constructor(parameters = {}) { super(parameters); this.isConstructed = (0, _pvutils.getParametersValue)(parameters, "isConstructed", false); } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} Offset after least decoded byte */ fromBER(inputBuffer, inputOffset, inputLength) { let resultOffset = 0; if (this.isConstructed === true) { this.isHexOnly = false; resultOffset = LocalConstructedValueBlock.prototype.fromBER.call(this, inputBuffer, inputOffset, inputLength); if (resultOffset === -1) return resultOffset; for (let i = 0; i < this.value.length; i++) { const currentBlockName = this.value[i].constructor.blockName(); if (currentBlockName === EndOfContent.blockName()) { if (this.isIndefiniteForm === true) break;else { this.error = "EndOfContent is unexpected, OCTET STRING may consists of OCTET STRINGs only"; return -1; } } if (currentBlockName !== OctetString.blockName()) { this.error = "OCTET STRING may consists of OCTET STRINGs only"; return -1; } } } else { this.isHexOnly = true; resultOffset = super.fromBER(inputBuffer, inputOffset, inputLength); this.blockLength = inputLength; } return resultOffset; } //********************************************************************************** /** * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules) * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes * @returns {ArrayBuffer} */ toBER(sizeOnly = false) { if (this.isConstructed === true) return LocalConstructedValueBlock.prototype.toBER.call(this, sizeOnly); let retBuf = new ArrayBuffer(this.valueHex.byteLength); if (sizeOnly === true) return retBuf; if (this.valueHex.byteLength === 0) return retBuf; retBuf = this.valueHex.slice(0); return retBuf; } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "OctetStringValueBlock"; } //********************************************************************************** toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.isConstructed = this.isConstructed; object.isHexOnly = this.isHexOnly; object.valueHex = (0, _pvutils.bufferToHexCodes)(this.valueHex, 0, this.valueHex.byteLength); return object; } //********************************************************************************** } //************************************************************************************** class OctetString extends BaseBlock { //********************************************************************************** /** * Constructor for "OctetString" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters, LocalOctetStringValueBlock); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 4; // OctetString } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} Offset after least decoded byte */ fromBER(inputBuffer, inputOffset, inputLength) { this.valueBlock.isConstructed = this.idBlock.isConstructed; this.valueBlock.isIndefiniteForm = this.lenBlock.isIndefiniteForm; //region Ability to encode empty OCTET STRING if (inputLength === 0) { if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength; if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength; return inputOffset; } //endregion return super.fromBER(inputBuffer, inputOffset, inputLength); } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "OctetString"; } //********************************************************************************** //noinspection JSUnusedGlobalSymbols /** * Checking that two OCTETSTRINGs are equal * @param {OctetString} octetString */ isEqual(octetString) { //region Check input type if (octetString instanceof OctetString === false) return false; //endregion //region Compare two JSON strings if (JSON.stringify(this) !== JSON.stringify(octetString)) return false; //endregion return true; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of ASN.1 BitString type class //************************************************************************************** exports.OctetString = OctetString; class LocalBitStringValueBlock extends HexBlock(LocalConstructedValueBlock) { //********************************************************************************** /** * Constructor for "LocalBitStringValueBlock" class * @param {Object} [parameters={}] * @property {ArrayBuffer} [valueHex] */ constructor(parameters = {}) { super(parameters); this.unusedBits = (0, _pvutils.getParametersValue)(parameters, "unusedBits", 0); this.isConstructed = (0, _pvutils.getParametersValue)(parameters, "isConstructed", false); this.blockLength = this.valueHex.byteLength; } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} Offset after least decoded byte */ fromBER(inputBuffer, inputOffset, inputLength) { //region Ability to decode zero-length BitString value if (inputLength === 0) return inputOffset; //endregion let resultOffset = -1; //region If the BISTRING supposed to be a constructed value if (this.isConstructed === true) { resultOffset = LocalConstructedValueBlock.prototype.fromBER.call(this, inputBuffer, inputOffset, inputLength); if (resultOffset === -1) return resultOffset; for (let i = 0; i < this.value.length; i++) { const currentBlockName = this.value[i].constructor.blockName(); if (currentBlockName === EndOfContent.blockName()) { if (this.isIndefiniteForm === true) break;else { this.error = "EndOfContent is unexpected, BIT STRING may consists of BIT STRINGs only"; return -1; } } if (currentBlockName !== BitString.blockName()) { this.error = "BIT STRING may consists of BIT STRINGs only"; return -1; } if (this.unusedBits > 0 && this.value[i].valueBlock.unusedBits > 0) { this.error = "Usign of \"unused bits\" inside constructive BIT STRING allowed for least one only"; return -1; } this.unusedBits = this.value[i].valueBlock.unusedBits; if (this.unusedBits > 7) { this.error = "Unused bits for BitString must be in range 0-7"; return -1; } } return resultOffset; } //endregion //region If the BitString supposed to be a primitive value //region Basic check for parameters //noinspection JSCheckFunctionSignatures if ((0, _pvutils.checkBufferParams)(this, inputBuffer, inputOffset, inputLength) === false) return -1; //endregion const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength); this.unusedBits = intBuffer[0]; if (this.unusedBits > 7) { this.error = "Unused bits for BitString must be in range 0-7"; return -1; } //region Copy input buffer to internal buffer this.valueHex = new ArrayBuffer(intBuffer.length - 1); const view = new Uint8Array(this.valueHex); for (let i = 0; i < inputLength - 1; i++) view[i] = intBuffer[i + 1]; //endregion this.blockLength = intBuffer.length; return inputOffset + inputLength; //endregion } //********************************************************************************** /** * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules) * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes * @returns {ArrayBuffer} */ toBER(sizeOnly = false) { if (this.isConstructed === true) return LocalConstructedValueBlock.prototype.toBER.call(this, sizeOnly); if (sizeOnly === true) return new ArrayBuffer(this.valueHex.byteLength + 1); if (this.valueHex.byteLength === 0) return new ArrayBuffer(0); const curView = new Uint8Array(this.valueHex); const retBuf = new ArrayBuffer(this.valueHex.byteLength + 1); const retView = new Uint8Array(retBuf); retView[0] = this.unusedBits; for (let i = 0; i < this.valueHex.byteLength; i++) retView[i + 1] = curView[i]; return retBuf; } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "BitStringValueBlock"; } //********************************************************************************** /** * Convertion for the block to JSON object * @returns {{blockName, blockLength, error, warnings, valueBeforeDecode}|{blockName: string, blockLength: number, error: string, warnings: Array., valueBeforeDecode: string}} */ toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.unusedBits = this.unusedBits; object.isConstructed = this.isConstructed; object.isHexOnly = this.isHexOnly; object.valueHex = (0, _pvutils.bufferToHexCodes)(this.valueHex, 0, this.valueHex.byteLength); return object; } //********************************************************************************** } //************************************************************************************** class BitString extends BaseBlock { //********************************************************************************** /** * Constructor for "BitString" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters, LocalBitStringValueBlock); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 3; // BitString } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "BitString"; } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} Offset after least decoded byte */ fromBER(inputBuffer, inputOffset, inputLength) { //region Ability to encode empty BitString if (inputLength === 0) return inputOffset; //endregion this.valueBlock.isConstructed = this.idBlock.isConstructed; this.valueBlock.isIndefiniteForm = this.lenBlock.isIndefiniteForm; return super.fromBER(inputBuffer, inputOffset, inputLength); } //********************************************************************************** /** * Checking that two BITSTRINGs are equal * @param {BitString} bitString */ isEqual(bitString) { //region Check input type if (bitString instanceof BitString === false) return false; //endregion //region Compare two JSON strings if (JSON.stringify(this) !== JSON.stringify(bitString)) return false; //endregion return true; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of ASN.1 Integer type class //************************************************************************************** /** * @extends ValueBlock */ exports.BitString = BitString; class LocalIntegerValueBlock extends HexBlock(ValueBlock) { //********************************************************************************** /** * Constructor for "LocalIntegerValueBlock" class * @param {Object} [parameters={}] * @property {ArrayBuffer} [valueHex] */ constructor(parameters = {}) { super(parameters); if ("value" in parameters) this.valueDec = parameters.value; } //********************************************************************************** /** * Setter for "valueHex" * @param {ArrayBuffer} _value */ set valueHex(_value) { this._valueHex = _value.slice(0); if (_value.byteLength >= 4) { this.warnings.push("Too big Integer for decoding, hex only"); this.isHexOnly = true; this._valueDec = 0; } else { this.isHexOnly = false; if (_value.byteLength > 0) this._valueDec = _pvutils.utilDecodeTC.call(this); } } //********************************************************************************** /** * Getter for "valueHex" * @returns {ArrayBuffer} */ get valueHex() { return this._valueHex; } //********************************************************************************** /** * Getter for "valueDec" * @param {number} _value */ set valueDec(_value) { this._valueDec = _value; this.isHexOnly = false; this._valueHex = (0, _pvutils.utilEncodeTC)(_value); } //********************************************************************************** /** * Getter for "valueDec" * @returns {number} */ get valueDec() { return this._valueDec; } //********************************************************************************** /** * Base function for converting block from DER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 DER encoded array * @param {!number} inputOffset Offset in ASN.1 DER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @param {number} [expectedLength=0] Expected length of converted "valueHex" buffer * @returns {number} Offset after least decoded byte */ fromDER(inputBuffer, inputOffset, inputLength, expectedLength = 0) { const offset = this.fromBER(inputBuffer, inputOffset, inputLength); if (offset === -1) return offset; const view = new Uint8Array(this._valueHex); if (view[0] === 0x00 && (view[1] & 0x80) !== 0) { const updatedValueHex = new ArrayBuffer(this._valueHex.byteLength - 1); const updatedView = new Uint8Array(updatedValueHex); updatedView.set(new Uint8Array(this._valueHex, 1, this._valueHex.byteLength - 1)); this._valueHex = updatedValueHex.slice(0); } else { if (expectedLength !== 0) { if (this._valueHex.byteLength < expectedLength) { if (expectedLength - this._valueHex.byteLength > 1) expectedLength = this._valueHex.byteLength + 1; const updatedValueHex = new ArrayBuffer(expectedLength); const updatedView = new Uint8Array(updatedValueHex); updatedView.set(view, expectedLength - this._valueHex.byteLength); this._valueHex = updatedValueHex.slice(0); } } } return offset; } //********************************************************************************** /** * Encoding of current ASN.1 block into ASN.1 encoded array (DER rules) * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes * @returns {ArrayBuffer} */ toDER(sizeOnly = false) { const view = new Uint8Array(this._valueHex); switch (true) { case (view[0] & 0x80) !== 0: { const updatedValueHex = new ArrayBuffer(this._valueHex.byteLength + 1); const updatedView = new Uint8Array(updatedValueHex); updatedView[0] = 0x00; updatedView.set(view, 1); this._valueHex = updatedValueHex.slice(0); } break; case view[0] === 0x00 && (view[1] & 0x80) === 0: { const updatedValueHex = new ArrayBuffer(this._valueHex.byteLength - 1); const updatedView = new Uint8Array(updatedValueHex); updatedView.set(new Uint8Array(this._valueHex, 1, this._valueHex.byteLength - 1)); this._valueHex = updatedValueHex.slice(0); } break; default: } return this.toBER(sizeOnly); } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} Offset after least decoded byte */ fromBER(inputBuffer, inputOffset, inputLength) { const resultOffset = super.fromBER(inputBuffer, inputOffset, inputLength); if (resultOffset === -1) return resultOffset; this.blockLength = inputLength; return inputOffset + inputLength; } //********************************************************************************** /** * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules) * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes * @returns {ArrayBuffer} */ toBER(sizeOnly = false) { //noinspection JSCheckFunctionSignatures return this.valueHex.slice(0); } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "IntegerValueBlock"; } //********************************************************************************** //noinspection JSUnusedGlobalSymbols /** * Convertion for the block to JSON object * @returns {Object} */ toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.valueDec = this.valueDec; return object; } //********************************************************************************** /** * Convert current value to decimal string representation */ toString() { //region Aux functions function viewAdd(first, second) { //region Initial variables const c = new Uint8Array([0]); let firstView = new Uint8Array(first); let secondView = new Uint8Array(second); let firstViewCopy = firstView.slice(0); const firstViewCopyLength = firstViewCopy.length - 1; let secondViewCopy = secondView.slice(0); const secondViewCopyLength = secondViewCopy.length - 1; let value = 0; const max = secondViewCopyLength < firstViewCopyLength ? firstViewCopyLength : secondViewCopyLength; let counter = 0; //endregion for (let i = max; i >= 0; i--, counter++) { switch (true) { case counter < secondViewCopy.length: value = firstViewCopy[firstViewCopyLength - counter] + secondViewCopy[secondViewCopyLength - counter] + c[0]; break; default: value = firstViewCopy[firstViewCopyLength - counter] + c[0]; } c[0] = value / 10; switch (true) { case counter >= firstViewCopy.length: firstViewCopy = (0, _pvutils.utilConcatView)(new Uint8Array([value % 10]), firstViewCopy); break; default: firstViewCopy[firstViewCopyLength - counter] = value % 10; } } if (c[0] > 0) firstViewCopy = (0, _pvutils.utilConcatView)(c, firstViewCopy); return firstViewCopy.slice(0); } function power2(n) { if (n >= powers2.length) { for (let p = powers2.length; p <= n; p++) { const c = new Uint8Array([0]); let digits = powers2[p - 1].slice(0); for (let i = digits.length - 1; i >= 0; i--) { const newValue = new Uint8Array([(digits[i] << 1) + c[0]]); c[0] = newValue[0] / 10; digits[i] = newValue[0] % 10; } if (c[0] > 0) digits = (0, _pvutils.utilConcatView)(c, digits); powers2.push(digits); } } return powers2[n]; } function viewSub(first, second) { //region Initial variables let b = 0; let firstView = new Uint8Array(first); let secondView = new Uint8Array(second); let firstViewCopy = firstView.slice(0); const firstViewCopyLength = firstViewCopy.length - 1; let secondViewCopy = secondView.slice(0); const secondViewCopyLength = secondViewCopy.length - 1; let value; let counter = 0; //endregion for (let i = secondViewCopyLength; i >= 0; i--, counter++) { value = firstViewCopy[firstViewCopyLength - counter] - secondViewCopy[secondViewCopyLength - counter] - b; switch (true) { case value < 0: b = 1; firstViewCopy[firstViewCopyLength - counter] = value + 10; break; default: b = 0; firstViewCopy[firstViewCopyLength - counter] = value; } } if (b > 0) { for (let i = firstViewCopyLength - secondViewCopyLength + 1; i >= 0; i--, counter++) { value = firstViewCopy[firstViewCopyLength - counter] - b; if (value < 0) { b = 1; firstViewCopy[firstViewCopyLength - counter] = value + 10; } else { b = 0; firstViewCopy[firstViewCopyLength - counter] = value; break; } } } return firstViewCopy.slice(); } //endregion //region Initial variables const firstBit = this._valueHex.byteLength * 8 - 1; let digits = new Uint8Array(this._valueHex.byteLength * 8 / 3); let bitNumber = 0; let currentByte; const asn1View = new Uint8Array(this._valueHex); let result = ""; let flag = false; //endregion //region Calculate number for (let byteNumber = this._valueHex.byteLength - 1; byteNumber >= 0; byteNumber--) { currentByte = asn1View[byteNumber]; for (let i = 0; i < 8; i++) { if ((currentByte & 1) === 1) { switch (bitNumber) { case firstBit: digits = viewSub(power2(bitNumber), digits); result = "-"; break; default: digits = viewAdd(digits, power2(bitNumber)); } } bitNumber++; currentByte >>= 1; } } //endregion //region Print number for (let i = 0; i < digits.length; i++) { if (digits[i]) flag = true; if (flag) result += digitsString.charAt(digits[i]); } if (flag === false) result += digitsString.charAt(0); //endregion return result; } //********************************************************************************** } //************************************************************************************** class Integer extends BaseBlock { //********************************************************************************** /** * Constructor for "Integer" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters, LocalIntegerValueBlock); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 2; // Integer } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "Integer"; } //********************************************************************************** //noinspection JSUnusedGlobalSymbols /** * Compare two Integer object, or Integer and ArrayBuffer objects * @param {!Integer|ArrayBuffer} otherValue * @returns {boolean} */ isEqual(otherValue) { if (otherValue instanceof Integer) { if (this.valueBlock.isHexOnly && otherValue.valueBlock.isHexOnly) // Compare two ArrayBuffers return (0, _pvutils.isEqualBuffer)(this.valueBlock.valueHex, otherValue.valueBlock.valueHex); if (this.valueBlock.isHexOnly === otherValue.valueBlock.isHexOnly) return this.valueBlock.valueDec === otherValue.valueBlock.valueDec; return false; } if (otherValue instanceof ArrayBuffer) return (0, _pvutils.isEqualBuffer)(this.valueBlock.valueHex, otherValue); return false; } //********************************************************************************** /** * Convert current Integer value from BER into DER format * @returns {Integer} */ convertToDER() { const integer = new Integer({ valueHex: this.valueBlock.valueHex }); integer.valueBlock.toDER(); return integer; } //********************************************************************************** /** * Convert current Integer value from DER to BER format * @returns {Integer} */ convertFromDER() { const expectedLength = this.valueBlock.valueHex.byteLength % 2 ? this.valueBlock.valueHex.byteLength + 1 : this.valueBlock.valueHex.byteLength; const integer = new Integer({ valueHex: this.valueBlock.valueHex }); integer.valueBlock.fromDER(integer.valueBlock.valueHex, 0, integer.valueBlock.valueHex.byteLength, expectedLength); return integer; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of ASN.1 Enumerated type class //************************************************************************************** exports.Integer = Integer; class Enumerated extends Integer { //********************************************************************************** /** * Constructor for "Enumerated" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 10; // Enumerated } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "Enumerated"; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of ASN.1 ObjectIdentifier type class //************************************************************************************** exports.Enumerated = Enumerated; class LocalSidValueBlock extends HexBlock(LocalBaseBlock) { //********************************************************************************** /** * Constructor for "LocalSidValueBlock" class * @param {Object} [parameters={}] * @property {number} [valueDec] * @property {boolean} [isFirstSid] */ constructor(parameters = {}) { super(parameters); this.valueDec = (0, _pvutils.getParametersValue)(parameters, "valueDec", -1); this.isFirstSid = (0, _pvutils.getParametersValue)(parameters, "isFirstSid", false); } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "sidBlock"; } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} Offset after least decoded byte */ fromBER(inputBuffer, inputOffset, inputLength) { if (inputLength === 0) return inputOffset; //region Basic check for parameters //noinspection JSCheckFunctionSignatures if ((0, _pvutils.checkBufferParams)(this, inputBuffer, inputOffset, inputLength) === false) return -1; //endregion const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength); this.valueHex = new ArrayBuffer(inputLength); let view = new Uint8Array(this.valueHex); for (let i = 0; i < inputLength; i++) { view[i] = intBuffer[i] & 0x7F; this.blockLength++; if ((intBuffer[i] & 0x80) === 0x00) break; } //region Ajust size of valueHex buffer const tempValueHex = new ArrayBuffer(this.blockLength); const tempView = new Uint8Array(tempValueHex); for (let i = 0; i < this.blockLength; i++) tempView[i] = view[i]; //noinspection JSCheckFunctionSignatures this.valueHex = tempValueHex.slice(0); view = new Uint8Array(this.valueHex); //endregion if ((intBuffer[this.blockLength - 1] & 0x80) !== 0x00) { this.error = "End of input reached before message was fully decoded"; return -1; } if (view[0] === 0x00) this.warnings.push("Needlessly long format of SID encoding"); if (this.blockLength <= 8) this.valueDec = (0, _pvutils.utilFromBase)(view, 7);else { this.isHexOnly = true; this.warnings.push("Too big SID for decoding, hex only"); } return inputOffset + this.blockLength; } //********************************************************************************** /** * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules) * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes * @returns {ArrayBuffer} */ toBER(sizeOnly = false) { //region Initial variables let retBuf; let retView; //endregion if (this.isHexOnly) { if (sizeOnly === true) return new ArrayBuffer(this.valueHex.byteLength); const curView = new Uint8Array(this.valueHex); retBuf = new ArrayBuffer(this.blockLength); retView = new Uint8Array(retBuf); for (let i = 0; i < this.blockLength - 1; i++) retView[i] = curView[i] | 0x80; retView[this.blockLength - 1] = curView[this.blockLength - 1]; return retBuf; } const encodedBuf = (0, _pvutils.utilToBase)(this.valueDec, 7); if (encodedBuf.byteLength === 0) { this.error = "Error during encoding SID value"; return new ArrayBuffer(0); } retBuf = new ArrayBuffer(encodedBuf.byteLength); if (sizeOnly === false) { const encodedView = new Uint8Array(encodedBuf); retView = new Uint8Array(retBuf); for (let i = 0; i < encodedBuf.byteLength - 1; i++) retView[i] = encodedView[i] | 0x80; retView[encodedBuf.byteLength - 1] = encodedView[encodedBuf.byteLength - 1]; } return retBuf; } //********************************************************************************** /** * Create string representation of current SID block * @returns {string} */ toString() { let result = ""; if (this.isHexOnly === true) result = (0, _pvutils.bufferToHexCodes)(this.valueHex, 0, this.valueHex.byteLength);else { if (this.isFirstSid) { let sidValue = this.valueDec; if (this.valueDec <= 39) result = "0.";else { if (this.valueDec <= 79) { result = "1."; sidValue -= 40; } else { result = "2."; sidValue -= 80; } } result += sidValue.toString(); } else result = this.valueDec.toString(); } return result; } //********************************************************************************** //noinspection JSUnusedGlobalSymbols /** * Convertion for the block to JSON object * @returns {Object} */ toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.valueDec = this.valueDec; object.isFirstSid = this.isFirstSid; return object; } //********************************************************************************** } //************************************************************************************** class LocalObjectIdentifierValueBlock extends ValueBlock { //********************************************************************************** /** * Constructor for "LocalObjectIdentifierValueBlock" class * @param {Object} [parameters={}] * @property {ArrayBuffer} [valueHex] */ constructor(parameters = {}) { super(parameters); this.fromString((0, _pvutils.getParametersValue)(parameters, "value", "")); } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} Offset after least decoded byte */ fromBER(inputBuffer, inputOffset, inputLength) { let resultOffset = inputOffset; while (inputLength > 0) { const sidBlock = new LocalSidValueBlock(); resultOffset = sidBlock.fromBER(inputBuffer, resultOffset, inputLength); if (resultOffset === -1) { this.blockLength = 0; this.error = sidBlock.error; return resultOffset; } if (this.value.length === 0) sidBlock.isFirstSid = true; this.blockLength += sidBlock.blockLength; inputLength -= sidBlock.blockLength; this.value.push(sidBlock); } return resultOffset; } //********************************************************************************** /** * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules) * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes * @returns {ArrayBuffer} */ toBER(sizeOnly = false) { let retBuf = new ArrayBuffer(0); for (let i = 0; i < this.value.length; i++) { const valueBuf = this.value[i].toBER(sizeOnly); if (valueBuf.byteLength === 0) { this.error = this.value[i].error; return new ArrayBuffer(0); } retBuf = (0, _pvutils.utilConcatBuf)(retBuf, valueBuf); } return retBuf; } //********************************************************************************** /** * Create "LocalObjectIdentifierValueBlock" class from string * @param {string} string Input string to convert from * @returns {boolean} */ fromString(string) { this.value = []; // Clear existing SID values let pos1 = 0; let pos2 = 0; let sid = ""; let flag = false; do { pos2 = string.indexOf(".", pos1); if (pos2 === -1) sid = string.substr(pos1);else sid = string.substr(pos1, pos2 - pos1); pos1 = pos2 + 1; if (flag) { const sidBlock = this.value[0]; let plus = 0; switch (sidBlock.valueDec) { case 0: break; case 1: plus = 40; break; case 2: plus = 80; break; default: this.value = []; // clear SID array return false; // ??? } const parsedSID = parseInt(sid, 10); if (isNaN(parsedSID)) return true; sidBlock.valueDec = parsedSID + plus; flag = false; } else { const sidBlock = new LocalSidValueBlock(); sidBlock.valueDec = parseInt(sid, 10); if (isNaN(sidBlock.valueDec)) return true; if (this.value.length === 0) { sidBlock.isFirstSid = true; flag = true; } this.value.push(sidBlock); } } while (pos2 !== -1); return true; } //********************************************************************************** /** * Converts "LocalObjectIdentifierValueBlock" class to string * @returns {string} */ toString() { let result = ""; let isHexOnly = false; for (let i = 0; i < this.value.length; i++) { isHexOnly = this.value[i].isHexOnly; let sidStr = this.value[i].toString(); if (i !== 0) result = `${result}.`; if (isHexOnly) { sidStr = `{${sidStr}}`; if (this.value[i].isFirstSid) result = `2.{${sidStr} - 80}`;else result += sidStr; } else result += sidStr; } return result; } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "ObjectIdentifierValueBlock"; } //********************************************************************************** /** * Convertion for the block to JSON object * @returns {Object} */ toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.value = this.toString(); object.sidArray = []; for (let i = 0; i < this.value.length; i++) object.sidArray.push(this.value[i].toJSON()); return object; } //********************************************************************************** } //************************************************************************************** /** * @extends BaseBlock */ class ObjectIdentifier extends BaseBlock { //********************************************************************************** /** * Constructor for "ObjectIdentifier" class * @param {Object} [parameters={}] * @property {ArrayBuffer} [valueHex] */ constructor(parameters = {}) { super(parameters, LocalObjectIdentifierValueBlock); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 6; // OBJECT IDENTIFIER } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "ObjectIdentifier"; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of all string's classes //************************************************************************************** exports.ObjectIdentifier = ObjectIdentifier; class LocalUtf8StringValueBlock extends HexBlock(LocalBaseBlock) { //********************************************************************************** //noinspection JSUnusedGlobalSymbols /** * Constructor for "LocalUtf8StringValueBlock" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.isHexOnly = true; this.value = ""; // String representation of decoded ArrayBuffer } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "Utf8StringValueBlock"; } //********************************************************************************** //noinspection JSUnusedGlobalSymbols /** * Convertion for the block to JSON object * @returns {Object} */ toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.value = this.value; return object; } //********************************************************************************** } //************************************************************************************** /** * @extends BaseBlock */ class Utf8String extends BaseBlock { //********************************************************************************** /** * Constructor for "Utf8String" class * @param {Object} [parameters={}] * @property {ArrayBuffer} [valueHex] */ constructor(parameters = {}) { super(parameters, LocalUtf8StringValueBlock); if ("value" in parameters) this.fromString(parameters.value); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 12; // Utf8String } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "Utf8String"; } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} Offset after least decoded byte */ fromBER(inputBuffer, inputOffset, inputLength) { const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, this.lenBlock.isIndefiniteForm === true ? inputLength : this.lenBlock.length); if (resultOffset === -1) { this.error = this.valueBlock.error; return resultOffset; } this.fromBuffer(this.valueBlock.valueHex); if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength; if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength; if (this.valueBlock.error.length === 0) this.blockLength += this.valueBlock.blockLength; return resultOffset; } //********************************************************************************** /** * Function converting ArrayBuffer into ASN.1 internal string * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array */ fromBuffer(inputBuffer) { this.valueBlock.value = String.fromCharCode.apply(null, new Uint8Array(inputBuffer)); try { //noinspection JSDeprecatedSymbols this.valueBlock.value = decodeURIComponent(escape(this.valueBlock.value)); } catch (ex) { this.warnings.push(`Error during "decodeURIComponent": ${ex}, using raw string`); } } //********************************************************************************** /** * Function converting JavaScript string into ASN.1 internal class * @param {!string} inputString ASN.1 BER encoded array */ fromString(inputString) { //noinspection JSDeprecatedSymbols const str = unescape(encodeURIComponent(inputString)); const strLen = str.length; this.valueBlock.valueHex = new ArrayBuffer(strLen); const view = new Uint8Array(this.valueBlock.valueHex); for (let i = 0; i < strLen; i++) view[i] = str.charCodeAt(i); this.valueBlock.value = inputString; } //********************************************************************************** } //************************************************************************************** //region Declaration of ASN.1 RelativeObjectIdentifier type class //************************************************************************************** exports.Utf8String = Utf8String; class LocalRelativeSidValueBlock extends HexBlock(LocalBaseBlock) { //********************************************************************************** /** * Constructor for "LocalRelativeSidValueBlock" class * @param {Object} [parameters={}] * @property {number} [valueDec] */ constructor(parameters = {}) { super(parameters); this.valueDec = (0, _pvutils.getParametersValue)(parameters, "valueDec", -1); } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "relativeSidBlock"; } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} Offset after least decoded byte */ fromBER(inputBuffer, inputOffset, inputLength) { if (inputLength === 0) return inputOffset; //region Basic check for parameters //noinspection JSCheckFunctionSignatures if ((0, _pvutils.checkBufferParams)(this, inputBuffer, inputOffset, inputLength) === false) return -1; //endregion const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength); this.valueHex = new ArrayBuffer(inputLength); let view = new Uint8Array(this.valueHex); for (let i = 0; i < inputLength; i++) { view[i] = intBuffer[i] & 0x7F; this.blockLength++; if ((intBuffer[i] & 0x80) === 0x00) break; } //region Ajust size of valueHex buffer const tempValueHex = new ArrayBuffer(this.blockLength); const tempView = new Uint8Array(tempValueHex); for (let i = 0; i < this.blockLength; i++) tempView[i] = view[i]; //noinspection JSCheckFunctionSignatures this.valueHex = tempValueHex.slice(0); view = new Uint8Array(this.valueHex); //endregion if ((intBuffer[this.blockLength - 1] & 0x80) !== 0x00) { this.error = "End of input reached before message was fully decoded"; return -1; } if (view[0] === 0x00) this.warnings.push("Needlessly long format of SID encoding"); if (this.blockLength <= 8) this.valueDec = (0, _pvutils.utilFromBase)(view, 7);else { this.isHexOnly = true; this.warnings.push("Too big SID for decoding, hex only"); } return inputOffset + this.blockLength; } //********************************************************************************** /** * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules) * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes * @returns {ArrayBuffer} */ toBER(sizeOnly = false) { //region Initial variables let retBuf; let retView; //endregion if (this.isHexOnly) { if (sizeOnly === true) return new ArrayBuffer(this.valueHex.byteLength); const curView = new Uint8Array(this.valueHex); retBuf = new ArrayBuffer(this.blockLength); retView = new Uint8Array(retBuf); for (let i = 0; i < this.blockLength - 1; i++) retView[i] = curView[i] | 0x80; retView[this.blockLength - 1] = curView[this.blockLength - 1]; return retBuf; } const encodedBuf = (0, _pvutils.utilToBase)(this.valueDec, 7); if (encodedBuf.byteLength === 0) { this.error = "Error during encoding SID value"; return new ArrayBuffer(0); } retBuf = new ArrayBuffer(encodedBuf.byteLength); if (sizeOnly === false) { const encodedView = new Uint8Array(encodedBuf); retView = new Uint8Array(retBuf); for (let i = 0; i < encodedBuf.byteLength - 1; i++) retView[i] = encodedView[i] | 0x80; retView[encodedBuf.byteLength - 1] = encodedView[encodedBuf.byteLength - 1]; } return retBuf; } //********************************************************************************** /** * Create string representation of current SID block * @returns {string} */ toString() { let result = ""; if (this.isHexOnly === true) result = (0, _pvutils.bufferToHexCodes)(this.valueHex, 0, this.valueHex.byteLength);else { result = this.valueDec.toString(); } return result; } //********************************************************************************** //noinspection JSUnusedGlobalSymbols /** * Convertion for the block to JSON object * @returns {Object} */ toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.valueDec = this.valueDec; return object; } //********************************************************************************** } //************************************************************************************** class LocalRelativeObjectIdentifierValueBlock extends ValueBlock { //********************************************************************************** /** * Constructor for "LocalRelativeObjectIdentifierValueBlock" class * @param {Object} [parameters={}] * @property {ArrayBuffer} [valueHex] */ constructor(parameters = {}) { super(parameters); this.fromString((0, _pvutils.getParametersValue)(parameters, "value", "")); } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} Offset after least decoded byte */ fromBER(inputBuffer, inputOffset, inputLength) { let resultOffset = inputOffset; while (inputLength > 0) { const sidBlock = new LocalRelativeSidValueBlock(); resultOffset = sidBlock.fromBER(inputBuffer, resultOffset, inputLength); if (resultOffset === -1) { this.blockLength = 0; this.error = sidBlock.error; return resultOffset; } this.blockLength += sidBlock.blockLength; inputLength -= sidBlock.blockLength; this.value.push(sidBlock); } return resultOffset; } //********************************************************************************** /** * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules) * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes * @returns {ArrayBuffer} */ toBER(sizeOnly = false) { let retBuf = new ArrayBuffer(0); for (let i = 0; i < this.value.length; i++) { const valueBuf = this.value[i].toBER(sizeOnly); if (valueBuf.byteLength === 0) { this.error = this.value[i].error; return new ArrayBuffer(0); } retBuf = (0, _pvutils.utilConcatBuf)(retBuf, valueBuf); } return retBuf; } //********************************************************************************** /** * Create "LocalRelativeObjectIdentifierValueBlock" class from string * @param {string} string Input string to convert from * @returns {boolean} */ fromString(string) { this.value = []; // Clear existing SID values let pos1 = 0; let pos2 = 0; let sid = ""; do { pos2 = string.indexOf(".", pos1); if (pos2 === -1) sid = string.substr(pos1);else sid = string.substr(pos1, pos2 - pos1); pos1 = pos2 + 1; const sidBlock = new LocalRelativeSidValueBlock(); sidBlock.valueDec = parseInt(sid, 10); if (isNaN(sidBlock.valueDec)) return true; this.value.push(sidBlock); } while (pos2 !== -1); return true; } //********************************************************************************** /** * Converts "LocalRelativeObjectIdentifierValueBlock" class to string * @returns {string} */ toString() { let result = ""; let isHexOnly = false; for (let i = 0; i < this.value.length; i++) { isHexOnly = this.value[i].isHexOnly; let sidStr = this.value[i].toString(); if (i !== 0) result = `${result}.`; if (isHexOnly) { sidStr = `{${sidStr}}`; result += sidStr; } else result += sidStr; } return result; } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "RelativeObjectIdentifierValueBlock"; } //********************************************************************************** /** * Convertion for the block to JSON object * @returns {Object} */ toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.value = this.toString(); object.sidArray = []; for (let i = 0; i < this.value.length; i++) object.sidArray.push(this.value[i].toJSON()); return object; } //********************************************************************************** } //************************************************************************************** /** * @extends BaseBlock */ class RelativeObjectIdentifier extends BaseBlock { //********************************************************************************** /** * Constructor for "RelativeObjectIdentifier" class * @param {Object} [parameters={}] * @property {ArrayBuffer} [valueHex] */ constructor(parameters = {}) { super(parameters, LocalRelativeObjectIdentifierValueBlock); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 13; // RELATIVE OBJECT IDENTIFIER } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "RelativeObjectIdentifier"; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** /** * @extends LocalBaseBlock * @extends HexBlock */ exports.RelativeObjectIdentifier = RelativeObjectIdentifier; class LocalBmpStringValueBlock extends HexBlock(LocalBaseBlock) { //********************************************************************************** /** * Constructor for "LocalBmpStringValueBlock" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.isHexOnly = true; this.value = ""; } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "BmpStringValueBlock"; } //********************************************************************************** //noinspection JSUnusedGlobalSymbols /** * Convertion for the block to JSON object * @returns {Object} */ toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.value = this.value; return object; } //********************************************************************************** } //************************************************************************************** /** * @extends BaseBlock */ class BmpString extends BaseBlock { //********************************************************************************** /** * Constructor for "BmpString" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters, LocalBmpStringValueBlock); if ("value" in parameters) this.fromString(parameters.value); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 30; // BmpString } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "BmpString"; } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} Offset after least decoded byte */ fromBER(inputBuffer, inputOffset, inputLength) { const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, this.lenBlock.isIndefiniteForm === true ? inputLength : this.lenBlock.length); if (resultOffset === -1) { this.error = this.valueBlock.error; return resultOffset; } this.fromBuffer(this.valueBlock.valueHex); if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength; if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength; if (this.valueBlock.error.length === 0) this.blockLength += this.valueBlock.blockLength; return resultOffset; } //********************************************************************************** /** * Function converting ArrayBuffer into ASN.1 internal string * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array */ fromBuffer(inputBuffer) { //noinspection JSCheckFunctionSignatures const copyBuffer = inputBuffer.slice(0); const valueView = new Uint8Array(copyBuffer); for (let i = 0; i < valueView.length; i += 2) { const temp = valueView[i]; valueView[i] = valueView[i + 1]; valueView[i + 1] = temp; } this.valueBlock.value = String.fromCharCode.apply(null, new Uint16Array(copyBuffer)); } //********************************************************************************** /** * Function converting JavaScript string into ASN.1 internal class * @param {!string} inputString ASN.1 BER encoded array */ fromString(inputString) { const strLength = inputString.length; this.valueBlock.valueHex = new ArrayBuffer(strLength * 2); const valueHexView = new Uint8Array(this.valueBlock.valueHex); for (let i = 0; i < strLength; i++) { const codeBuf = (0, _pvutils.utilToBase)(inputString.charCodeAt(i), 8); const codeView = new Uint8Array(codeBuf); if (codeView.length > 2) continue; const dif = 2 - codeView.length; for (let j = codeView.length - 1; j >= 0; j--) valueHexView[i * 2 + j + dif] = codeView[j]; } this.valueBlock.value = inputString; } //********************************************************************************** } //************************************************************************************** exports.BmpString = BmpString; class LocalUniversalStringValueBlock extends HexBlock(LocalBaseBlock) { //********************************************************************************** /** * Constructor for "LocalUniversalStringValueBlock" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.isHexOnly = true; this.value = ""; } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "UniversalStringValueBlock"; } //********************************************************************************** //noinspection JSUnusedGlobalSymbols /** * Convertion for the block to JSON object * @returns {Object} */ toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.value = this.value; return object; } //********************************************************************************** } //************************************************************************************** /** * @extends BaseBlock */ class UniversalString extends BaseBlock { //********************************************************************************** /** * Constructor for "UniversalString" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters, LocalUniversalStringValueBlock); if ("value" in parameters) this.fromString(parameters.value); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 28; // UniversalString } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "UniversalString"; } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} Offset after least decoded byte */ fromBER(inputBuffer, inputOffset, inputLength) { const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, this.lenBlock.isIndefiniteForm === true ? inputLength : this.lenBlock.length); if (resultOffset === -1) { this.error = this.valueBlock.error; return resultOffset; } this.fromBuffer(this.valueBlock.valueHex); if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength; if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength; if (this.valueBlock.error.length === 0) this.blockLength += this.valueBlock.blockLength; return resultOffset; } //********************************************************************************** /** * Function converting ArrayBuffer into ASN.1 internal string * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array */ fromBuffer(inputBuffer) { //noinspection JSCheckFunctionSignatures const copyBuffer = inputBuffer.slice(0); const valueView = new Uint8Array(copyBuffer); for (let i = 0; i < valueView.length; i += 4) { valueView[i] = valueView[i + 3]; valueView[i + 1] = valueView[i + 2]; valueView[i + 2] = 0x00; valueView[i + 3] = 0x00; } this.valueBlock.value = String.fromCharCode.apply(null, new Uint32Array(copyBuffer)); } //********************************************************************************** /** * Function converting JavaScript string into ASN.1 internal class * @param {!string} inputString ASN.1 BER encoded array */ fromString(inputString) { const strLength = inputString.length; this.valueBlock.valueHex = new ArrayBuffer(strLength * 4); const valueHexView = new Uint8Array(this.valueBlock.valueHex); for (let i = 0; i < strLength; i++) { const codeBuf = (0, _pvutils.utilToBase)(inputString.charCodeAt(i), 8); const codeView = new Uint8Array(codeBuf); if (codeView.length > 4) continue; const dif = 4 - codeView.length; for (let j = codeView.length - 1; j >= 0; j--) valueHexView[i * 4 + j + dif] = codeView[j]; } this.valueBlock.value = inputString; } //********************************************************************************** } //************************************************************************************** exports.UniversalString = UniversalString; class LocalSimpleStringValueBlock extends HexBlock(LocalBaseBlock) { //********************************************************************************** /** * Constructor for "LocalSimpleStringValueBlock" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.value = ""; this.isHexOnly = true; } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "SimpleStringValueBlock"; } //********************************************************************************** //noinspection JSUnusedGlobalSymbols /** * Convertion for the block to JSON object * @returns {Object} */ toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.value = this.value; return object; } //********************************************************************************** } //************************************************************************************** /** * @extends BaseBlock */ class LocalSimpleStringBlock extends BaseBlock { //********************************************************************************** /** * Constructor for "LocalSimpleStringBlock" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters, LocalSimpleStringValueBlock); if ("value" in parameters) this.fromString(parameters.value); } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "SIMPLESTRING"; } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} Offset after least decoded byte */ fromBER(inputBuffer, inputOffset, inputLength) { const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, this.lenBlock.isIndefiniteForm === true ? inputLength : this.lenBlock.length); if (resultOffset === -1) { this.error = this.valueBlock.error; return resultOffset; } this.fromBuffer(this.valueBlock.valueHex); if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength; if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength; if (this.valueBlock.error.length === 0) this.blockLength += this.valueBlock.blockLength; return resultOffset; } //********************************************************************************** /** * Function converting ArrayBuffer into ASN.1 internal string * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array */ fromBuffer(inputBuffer) { this.valueBlock.value = String.fromCharCode.apply(null, new Uint8Array(inputBuffer)); } //********************************************************************************** /** * Function converting JavaScript string into ASN.1 internal class * @param {!string} inputString ASN.1 BER encoded array */ fromString(inputString) { const strLen = inputString.length; this.valueBlock.valueHex = new ArrayBuffer(strLen); const view = new Uint8Array(this.valueBlock.valueHex); for (let i = 0; i < strLen; i++) view[i] = inputString.charCodeAt(i); this.valueBlock.value = inputString; } //********************************************************************************** } //************************************************************************************** /** * @extends LocalSimpleStringBlock */ class NumericString extends LocalSimpleStringBlock { //********************************************************************************** /** * Constructor for "NumericString" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 18; // NumericString } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "NumericString"; } //********************************************************************************** } //************************************************************************************** /** * @extends LocalSimpleStringBlock */ exports.NumericString = NumericString; class PrintableString extends LocalSimpleStringBlock { //********************************************************************************** /** * Constructor for "PrintableString" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 19; // PrintableString } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "PrintableString"; } //********************************************************************************** } //************************************************************************************** /** * @extends LocalSimpleStringBlock */ exports.PrintableString = PrintableString; class TeletexString extends LocalSimpleStringBlock { //********************************************************************************** /** * Constructor for "TeletexString" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 20; // TeletexString } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "TeletexString"; } //********************************************************************************** } //************************************************************************************** /** * @extends LocalSimpleStringBlock */ exports.TeletexString = TeletexString; class VideotexString extends LocalSimpleStringBlock { //********************************************************************************** /** * Constructor for "VideotexString" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 21; // VideotexString } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "VideotexString"; } //********************************************************************************** } //************************************************************************************** /** * @extends LocalSimpleStringBlock */ exports.VideotexString = VideotexString; class IA5String extends LocalSimpleStringBlock { //********************************************************************************** /** * Constructor for "IA5String" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 22; // IA5String } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "IA5String"; } //********************************************************************************** } //************************************************************************************** /** * @extends LocalSimpleStringBlock */ exports.IA5String = IA5String; class GraphicString extends LocalSimpleStringBlock { //********************************************************************************** /** * Constructor for "GraphicString" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 25; // GraphicString } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "GraphicString"; } //********************************************************************************** } //************************************************************************************** /** * @extends LocalSimpleStringBlock */ exports.GraphicString = GraphicString; class VisibleString extends LocalSimpleStringBlock { //********************************************************************************** /** * Constructor for "VisibleString" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 26; // VisibleString } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "VisibleString"; } //********************************************************************************** } //************************************************************************************** /** * @extends LocalSimpleStringBlock */ exports.VisibleString = VisibleString; class GeneralString extends LocalSimpleStringBlock { //********************************************************************************** /** * Constructor for "GeneralString" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 27; // GeneralString } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "GeneralString"; } //********************************************************************************** } //************************************************************************************** /** * @extends LocalSimpleStringBlock */ exports.GeneralString = GeneralString; class CharacterString extends LocalSimpleStringBlock { //********************************************************************************** /** * Constructor for "CharacterString" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 29; // CharacterString } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "CharacterString"; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of all date and time classes //************************************************************************************** /** * @extends VisibleString */ exports.CharacterString = CharacterString; class UTCTime extends VisibleString { //********************************************************************************** /** * Constructor for "UTCTime" class * @param {Object} [parameters={}] * @property {string} [value] String representatio of the date * @property {Date} [valueDate] JavaScript "Date" object */ constructor(parameters = {}) { super(parameters); this.year = 0; this.month = 0; this.day = 0; this.hour = 0; this.minute = 0; this.second = 0; //region Create UTCTime from ASN.1 UTC string value if ("value" in parameters) { this.fromString(parameters.value); this.valueBlock.valueHex = new ArrayBuffer(parameters.value.length); const view = new Uint8Array(this.valueBlock.valueHex); for (let i = 0; i < parameters.value.length; i++) view[i] = parameters.value.charCodeAt(i); } //endregion //region Create GeneralizedTime from JavaScript Date type if ("valueDate" in parameters) { this.fromDate(parameters.valueDate); this.valueBlock.valueHex = this.toBuffer(); } //endregion this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 23; // UTCTime } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} Offset after least decoded byte */ fromBER(inputBuffer, inputOffset, inputLength) { const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, this.lenBlock.isIndefiniteForm === true ? inputLength : this.lenBlock.length); if (resultOffset === -1) { this.error = this.valueBlock.error; return resultOffset; } this.fromBuffer(this.valueBlock.valueHex); if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength; if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength; if (this.valueBlock.error.length === 0) this.blockLength += this.valueBlock.blockLength; return resultOffset; } //********************************************************************************** /** * Function converting ArrayBuffer into ASN.1 internal string * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array */ fromBuffer(inputBuffer) { this.fromString(String.fromCharCode.apply(null, new Uint8Array(inputBuffer))); } //********************************************************************************** /** * Function converting ASN.1 internal string into ArrayBuffer * @returns {ArrayBuffer} */ toBuffer() { const str = this.toString(); const buffer = new ArrayBuffer(str.length); const view = new Uint8Array(buffer); for (let i = 0; i < str.length; i++) view[i] = str.charCodeAt(i); return buffer; } //********************************************************************************** /** * Function converting "Date" object into ASN.1 internal string * @param {!Date} inputDate JavaScript "Date" object */ fromDate(inputDate) { this.year = inputDate.getUTCFullYear(); this.month = inputDate.getUTCMonth() + 1; this.day = inputDate.getUTCDate(); this.hour = inputDate.getUTCHours(); this.minute = inputDate.getUTCMinutes(); this.second = inputDate.getUTCSeconds(); } //********************************************************************************** //noinspection JSUnusedGlobalSymbols /** * Function converting ASN.1 internal string into "Date" object * @returns {Date} */ toDate() { return new Date(Date.UTC(this.year, this.month - 1, this.day, this.hour, this.minute, this.second)); } //********************************************************************************** /** * Function converting JavaScript string into ASN.1 internal class * @param {!string} inputString ASN.1 BER encoded array */ fromString(inputString) { //region Parse input string const parser = /(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})Z/ig; const parserArray = parser.exec(inputString); if (parserArray === null) { this.error = "Wrong input string for convertion"; return; } //endregion //region Store parsed values const year = parseInt(parserArray[1], 10); if (year >= 50) this.year = 1900 + year;else this.year = 2000 + year; this.month = parseInt(parserArray[2], 10); this.day = parseInt(parserArray[3], 10); this.hour = parseInt(parserArray[4], 10); this.minute = parseInt(parserArray[5], 10); this.second = parseInt(parserArray[6], 10); //endregion } //********************************************************************************** /** * Function converting ASN.1 internal class into JavaScript string * @returns {string} */ toString() { const outputArray = new Array(7); outputArray[0] = (0, _pvutils.padNumber)(this.year < 2000 ? this.year - 1900 : this.year - 2000, 2); outputArray[1] = (0, _pvutils.padNumber)(this.month, 2); outputArray[2] = (0, _pvutils.padNumber)(this.day, 2); outputArray[3] = (0, _pvutils.padNumber)(this.hour, 2); outputArray[4] = (0, _pvutils.padNumber)(this.minute, 2); outputArray[5] = (0, _pvutils.padNumber)(this.second, 2); outputArray[6] = "Z"; return outputArray.join(""); } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "UTCTime"; } //********************************************************************************** /** * Convertion for the block to JSON object * @returns {Object} */ toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.year = this.year; object.month = this.month; object.day = this.day; object.hour = this.hour; object.minute = this.minute; object.second = this.second; return object; } //********************************************************************************** } //************************************************************************************** /** * @extends VisibleString */ exports.UTCTime = UTCTime; class GeneralizedTime extends VisibleString { //********************************************************************************** /** * Constructor for "GeneralizedTime" class * @param {Object} [parameters={}] * @property {string} [value] String representatio of the date * @property {Date} [valueDate] JavaScript "Date" object */ constructor(parameters = {}) { super(parameters); this.year = 0; this.month = 0; this.day = 0; this.hour = 0; this.minute = 0; this.second = 0; this.millisecond = 0; //region Create UTCTime from ASN.1 UTC string value if ("value" in parameters) { this.fromString(parameters.value); this.valueBlock.valueHex = new ArrayBuffer(parameters.value.length); const view = new Uint8Array(this.valueBlock.valueHex); for (let i = 0; i < parameters.value.length; i++) view[i] = parameters.value.charCodeAt(i); } //endregion //region Create GeneralizedTime from JavaScript Date type if ("valueDate" in parameters) { this.fromDate(parameters.valueDate); this.valueBlock.valueHex = this.toBuffer(); } //endregion this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 24; // GeneralizedTime } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} Offset after least decoded byte */ fromBER(inputBuffer, inputOffset, inputLength) { const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, this.lenBlock.isIndefiniteForm === true ? inputLength : this.lenBlock.length); if (resultOffset === -1) { this.error = this.valueBlock.error; return resultOffset; } this.fromBuffer(this.valueBlock.valueHex); if (this.idBlock.error.length === 0) this.blockLength += this.idBlock.blockLength; if (this.lenBlock.error.length === 0) this.blockLength += this.lenBlock.blockLength; if (this.valueBlock.error.length === 0) this.blockLength += this.valueBlock.blockLength; return resultOffset; } //********************************************************************************** /** * Function converting ArrayBuffer into ASN.1 internal string * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array */ fromBuffer(inputBuffer) { this.fromString(String.fromCharCode.apply(null, new Uint8Array(inputBuffer))); } //********************************************************************************** /** * Function converting ASN.1 internal string into ArrayBuffer * @returns {ArrayBuffer} */ toBuffer() { const str = this.toString(); const buffer = new ArrayBuffer(str.length); const view = new Uint8Array(buffer); for (let i = 0; i < str.length; i++) view[i] = str.charCodeAt(i); return buffer; } //********************************************************************************** /** * Function converting "Date" object into ASN.1 internal string * @param {!Date} inputDate JavaScript "Date" object */ fromDate(inputDate) { this.year = inputDate.getUTCFullYear(); this.month = inputDate.getUTCMonth() + 1; this.day = inputDate.getUTCDate(); this.hour = inputDate.getUTCHours(); this.minute = inputDate.getUTCMinutes(); this.second = inputDate.getUTCSeconds(); this.millisecond = inputDate.getUTCMilliseconds(); } //********************************************************************************** //noinspection JSUnusedGlobalSymbols /** * Function converting ASN.1 internal string into "Date" object * @returns {Date} */ toDate() { return new Date(Date.UTC(this.year, this.month - 1, this.day, this.hour, this.minute, this.second, this.millisecond)); } //********************************************************************************** /** * Function converting JavaScript string into ASN.1 internal class * @param {!string} inputString ASN.1 BER encoded array */ fromString(inputString) { //region Initial variables let isUTC = false; let timeString = ""; let dateTimeString = ""; let fractionPart = 0; let parser; let hourDifference = 0; let minuteDifference = 0; //endregion //region Convert as UTC time if (inputString[inputString.length - 1] === "Z") { timeString = inputString.substr(0, inputString.length - 1); isUTC = true; } //endregion //region Convert as local time else { //noinspection JSPrimitiveTypeWrapperUsage const number = new Number(inputString[inputString.length - 1]); if (isNaN(number.valueOf())) throw new Error("Wrong input string for convertion"); timeString = inputString; } //endregion //region Check that we do not have a "+" and "-" symbols inside UTC time if (isUTC) { if (timeString.indexOf("+") !== -1) throw new Error("Wrong input string for convertion"); if (timeString.indexOf("-") !== -1) throw new Error("Wrong input string for convertion"); } //endregion //region Get "UTC time difference" in case of local time else { let multiplier = 1; let differencePosition = timeString.indexOf("+"); let differenceString = ""; if (differencePosition === -1) { differencePosition = timeString.indexOf("-"); multiplier = -1; } if (differencePosition !== -1) { differenceString = timeString.substr(differencePosition + 1); timeString = timeString.substr(0, differencePosition); if (differenceString.length !== 2 && differenceString.length !== 4) throw new Error("Wrong input string for convertion"); //noinspection JSPrimitiveTypeWrapperUsage let number = new Number(differenceString.substr(0, 2)); if (isNaN(number.valueOf())) throw new Error("Wrong input string for convertion"); hourDifference = multiplier * number; if (differenceString.length === 4) { //noinspection JSPrimitiveTypeWrapperUsage number = new Number(differenceString.substr(2, 2)); if (isNaN(number.valueOf())) throw new Error("Wrong input string for convertion"); minuteDifference = multiplier * number; } } } //endregion //region Get position of fraction point let fractionPointPosition = timeString.indexOf("."); // Check for "full stop" symbol if (fractionPointPosition === -1) fractionPointPosition = timeString.indexOf(","); // Check for "comma" symbol //endregion //region Get fraction part if (fractionPointPosition !== -1) { //noinspection JSPrimitiveTypeWrapperUsage const fractionPartCheck = new Number(`0${timeString.substr(fractionPointPosition)}`); if (isNaN(fractionPartCheck.valueOf())) throw new Error("Wrong input string for convertion"); fractionPart = fractionPartCheck.valueOf(); dateTimeString = timeString.substr(0, fractionPointPosition); } else dateTimeString = timeString; //endregion //region Parse internal date switch (true) { case dateTimeString.length === 8: // "YYYYMMDD" parser = /(\d{4})(\d{2})(\d{2})/ig; if (fractionPointPosition !== -1) throw new Error("Wrong input string for convertion"); // Here we should not have a "fraction point" break; case dateTimeString.length === 10: // "YYYYMMDDHH" parser = /(\d{4})(\d{2})(\d{2})(\d{2})/ig; if (fractionPointPosition !== -1) { let fractionResult = 60 * fractionPart; this.minute = Math.floor(fractionResult); fractionResult = 60 * (fractionResult - this.minute); this.second = Math.floor(fractionResult); fractionResult = 1000 * (fractionResult - this.second); this.millisecond = Math.floor(fractionResult); } break; case dateTimeString.length === 12: // "YYYYMMDDHHMM" parser = /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})/ig; if (fractionPointPosition !== -1) { let fractionResult = 60 * fractionPart; this.second = Math.floor(fractionResult); fractionResult = 1000 * (fractionResult - this.second); this.millisecond = Math.floor(fractionResult); } break; case dateTimeString.length === 14: // "YYYYMMDDHHMMSS" parser = /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/ig; if (fractionPointPosition !== -1) { const fractionResult = 1000 * fractionPart; this.millisecond = Math.floor(fractionResult); } break; default: throw new Error("Wrong input string for convertion"); } //endregion //region Put parsed values at right places const parserArray = parser.exec(dateTimeString); if (parserArray === null) throw new Error("Wrong input string for convertion"); for (let j = 1; j < parserArray.length; j++) { switch (j) { case 1: this.year = parseInt(parserArray[j], 10); break; case 2: this.month = parseInt(parserArray[j], 10); break; case 3: this.day = parseInt(parserArray[j], 10); break; case 4: this.hour = parseInt(parserArray[j], 10) + hourDifference; break; case 5: this.minute = parseInt(parserArray[j], 10) + minuteDifference; break; case 6: this.second = parseInt(parserArray[j], 10); break; default: throw new Error("Wrong input string for convertion"); } } //endregion //region Get final date if (isUTC === false) { const tempDate = new Date(this.year, this.month, this.day, this.hour, this.minute, this.second, this.millisecond); this.year = tempDate.getUTCFullYear(); this.month = tempDate.getUTCMonth(); this.day = tempDate.getUTCDay(); this.hour = tempDate.getUTCHours(); this.minute = tempDate.getUTCMinutes(); this.second = tempDate.getUTCSeconds(); this.millisecond = tempDate.getUTCMilliseconds(); } //endregion } //********************************************************************************** /** * Function converting ASN.1 internal class into JavaScript string * @returns {string} */ toString() { const outputArray = []; outputArray.push((0, _pvutils.padNumber)(this.year, 4)); outputArray.push((0, _pvutils.padNumber)(this.month, 2)); outputArray.push((0, _pvutils.padNumber)(this.day, 2)); outputArray.push((0, _pvutils.padNumber)(this.hour, 2)); outputArray.push((0, _pvutils.padNumber)(this.minute, 2)); outputArray.push((0, _pvutils.padNumber)(this.second, 2)); if (this.millisecond !== 0) { outputArray.push("."); outputArray.push((0, _pvutils.padNumber)(this.millisecond, 3)); } outputArray.push("Z"); return outputArray.join(""); } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "GeneralizedTime"; } //********************************************************************************** /** * Convertion for the block to JSON object * @returns {Object} */ toJSON() { let object = {}; //region Seems at the moment (Sep 2016) there is no way how to check method is supported in "super" object try { object = super.toJSON(); } catch (ex) {} //endregion object.year = this.year; object.month = this.month; object.day = this.day; object.hour = this.hour; object.minute = this.minute; object.second = this.second; object.millisecond = this.millisecond; return object; } //********************************************************************************** } //************************************************************************************** /** * @extends Utf8String */ exports.GeneralizedTime = GeneralizedTime; class DATE extends Utf8String { //********************************************************************************** /** * Constructor for "DATE" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 31; // DATE } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "DATE"; } //********************************************************************************** } //************************************************************************************** /** * @extends Utf8String */ exports.DATE = DATE; class TimeOfDay extends Utf8String { //********************************************************************************** /** * Constructor for "TimeOfDay" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 32; // TimeOfDay } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "TimeOfDay"; } //********************************************************************************** } //************************************************************************************** /** * @extends Utf8String */ exports.TimeOfDay = TimeOfDay; class DateTime extends Utf8String { //********************************************************************************** /** * Constructor for "DateTime" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 33; // DateTime } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "DateTime"; } //********************************************************************************** } //************************************************************************************** /** * @extends Utf8String */ exports.DateTime = DateTime; class Duration extends Utf8String { //********************************************************************************** /** * Constructor for "Duration" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 34; // Duration } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "Duration"; } //********************************************************************************** } //************************************************************************************** /** * @extends Utf8String */ exports.Duration = Duration; class TIME extends Utf8String { //********************************************************************************** /** * Constructor for "Time" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.idBlock.tagClass = 1; // UNIVERSAL this.idBlock.tagNumber = 14; // Time } //********************************************************************************** /** * Aux function, need to get a block name. Need to have it here for inhiritence * @returns {string} */ static blockName() { return "TIME"; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of special ASN.1 schema type Choice //************************************************************************************** exports.TIME = TIME; class Choice { //********************************************************************************** /** * Constructor for "Choice" class * @param {Object} [parameters={}] * @property {Array} [value] Array of ASN.1 types for make a choice from * @property {boolean} [optional] */ constructor(parameters = {}) { this.value = (0, _pvutils.getParametersValue)(parameters, "value", []); this.optional = (0, _pvutils.getParametersValue)(parameters, "optional", false); } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of special ASN.1 schema type Any //************************************************************************************** exports.Choice = Choice; class Any { //********************************************************************************** /** * Constructor for "Any" class * @param {Object} [parameters={}] * @property {string} [name] * @property {boolean} [optional] */ constructor(parameters = {}) { this.name = (0, _pvutils.getParametersValue)(parameters, "name", ""); this.optional = (0, _pvutils.getParametersValue)(parameters, "optional", false); } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of special ASN.1 schema type Repeated //************************************************************************************** exports.Any = Any; class Repeated { //********************************************************************************** /** * Constructor for "Repeated" class * @param {Object} [parameters={}] * @property {string} [name] * @property {boolean} [optional] */ constructor(parameters = {}) { this.name = (0, _pvutils.getParametersValue)(parameters, "name", ""); this.optional = (0, _pvutils.getParametersValue)(parameters, "optional", false); this.value = (0, _pvutils.getParametersValue)(parameters, "value", new Any()); this.local = (0, _pvutils.getParametersValue)(parameters, "local", false); // Could local or global array to store elements } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of special ASN.1 schema type RawData //************************************************************************************** /** * @description Special class providing ability to have "toBER/fromBER" for raw ArrayBuffer */ exports.Repeated = Repeated; class RawData { //********************************************************************************** /** * Constructor for "Repeated" class * @param {Object} [parameters={}] * @property {string} [name] * @property {boolean} [optional] */ constructor(parameters = {}) { this.data = (0, _pvutils.getParametersValue)(parameters, "data", new ArrayBuffer(0)); } //********************************************************************************** /** * Base function for converting block from BER encoded array of bytes * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {number} Offset after least decoded byte */ fromBER(inputBuffer, inputOffset, inputLength) { this.data = inputBuffer.slice(inputOffset, inputLength); return inputOffset + inputLength; } //********************************************************************************** /** * Encoding of current ASN.1 block into ASN.1 encoded array (BER rules) * @param {boolean} [sizeOnly=false] Flag that we need only a size of encoding, not a real array of bytes * @returns {ArrayBuffer} */ toBER(sizeOnly = false) { return this.data; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Major ASN.1 BER decoding function //************************************************************************************** /** * Internal library function for decoding ASN.1 BER * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array * @param {!number} inputOffset Offset in ASN.1 BER encoded array where decoding should be started * @param {!number} inputLength Maximum length of array of bytes which can be using in this function * @returns {{offset: number, result: Object}} */ exports.RawData = RawData; function LocalFromBER(inputBuffer, inputOffset, inputLength) { const incomingOffset = inputOffset; // Need to store initial offset since "inputOffset" is changing in the function //region Local function changing a type for ASN.1 classes function localChangeType(inputObject, newType) { if (inputObject instanceof newType) return inputObject; const newObject = new newType(); newObject.idBlock = inputObject.idBlock; newObject.lenBlock = inputObject.lenBlock; newObject.warnings = inputObject.warnings; //noinspection JSCheckFunctionSignatures newObject.valueBeforeDecode = inputObject.valueBeforeDecode.slice(0); return newObject; } //endregion //region Create a basic ASN.1 type since we need to return errors and warnings from the function let returnObject = new BaseBlock({}, Object); //endregion //region Basic check for parameters const baseBlock = new LocalBaseBlock(); if ((0, _pvutils.checkBufferParams)(baseBlock, inputBuffer, inputOffset, inputLength) === false) { returnObject.error = baseBlock.error; return { offset: -1, result: returnObject }; } //endregion //region Getting Uint8Array from ArrayBuffer const intBuffer = new Uint8Array(inputBuffer, inputOffset, inputLength); //endregion //region Initial checks if (intBuffer.length === 0) { this.error = "Zero buffer length"; return { offset: -1, result: returnObject }; } //endregion //region Decode indentifcation block of ASN.1 BER structure let resultOffset = returnObject.idBlock.fromBER(inputBuffer, inputOffset, inputLength); returnObject.warnings.concat(returnObject.idBlock.warnings); if (resultOffset === -1) { returnObject.error = returnObject.idBlock.error; return { offset: -1, result: returnObject }; } inputOffset = resultOffset; inputLength -= returnObject.idBlock.blockLength; //endregion //region Decode length block of ASN.1 BER structure resultOffset = returnObject.lenBlock.fromBER(inputBuffer, inputOffset, inputLength); returnObject.warnings.concat(returnObject.lenBlock.warnings); if (resultOffset === -1) { returnObject.error = returnObject.lenBlock.error; return { offset: -1, result: returnObject }; } inputOffset = resultOffset; inputLength -= returnObject.lenBlock.blockLength; //endregion //region Check for usign indefinite length form in encoding for primitive types if (returnObject.idBlock.isConstructed === false && returnObject.lenBlock.isIndefiniteForm === true) { returnObject.error = "Indefinite length form used for primitive encoding form"; return { offset: -1, result: returnObject }; } //endregion //region Switch ASN.1 block type let newASN1Type = BaseBlock; switch (returnObject.idBlock.tagClass) { //region UNIVERSAL case 1: //region Check for reserved tag numbers if (returnObject.idBlock.tagNumber >= 37 && returnObject.idBlock.isHexOnly === false) { returnObject.error = "UNIVERSAL 37 and upper tags are reserved by ASN.1 standard"; return { offset: -1, result: returnObject }; } //endregion switch (returnObject.idBlock.tagNumber) { //region EndOfContent type case 0: //region Check for EndOfContent type if (returnObject.idBlock.isConstructed === true && returnObject.lenBlock.length > 0) { returnObject.error = "Type [UNIVERSAL 0] is reserved"; return { offset: -1, result: returnObject }; } //endregion newASN1Type = EndOfContent; break; //endregion //region Boolean type case 1: newASN1Type = Boolean; break; //endregion //region Integer type case 2: newASN1Type = Integer; break; //endregion //region BitString type case 3: newASN1Type = BitString; break; //endregion //region OctetString type case 4: newASN1Type = OctetString; break; //endregion //region Null type case 5: newASN1Type = Null; break; //endregion //region OBJECT IDENTIFIER type case 6: newASN1Type = ObjectIdentifier; break; //endregion //region Enumerated type case 10: newASN1Type = Enumerated; break; //endregion //region Utf8String type case 12: newASN1Type = Utf8String; break; //endregion //region Time type //region RELATIVE OBJECT IDENTIFIER type case 13: newASN1Type = RelativeObjectIdentifier; break; //endregion case 14: newASN1Type = TIME; break; //endregion //region ASN.1 reserved type case 15: returnObject.error = "[UNIVERSAL 15] is reserved by ASN.1 standard"; return { offset: -1, result: returnObject }; //endregion //region Sequence type case 16: newASN1Type = Sequence; break; //endregion //region Set type case 17: newASN1Type = Set; break; //endregion //region NumericString type case 18: newASN1Type = NumericString; break; //endregion //region PrintableString type case 19: newASN1Type = PrintableString; break; //endregion //region TeletexString type case 20: newASN1Type = TeletexString; break; //endregion //region VideotexString type case 21: newASN1Type = VideotexString; break; //endregion //region IA5String type case 22: newASN1Type = IA5String; break; //endregion //region UTCTime type case 23: newASN1Type = UTCTime; break; //endregion //region GeneralizedTime type case 24: newASN1Type = GeneralizedTime; break; //endregion //region GraphicString type case 25: newASN1Type = GraphicString; break; //endregion //region VisibleString type case 26: newASN1Type = VisibleString; break; //endregion //region GeneralString type case 27: newASN1Type = GeneralString; break; //endregion //region UniversalString type case 28: newASN1Type = UniversalString; break; //endregion //region CharacterString type case 29: newASN1Type = CharacterString; break; //endregion //region BmpString type case 30: newASN1Type = BmpString; break; //endregion //region DATE type case 31: newASN1Type = DATE; break; //endregion //region TimeOfDay type case 32: newASN1Type = TimeOfDay; break; //endregion //region Date-Time type case 33: newASN1Type = DateTime; break; //endregion //region Duration type case 34: newASN1Type = Duration; break; //endregion //region default default: { let newObject; if (returnObject.idBlock.isConstructed === true) newObject = new Constructed();else newObject = new Primitive(); newObject.idBlock = returnObject.idBlock; newObject.lenBlock = returnObject.lenBlock; newObject.warnings = returnObject.warnings; returnObject = newObject; resultOffset = returnObject.fromBER(inputBuffer, inputOffset, inputLength); } //endregion } break; //endregion //region All other tag classes case 2: // APPLICATION case 3: // CONTEXT-SPECIFIC case 4: // PRIVATE default: { if (returnObject.idBlock.isConstructed === true) newASN1Type = Constructed;else newASN1Type = Primitive; } //endregion } //endregion //region Change type and perform BER decoding returnObject = localChangeType(returnObject, newASN1Type); resultOffset = returnObject.fromBER(inputBuffer, inputOffset, returnObject.lenBlock.isIndefiniteForm === true ? inputLength : returnObject.lenBlock.length); //endregion //region Coping incoming buffer for entire ASN.1 block returnObject.valueBeforeDecode = inputBuffer.slice(incomingOffset, incomingOffset + returnObject.blockLength); //endregion return { offset: resultOffset, result: returnObject }; } //************************************************************************************** /** * Major function for decoding ASN.1 BER array into internal library structuries * @param {!ArrayBuffer} inputBuffer ASN.1 BER encoded array of bytes */ function fromBER(inputBuffer) { if (inputBuffer.byteLength === 0) { const result = new BaseBlock({}, Object); result.error = "Input buffer has zero length"; return { offset: -1, result }; } return LocalFromBER(inputBuffer, 0, inputBuffer.byteLength); } //************************************************************************************** //endregion //************************************************************************************** //region Major scheme verification function //************************************************************************************** /** * Compare of two ASN.1 object trees * @param {!Object} root Root of input ASN.1 object tree * @param {!Object} inputData Input ASN.1 object tree * @param {!Object} inputSchema Input ASN.1 schema to compare with * @return {{verified: boolean}|{verified:boolean, result: Object}} */ function compareSchema(root, inputData, inputSchema) { //region Special case for Choice schema element type if (inputSchema instanceof Choice) { const choiceResult = false; for (let j = 0; j < inputSchema.value.length; j++) { const result = compareSchema(root, inputData, inputSchema.value[j]); if (result.verified === true) { return { verified: true, result: root }; } } if (choiceResult === false) { const _result = { verified: false, result: { error: "Wrong values for Choice type" } }; if (inputSchema.hasOwnProperty("name")) _result.name = inputSchema.name; return _result; } } //endregion //region Special case for Any schema element type if (inputSchema instanceof Any) { //region Add named component of ASN.1 schema if (inputSchema.hasOwnProperty("name")) root[inputSchema.name] = inputData; //endregion return { verified: true, result: root }; } //endregion //region Initial check if (root instanceof Object === false) { return { verified: false, result: { error: "Wrong root object" } }; } if (inputData instanceof Object === false) { return { verified: false, result: { error: "Wrong ASN.1 data" } }; } if (inputSchema instanceof Object === false) { return { verified: false, result: { error: "Wrong ASN.1 schema" } }; } if ("idBlock" in inputSchema === false) { return { verified: false, result: { error: "Wrong ASN.1 schema" } }; } //endregion //region Comparing idBlock properties in ASN.1 data and ASN.1 schema //region Encode and decode ASN.1 schema idBlock /// This encoding/decoding is neccessary because could be an errors in schema definition if ("fromBER" in inputSchema.idBlock === false) { return { verified: false, result: { error: "Wrong ASN.1 schema" } }; } if ("toBER" in inputSchema.idBlock === false) { return { verified: false, result: { error: "Wrong ASN.1 schema" } }; } const encodedId = inputSchema.idBlock.toBER(false); if (encodedId.byteLength === 0) { return { verified: false, result: { error: "Error encoding idBlock for ASN.1 schema" } }; } const decodedOffset = inputSchema.idBlock.fromBER(encodedId, 0, encodedId.byteLength); if (decodedOffset === -1) { return { verified: false, result: { error: "Error decoding idBlock for ASN.1 schema" } }; } //endregion //region tagClass if (inputSchema.idBlock.hasOwnProperty("tagClass") === false) { return { verified: false, result: { error: "Wrong ASN.1 schema" } }; } if (inputSchema.idBlock.tagClass !== inputData.idBlock.tagClass) { return { verified: false, result: root }; } //endregion //region tagNumber if (inputSchema.idBlock.hasOwnProperty("tagNumber") === false) { return { verified: false, result: { error: "Wrong ASN.1 schema" } }; } if (inputSchema.idBlock.tagNumber !== inputData.idBlock.tagNumber) { return { verified: false, result: root }; } //endregion //region isConstructed if (inputSchema.idBlock.hasOwnProperty("isConstructed") === false) { return { verified: false, result: { error: "Wrong ASN.1 schema" } }; } if (inputSchema.idBlock.isConstructed !== inputData.idBlock.isConstructed) { return { verified: false, result: root }; } //endregion //region isHexOnly if ("isHexOnly" in inputSchema.idBlock === false) // Since 'isHexOnly' is an inhirited property { return { verified: false, result: { error: "Wrong ASN.1 schema" } }; } if (inputSchema.idBlock.isHexOnly !== inputData.idBlock.isHexOnly) { return { verified: false, result: root }; } //endregion //region valueHex if (inputSchema.idBlock.isHexOnly === true) { if ("valueHex" in inputSchema.idBlock === false) // Since 'valueHex' is an inhirited property { return { verified: false, result: { error: "Wrong ASN.1 schema" } }; } const schemaView = new Uint8Array(inputSchema.idBlock.valueHex); const asn1View = new Uint8Array(inputData.idBlock.valueHex); if (schemaView.length !== asn1View.length) { return { verified: false, result: root }; } for (let i = 0; i < schemaView.length; i++) { if (schemaView[i] !== asn1View[1]) { return { verified: false, result: root }; } } } //endregion //endregion //region Add named component of ASN.1 schema if (inputSchema.hasOwnProperty("name")) { inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, ""); if (inputSchema.name !== "") root[inputSchema.name] = inputData; } //endregion //region Getting next ASN.1 block for comparition if (inputSchema.idBlock.isConstructed === true) { let admission = 0; let result = { verified: false }; let maxLength = inputSchema.valueBlock.value.length; if (maxLength > 0) { if (inputSchema.valueBlock.value[0] instanceof Repeated) maxLength = inputData.valueBlock.value.length; } //region Special case when constructive value has no elements if (maxLength === 0) { return { verified: true, result: root }; } //endregion //region Special case when "inputData" has no values and "inputSchema" has all optional values if (inputData.valueBlock.value.length === 0 && inputSchema.valueBlock.value.length !== 0) { let _optional = true; for (let i = 0; i < inputSchema.valueBlock.value.length; i++) _optional = _optional && (inputSchema.valueBlock.value[i].optional || false); if (_optional === true) { return { verified: true, result: root }; } //region Delete early added name of block if (inputSchema.hasOwnProperty("name")) { inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, ""); if (inputSchema.name !== "") delete root[inputSchema.name]; } //endregion root.error = "Inconsistent object length"; return { verified: false, result: root }; } //endregion for (let i = 0; i < maxLength; i++) { //region Special case when there is an "optional" element of ASN.1 schema at the end if (i - admission >= inputData.valueBlock.value.length) { if (inputSchema.valueBlock.value[i].optional === false) { const _result = { verified: false, result: root }; root.error = "Inconsistent length between ASN.1 data and schema"; //region Delete early added name of block if (inputSchema.hasOwnProperty("name")) { inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, ""); if (inputSchema.name !== "") { delete root[inputSchema.name]; _result.name = inputSchema.name; } } //endregion return _result; } } //endregion else { //region Special case for Repeated type of ASN.1 schema element if (inputSchema.valueBlock.value[0] instanceof Repeated) { result = compareSchema(root, inputData.valueBlock.value[i], inputSchema.valueBlock.value[0].value); if (result.verified === false) { if (inputSchema.valueBlock.value[0].optional === true) admission++;else { //region Delete early added name of block if (inputSchema.hasOwnProperty("name")) { inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, ""); if (inputSchema.name !== "") delete root[inputSchema.name]; } //endregion return result; } } if ("name" in inputSchema.valueBlock.value[0] && inputSchema.valueBlock.value[0].name.length > 0) { let arrayRoot = {}; if ("local" in inputSchema.valueBlock.value[0] && inputSchema.valueBlock.value[0].local === true) arrayRoot = inputData;else arrayRoot = root; if (typeof arrayRoot[inputSchema.valueBlock.value[0].name] === "undefined") arrayRoot[inputSchema.valueBlock.value[0].name] = []; arrayRoot[inputSchema.valueBlock.value[0].name].push(inputData.valueBlock.value[i]); } } //endregion else { result = compareSchema(root, inputData.valueBlock.value[i - admission], inputSchema.valueBlock.value[i]); if (result.verified === false) { if (inputSchema.valueBlock.value[i].optional === true) admission++;else { //region Delete early added name of block if (inputSchema.hasOwnProperty("name")) { inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, ""); if (inputSchema.name !== "") delete root[inputSchema.name]; } //endregion return result; } } } } } if (result.verified === false) // The situation may take place if last element is "optional" and verification failed { const _result = { verified: false, result: root }; //region Delete early added name of block if (inputSchema.hasOwnProperty("name")) { inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, ""); if (inputSchema.name !== "") { delete root[inputSchema.name]; _result.name = inputSchema.name; } } //endregion return _result; } return { verified: true, result: root }; } //endregion //region Ability to parse internal value for primitive-encoded value (value of OctetString, for example) if ("primitiveSchema" in inputSchema && "valueHex" in inputData.valueBlock) { //region Decoding of raw ASN.1 data const asn1 = fromBER(inputData.valueBlock.valueHex); if (asn1.offset === -1) { const _result = { verified: false, result: asn1.result }; //region Delete early added name of block if (inputSchema.hasOwnProperty("name")) { inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, ""); if (inputSchema.name !== "") { delete root[inputSchema.name]; _result.name = inputSchema.name; } } //endregion return _result; } //endregion return compareSchema(root, asn1.result, inputSchema.primitiveSchema); } return { verified: true, result: root }; //endregion } //************************************************************************************** //noinspection JSUnusedGlobalSymbols /** * ASN.1 schema verification for ArrayBuffer data * @param {!ArrayBuffer} inputBuffer Input BER-encoded ASN.1 data * @param {!Object} inputSchema Input ASN.1 schema to verify against to * @return {{verified: boolean}|{verified:boolean, result: Object}} */ function verifySchema(inputBuffer, inputSchema) { //region Initial check if (inputSchema instanceof Object === false) { return { verified: false, result: { error: "Wrong ASN.1 schema type" } }; } //endregion //region Decoding of raw ASN.1 data const asn1 = fromBER(inputBuffer); if (asn1.offset === -1) { return { verified: false, result: asn1.result }; } //endregion //region Compare ASN.1 struct with input schema return compareSchema(asn1.result, asn1.result, inputSchema); //endregion } //************************************************************************************** //endregion //************************************************************************************** //region Major function converting JSON to ASN.1 objects //************************************************************************************** //noinspection JSUnusedGlobalSymbols /** * Converting from JSON to ASN.1 objects * @param {string|Object} json JSON string or object to convert to ASN.1 objects */ function fromJSON(json) {} // TODO Implement //************************************************************************************** //endregion //************************************************************************************** //# sourceMappingURL=asn1.js.map