UNPKG

@penneo/asn1js

Version:

asn1js is a pure JavaScript library implementing this standard. ASN.1 is the basis of all X.509 related data structures and numerous other protocols used on the web

1,590 lines (1,423 loc) 168 kB
/* eslint-disable indent */ /* * Copyright (c) 2016-2018, Peculiar Ventures * All rights reserved. * * Author 2016-2018, Yury Strozhevsky <www.strozhevsky.com>. * * 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. * */ //************************************************************************************** import { getParametersValue, padNumber, isEqualBuffer, bufferToHexCodes, checkBufferParams, utilToBase, utilFromBase, utilEncodeTC, utilDecodeTC, utilConcatBuf, utilConcatView } from "@penneo/pvutils"; //************************************************************************************** //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.<string>} warnings * @property {ArrayBuffer} valueBeforeDecode */ class LocalBaseBlock { //********************************************************************************** /** * Constructor for "LocalBaseBlock" class * @param {Object} [parameters={}] * @property {ArrayBuffer} [valueBeforeDecode] */ constructor(parameters = {}) { /** * @type {number} blockLength */ this.blockLength = getParametersValue(parameters, "blockLength", 0); /** * @type {string} error */ this.error = getParametersValue(parameters, "error", ""); /** * @type {Array.<string>} warnings */ this.warnings = 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.<string>, valueBeforeDecode: string}} */ toJSON() { return { blockName: this.constructor.blockName(), blockLength: this.blockLength, error: this.error, warnings: this.warnings, valueBeforeDecode: bufferToHexCodes(this.valueBeforeDecode, 0, this.valueBeforeDecode.byteLength) }; } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Description for "LocalHexBlock" class //************************************************************************************** /** * Class used as a base block for all remaining ASN.1 classes * @extends LocalBaseBlock * @typedef LocalHexBlock * @property {number} blockLength * @property {string} error * @property {Array.<string>} warnings * @property {ArrayBuffer} valueBeforeDecode * @property {boolean} isHexOnly * @property {ArrayBuffer} valueHex */ //noinspection JSUnusedLocalSymbols const LocalHexBlock = BaseClass => class LocalHexBlockMixin extends BaseClass { //********************************************************************************** //noinspection JSUnusedGlobalSymbols /** * Constructor for "LocalHexBlock" class * @param {Object} [parameters={}] * @property {ArrayBuffer} [valueHex] */ constructor(parameters = {}) { super(parameters); /** * @type {boolean} */ this.isHexOnly = 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(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 = bufferToHexCodes(this.valueHex, 0, this.valueHex.byteLength); return object; } //********************************************************************************** }; //************************************************************************************** //endregion //************************************************************************************** //region Declaration of identification block class //************************************************************************************** class LocalIdentificationBlock extends LocalHexBlock(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 = getParametersValue(parameters.idBlock, "isHexOnly", false); this.valueHex = getParametersValue(parameters.idBlock, "valueHex", new ArrayBuffer(0)); //endregion this.tagClass = getParametersValue(parameters.idBlock, "tagClass", (-1)); this.tagNumber = getParametersValue(parameters.idBlock, "tagNumber", (-1)); this.isConstructed = 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 = 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(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 = 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 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.<string>, * 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 = getParametersValue(parameters.lenBlock, "isIndefiniteForm", false); this.longFormUsed = getParametersValue(parameters.lenBlock, "longFormUsed", false); this.length = 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(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 = 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 = 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.<string>, 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 LocalValueBlock extends LocalBaseBlock { //********************************************************************************** /** * Constructor for "LocalValueBlock" 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 \"LocalValueBlock\""); //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 \"LocalValueBlock\""); //endregion } //********************************************************************************** } //************************************************************************************** //endregion //************************************************************************************** //region Declaration of basic ASN.1 block class //************************************************************************************** export 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 = LocalValueBlock) { 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 = utilConcatBuf(idBlockBuf, lenBlockBuf); let valueBlockBuf; if(sizeOnly === false) valueBlockBuf = this.valueBlock.toBER(sizeOnly); else valueBlockBuf = new ArrayBuffer(this.lenBlock.length); retBuf = 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 = 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.<string>, 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 //************************************************************************************** class LocalPrimitiveValueBlock extends LocalValueBlock { //********************************************************************************** /** * 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 = 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(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.<string>, 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 = bufferToHexCodes(this.valueHex, 0, this.valueHex.byteLength); object.isHexOnly = this.isHexOnly; return object; } //********************************************************************************** } //************************************************************************************** export 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 //************************************************************************************** class LocalConstructedValueBlock extends LocalValueBlock { //********************************************************************************** /** * Constructor for "LocalConstructedValueBlock" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.value = getParametersValue(parameters, "value", []); this.isIndefiniteForm = 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(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 = 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.<string>, 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; } //********************************************************************************** } //************************************************************************************** export 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 //************************************************************************************** class LocalEndOfContentValueBlock extends LocalValueBlock { //********************************************************************************** /** * 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"; } //********************************************************************************** } //************************************************************************************** export 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 //************************************************************************************** class LocalBooleanValueBlock extends LocalValueBlock { //********************************************************************************** /** * Constructor for "LocalBooleanValueBlock" class * @param {Object} [parameters={}] */ constructor(parameters = {}) { super(parameters); this.value = getParametersValue(parameters, "value", false); this.isHexOnly = 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(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(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.<string>, 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 = bufferToHexCodes(this.valueHex, 0, this.valueHex.byteLength); return object; } //********************************************************************************** } //************************************************************************************** export 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 //************************************************************************************** export 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"; } //********************************************************************************** } //************************************************************************************** export class Set extends Constructed { //******************************************************************************