asn1tools-js
Version:
ASN.1 encoding and decoding library for TypeScript/JavaScript, compatible with Python asn1tools
214 lines • 8.19 kB
JavaScript
;
/**
* ASN.1 type implementations using BER encoding
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.EnumeratedType = exports.NullType = exports.OctetStringType = exports.BooleanType = exports.IntegerType = exports.BaseType = void 0;
const types_1 = require("../types");
const encoding_1 = require("./encoding");
/**
* Base class for all ASN.1 types
*/
class BaseType {
constructor(name, tag) {
this.constructed = false;
this.name = name;
this.tag = tag;
}
encodeWithTag(content) {
const tagBytes = (0, encoding_1.encodeTag)(this.tag, this.constructed ? types_1.BER.ENCODING.CONSTRUCTED : types_1.BER.ENCODING.PRIMITIVE);
const lengthBytes = (0, encoding_1.encodeLength)(content.length);
const result = new Uint8Array(tagBytes.length + lengthBytes.length + content.length);
let offset = 0;
result.set(tagBytes, offset);
offset += tagBytes.length;
result.set(lengthBytes, offset);
offset += lengthBytes.length;
result.set(content, offset);
return result;
}
decodeWithTag(data, offset = 0) {
if (offset >= data.length) {
throw new types_1.DecodeError(`Unexpected end of data while decoding ${this.name}`, offset);
}
const tagInfo = (0, encoding_1.decodeTag)(data, offset);
let currentOffset = offset + tagInfo.length;
// Verify tag matches expected
if (tagInfo.tag !== this.tag) {
throw new types_1.DecodeError(`Expected tag ${this.tag} for ${this.name}, got ${tagInfo.tag}`, offset);
}
const lengthInfo = (0, encoding_1.decodeLength)(data, currentOffset);
currentOffset += lengthInfo.octets;
if (currentOffset + lengthInfo.length > data.length) {
throw new types_1.DecodeError(`Not enough data for ${this.name}: expected ${lengthInfo.length} bytes`, currentOffset);
}
const content = data.slice(currentOffset, currentOffset + lengthInfo.length);
return {
content,
totalLength: tagInfo.length + lengthInfo.octets + lengthInfo.length
};
}
}
exports.BaseType = BaseType;
/**
* ASN.1 INTEGER type
*/
class IntegerType extends BaseType {
constructor(name) {
super(name, types_1.BER.TAG.INTEGER);
}
encode(value) {
if (typeof value !== 'number' && typeof value !== 'bigint') {
throw new types_1.EncodeError(`INTEGER ${this.name}: expected number or bigint, got ${typeof value}`);
}
const content = (0, encoding_1.encodeSignedInteger)(value);
return this.encodeWithTag(content);
}
decode(data, offset = 0) {
const { content, totalLength } = this.decodeWithTag(data, offset);
if (content.length === 0) {
throw new types_1.DecodeError(`INTEGER ${this.name}: empty content`, offset);
}
const bigintValue = (0, encoding_1.decodeSignedInteger)(content);
const value = (0, encoding_1.toSafeNumber)(bigintValue);
return { value, length: totalLength };
}
}
exports.IntegerType = IntegerType;
/**
* ASN.1 BOOLEAN type
*/
class BooleanType extends BaseType {
constructor(name) {
super(name, types_1.BER.TAG.BOOLEAN);
}
encode(value) {
if (typeof value !== 'boolean') {
throw new types_1.EncodeError(`BOOLEAN ${this.name}: expected boolean, got ${typeof value}`);
}
const content = new Uint8Array([value ? 0xff : 0x00]);
return this.encodeWithTag(content);
}
decode(data, offset = 0) {
const { content, totalLength } = this.decodeWithTag(data, offset);
if (content.length !== 1) {
throw new types_1.DecodeError(`BOOLEAN ${this.name}: expected 1 byte content, got ${content.length}`, offset);
}
const value = content[0] !== 0;
return { value, length: totalLength };
}
}
exports.BooleanType = BooleanType;
/**
* ASN.1 OCTET STRING type
*/
class OctetStringType extends BaseType {
constructor(name) {
super(name, types_1.BER.TAG.OCTET_STRING);
}
encode(value) {
let content;
if (value instanceof Uint8Array) {
content = value;
}
else if (value instanceof ArrayBuffer) {
content = new Uint8Array(value);
}
else if (Array.isArray(value)) {
content = new Uint8Array(value);
}
else if (typeof value === 'string') {
// Assume hex string
const hex = value.replace(/[^0-9a-fA-F]/g, '');
if (hex.length % 2 !== 0) {
throw new types_1.EncodeError(`OCTET STRING ${this.name}: hex string must have even length`);
}
content = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) {
content[i / 2] = parseInt(hex.substr(i, 2), 16);
}
}
else {
throw new types_1.EncodeError(`OCTET STRING ${this.name}: expected Uint8Array, ArrayBuffer, number[], or hex string, got ${typeof value}`);
}
return this.encodeWithTag(content);
}
decode(data, offset = 0) {
const { content, totalLength } = this.decodeWithTag(data, offset);
return { value: content, length: totalLength };
}
}
exports.OctetStringType = OctetStringType;
/**
* ASN.1 NULL type
*/
class NullType extends BaseType {
constructor(name) {
super(name, types_1.BER.TAG.NULL);
}
encode(value) {
if (value !== null && value !== undefined) {
throw new types_1.EncodeError(`NULL ${this.name}: expected null or undefined, got ${typeof value}`);
}
const content = new Uint8Array(0);
return this.encodeWithTag(content);
}
decode(data, offset = 0) {
const { content, totalLength } = this.decodeWithTag(data, offset);
if (content.length !== 0) {
throw new types_1.DecodeError(`NULL ${this.name}: expected empty content, got ${content.length} bytes`, offset);
}
return { value: null, length: totalLength };
}
}
exports.NullType = NullType;
/**
* ASN.1 ENUMERATED type
*/
class EnumeratedType extends BaseType {
constructor(name, values) {
super(name, types_1.BER.TAG.ENUMERATED);
this.valueMap = new Map();
this.nameMap = new Map();
for (const [valueName, valueNumber] of values) {
this.valueMap.set(valueName, valueNumber);
this.nameMap.set(valueNumber, valueName);
}
}
encode(value) {
let enumValue;
if (typeof value === 'string') {
const mappedValue = this.valueMap.get(value);
if (mappedValue === undefined) {
throw new types_1.EncodeError(`ENUMERATED ${this.name}: unknown value '${value}'`);
}
enumValue = mappedValue;
}
else if (typeof value === 'number') {
if (!this.nameMap.has(value)) {
throw new types_1.EncodeError(`ENUMERATED ${this.name}: unknown numeric value ${value}`);
}
enumValue = value;
}
else {
throw new types_1.EncodeError(`ENUMERATED ${this.name}: expected string or number, got ${typeof value}`);
}
const content = (0, encoding_1.encodeSignedInteger)(enumValue);
return this.encodeWithTag(content);
}
decode(data, offset = 0) {
const { content, totalLength } = this.decodeWithTag(data, offset);
if (content.length === 0) {
throw new types_1.DecodeError(`ENUMERATED ${this.name}: empty content`, offset);
}
const bigintValue = (0, encoding_1.decodeSignedInteger)(content);
const numericValue = Number(bigintValue);
const name = this.nameMap.get(numericValue);
if (name === undefined) {
throw new types_1.DecodeError(`ENUMERATED ${this.name}: unknown value ${numericValue}`, offset);
}
return { value: name, length: totalLength };
}
}
exports.EnumeratedType = EnumeratedType;
//# sourceMappingURL=types.js.map