@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
JavaScript
/* 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
{
//******************************************************************************