// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2017 Fedor Indutny. All rights reserved. MIT license. import { Reporter } from "internal:deno_node/polyfills/_crypto/crypto_browserify/asn1.js/base/reporter.js"; import { DecoderBuffer, EncoderBuffer, } from "internal:deno_node/polyfills/_crypto/crypto_browserify/asn1.js/base/buffer.js"; import { assert } from "internal:deno_node/polyfills/_util/asserts.ts"; // Supported tags const tags = [ "seq", "seqof", "set", "setof", "objid", "bool", "gentime", "utctime", "null_", "enum", "int", "objDesc", "bitstr", "bmpstr", "charstr", "genstr", "graphstr", "ia5str", "iso646str", "numstr", "octstr", "printstr", "t61str", "unistr", "utf8str", "videostr", ]; // Public methods list const methods = [ "key", "obj", "use", "optional", "explicit", "implicit", "def", "choice", "any", "contains", ].concat(tags); // Overrided methods list const overrided = [ "_peekTag", "_decodeTag", "_use", "_decodeStr", "_decodeObjid", "_decodeTime", "_decodeNull", "_decodeInt", "_decodeBool", "_decodeList", "_encodeComposite", "_encodeStr", "_encodeObjid", "_encodeTime", "_encodeNull", "_encodeInt", "_encodeBool", ]; export function Node(enc, parent, name) { const state = {}; this._baseState = state; state.name = name; state.enc = enc; state.parent = parent || null; state.children = null; // State state.tag = null; state.args = null; state.reverseArgs = null; state.choice = null; state.optional = false; state.any = false; state.obj = false; state.use = null; state.useDecoder = null; state.key = null; state["default"] = null; state.explicit = null; state.implicit = null; state.contains = null; // Should create new instance on each method if (!state.parent) { state.children = []; this._wrap(); } } const stateProps = [ "enc", "parent", "children", "tag", "args", "reverseArgs", "choice", "optional", "any", "obj", "use", "alteredUse", "key", "default", "explicit", "implicit", "contains", ]; Node.prototype.clone = function clone() { const state = this._baseState; const cstate = {}; stateProps.forEach(function (prop) { cstate[prop] = state[prop]; }); const res = new this.constructor(cstate.parent); res._baseState = cstate; return res; }; Node.prototype._wrap = function wrap() { const state = this._baseState; methods.forEach(function (method) { this[method] = function _wrappedMethod() { const clone = new this.constructor(this); state.children.push(clone); return clone[method].apply(clone, arguments); }; }, this); }; Node.prototype._init = function init(body) { const state = this._baseState; assert(state.parent === null); body.call(this); // Filter children state.children = state.children.filter(function (child) { return child._baseState.parent === this; }, this); assert(state.children.length === 1, "Root node can have only one child"); }; Node.prototype._useArgs = function useArgs(args) { const state = this._baseState; // Filter children and args const children = args.filter(function (arg) { return arg instanceof this.constructor; }, this); args = args.filter(function (arg) { return !(arg instanceof this.constructor); }, this); if (children.length !== 0) { assert(state.children === null); state.children = children; // Replace parent to maintain backward link children.forEach(function (child) { child._baseState.parent = this; }, this); } if (args.length !== 0) { assert(state.args === null); state.args = args; state.reverseArgs = args.map(function (arg) { if (typeof arg !== "object" || arg.constructor !== Object) { return arg; } const res = {}; Object.keys(arg).forEach(function (key) { if (key == (key | 0)) { key |= 0; } const value = arg[key]; res[value] = key; }); return res; }); } }; // // Overrided methods // overrided.forEach(function (method) { Node.prototype[method] = function _overrided() { const state = this._baseState; throw new Error(method + " not implemented for encoding: " + state.enc); }; }); // // Public methods // tags.forEach(function (tag) { Node.prototype[tag] = function _tagMethod() { const state = this._baseState; const args = Array.prototype.slice.call(arguments); assert(state.tag === null); state.tag = tag; this._useArgs(args); return this; }; }); Node.prototype.use = function use(item) { assert(item); const state = this._baseState; assert(state.use === null); state.use = item; return this; }; Node.prototype.optional = function optional() { const state = this._baseState; state.optional = true; return this; }; Node.prototype.def = function def(val) { const state = this._baseState; assert(state["default"] === null); state["default"] = val; state.optional = true; return this; }; Node.prototype.explicit = function explicit(num) { const state = this._baseState; assert(state.explicit === null && state.implicit === null); state.explicit = num; return this; }; Node.prototype.implicit = function implicit(num) { const state = this._baseState; assert(state.explicit === null && state.implicit === null); state.implicit = num; return this; }; Node.prototype.obj = function obj() { const state = this._baseState; const args = Array.prototype.slice.call(arguments); state.obj = true; if (args.length !== 0) { this._useArgs(args); } return this; }; Node.prototype.key = function key(newKey) { const state = this._baseState; assert(state.key === null); state.key = newKey; return this; }; Node.prototype.any = function any() { const state = this._baseState; state.any = true; return this; }; Node.prototype.choice = function choice(obj) { const state = this._baseState; assert(state.choice === null); state.choice = obj; this._useArgs( Object.keys(obj).map(function (key) { return obj[key]; }), ); return this; }; Node.prototype.contains = function contains(item) { const state = this._baseState; assert(state.use === null); state.contains = item; return this; }; // // Decoding // Node.prototype._decode = function decode(input, options) { const state = this._baseState; // Decode root node if (state.parent === null) { return input.wrapResult(state.children[0]._decode(input, options)); } let result = state["default"]; let present = true; let prevKey = null; if (state.key !== null) { prevKey = input.enterKey(state.key); } // Check if tag is there if (state.optional) { let tag = null; if (state.explicit !== null) { tag = state.explicit; } else if (state.implicit !== null) { tag = state.implicit; } else if (state.tag !== null) { tag = state.tag; } if (tag === null && !state.any) { // Trial and Error const save = input.save(); try { if (state.choice === null) { this._decodeGeneric(state.tag, input, options); } else { this._decodeChoice(input, options); } present = true; } catch (_e) { present = false; } input.restore(save); } else { present = this._peekTag(input, tag, state.any); if (input.isError(present)) { return present; } } } // Push object on stack let prevObj; if (state.obj && present) { prevObj = input.enterObject(); } if (present) { // Unwrap explicit values if (state.explicit !== null) { const explicit = this._decodeTag(input, state.explicit); if (input.isError(explicit)) { return explicit; } input = explicit; } const start = input.offset; // Unwrap implicit and normal values if (state.use === null && state.choice === null) { let save; if (state.any) { save = input.save(); } const body = this._decodeTag( input, state.implicit !== null ? state.implicit : state.tag, state.any, ); if (input.isError(body)) { return body; } if (state.any) { result = input.raw(save); } else { input = body; } } if (options && options.track && state.tag !== null) { options.track(input.path(), start, input.length, "tagged"); } if (options && options.track && state.tag !== null) { options.track(input.path(), input.offset, input.length, "content"); } // Select proper method for tag if (state.any) { // no-op } else if (state.choice === null) { result = this._decodeGeneric(state.tag, input, options); } else { result = this._decodeChoice(input, options); } if (input.isError(result)) { return result; } // Decode children if (!state.any && state.choice === null && state.children !== null) { state.children.forEach(function decodeChildren(child) { // NOTE: We are ignoring errors here, to let parser continue with other // parts of encoded data child._decode(input, options); }); } // Decode contained/encoded by schema, only in bit or octet strings if (state.contains && (state.tag === "octstr" || state.tag === "bitstr")) { const data = new DecoderBuffer(result); result = this._getUse(state.contains, input._reporterState.obj) ._decode(data, options); } } // Pop object if (state.obj && present) { result = input.leaveObject(prevObj); } // Set key if (state.key !== null && (result !== null || present === true)) { input.leaveKey(prevKey, state.key, result); } else if (prevKey !== null) { input.exitKey(prevKey); } return result; }; Node.prototype._decodeGeneric = function decodeGeneric(tag, input, options) { const state = this._baseState; if (tag === "seq" || tag === "set") { return null; } if (tag === "seqof" || tag === "setof") { return this._decodeList(input, tag, state.args[0], options); } else if (/str$/.test(tag)) { return this._decodeStr(input, tag, options); } else if (tag === "objid" && state.args) { return this._decodeObjid(input, state.args[0], state.args[1], options); } else if (tag === "objid") { return this._decodeObjid(input, null, null, options); } else if (tag === "gentime" || tag === "utctime") { return this._decodeTime(input, tag, options); } else if (tag === "null_") { return this._decodeNull(input, options); } else if (tag === "bool") { return this._decodeBool(input, options); } else if (tag === "objDesc") { return this._decodeStr(input, tag, options); } else if (tag === "int" || tag === "enum") { return this._decodeInt(input, state.args && state.args[0], options); } if (state.use !== null) { return this._getUse(state.use, input._reporterState.obj) ._decode(input, options); } else { return input.error("unknown tag: " + tag); } }; Node.prototype._getUse = function _getUse(entity, obj) { const state = this._baseState; // Create altered use decoder if implicit is set state.useDecoder = this._use(entity, obj); assert(state.useDecoder._baseState.parent === null); state.useDecoder = state.useDecoder._baseState.children[0]; if (state.implicit !== state.useDecoder._baseState.implicit) { state.useDecoder = state.useDecoder.clone(); state.useDecoder._baseState.implicit = state.implicit; } return state.useDecoder; }; Node.prototype._decodeChoice = function decodeChoice(input, options) { const state = this._baseState; let result = null; let match = false; Object.keys(state.choice).some(function (key) { const save = input.save(); const node = state.choice[key]; try { const value = node._decode(input, options); if (input.isError(value)) { return false; } result = { type: key, value: value }; match = true; } catch (_e) { input.restore(save); return false; } return true; }, this); if (!match) { return input.error("Choice not matched"); } return result; }; // // Encoding // Node.prototype._createEncoderBuffer = function createEncoderBuffer(data) { return new EncoderBuffer(data, this.reporter); }; Node.prototype._encode = function encode(data, reporter, parent) { const state = this._baseState; if (state["default"] !== null && state["default"] === data) { return; } const result = this._encodeValue(data, reporter, parent); if (result === undefined) { return; } if (this._skipDefault(result, reporter, parent)) { return; } return result; }; Node.prototype._encodeValue = function encode(data, reporter, parent) { const state = this._baseState; // Decode root node if (state.parent === null) { return state.children[0]._encode(data, reporter || new Reporter()); } let result = null; // Set reporter to share it with a child class this.reporter = reporter; // Check if data is there if (state.optional && data === undefined) { if (state["default"] !== null) { data = state["default"]; } else { return; } } // Encode children first let content = null; let primitive = false; if (state.any) { // Anything that was given is translated to buffer result = this._createEncoderBuffer(data); } else if (state.choice) { result = this._encodeChoice(data, reporter); } else if (state.contains) { content = this._getUse(state.contains, parent)._encode(data, reporter); primitive = true; } else if (state.children) { content = state.children.map(function (child) { if (child._baseState.tag === "null_") { return child._encode(null, reporter, data); } if (child._baseState.key === null) { return reporter.error("Child should have a key"); } const prevKey = reporter.enterKey(child._baseState.key); if (typeof data !== "object") { return reporter.error("Child expected, but input is not object"); } const res = child._encode(data[child._baseState.key], reporter, data); reporter.leaveKey(prevKey); return res; }, this).filter(function (child) { return child; }); content = this._createEncoderBuffer(content); } else { if (state.tag === "seqof" || state.tag === "setof") { // TODO(indutny): this should be thrown on DSL level if (!(state.args && state.args.length === 1)) { return reporter.error("Too many args for : " + state.tag); } if (!Array.isArray(data)) { return reporter.error("seqof/setof, but data is not Array"); } const child = this.clone(); child._baseState.implicit = null; content = this._createEncoderBuffer(data.map(function (item) { const state = this._baseState; return this._getUse(state.args[0], data)._encode(item, reporter); }, child)); } else if (state.use !== null) { result = this._getUse(state.use, parent)._encode(data, reporter); } else { content = this._encodePrimitive(state.tag, data); primitive = true; } } // Encode data itself if (!state.any && state.choice === null) { const tag = state.implicit !== null ? state.implicit : state.tag; const cls = state.implicit === null ? "universal" : "context"; if (tag === null) { if (state.use === null) { reporter.error("Tag could be omitted only for .use()"); } } else { if (state.use === null) { result = this._encodeComposite(tag, primitive, cls, content); } } } // Wrap in explicit if (state.explicit !== null) { result = this._encodeComposite(state.explicit, false, "context", result); } return result; }; Node.prototype._encodeChoice = function encodeChoice(data, reporter) { const state = this._baseState; const node = state.choice[data.type]; if (!node) { assert( false, data.type + " not found in " + JSON.stringify(Object.keys(state.choice)), ); } return node._encode(data.value, reporter); }; Node.prototype._encodePrimitive = function encodePrimitive(tag, data) { const state = this._baseState; if (/str$/.test(tag)) { return this._encodeStr(data, tag); } else if (tag === "objid" && state.args) { return this._encodeObjid(data, state.reverseArgs[0], state.args[1]); } else if (tag === "objid") { return this._encodeObjid(data, null, null); } else if (tag === "gentime" || tag === "utctime") { return this._encodeTime(data, tag); } else if (tag === "null_") { return this._encodeNull(); } else if (tag === "int" || tag === "enum") { return this._encodeInt(data, state.args && state.reverseArgs[0]); } else if (tag === "bool") { return this._encodeBool(data); } else if (tag === "objDesc") { return this._encodeStr(data, tag); } else { throw new Error("Unsupported tag: " + tag); } }; Node.prototype._isNumstr = function isNumstr(str) { return /^[0-9 ]*$/.test(str); }; Node.prototype._isPrintstr = function isPrintstr(str) { return /^[A-Za-z0-9 '()+,-./:=?]*$/.test(str); };