@lapo/asn1js
Advanced tools
Comparing version 1.2.4 to 1.3.0
1057
asn1.js
// ASN.1 JavaScript decoder | ||
// Copyright (c) 2008-2022 Lapo Luchini <lapo@lapo.it> | ||
// Copyright (c) 2008-2024 Lapo Luchini <lapo@lapo.it> | ||
@@ -16,13 +16,29 @@ // Permission to use, copy, modify, and/or distribute this software for any | ||
(typeof define != 'undefined' ? define : function (factory) { 'use strict'; | ||
if (typeof module == 'object') module.exports = factory(function (name) { return require(name); }); | ||
else window.asn1 = factory(function (name) { return window[name.substring(2)]; }); | ||
})(function (require) { | ||
"use strict"; | ||
import { Int10 } from './int10.js'; | ||
import { oids } from './oids.js'; | ||
var Int10 = require('./int10'), | ||
oids = require('./oids'), | ||
ellipsis = "\u2026", | ||
const | ||
ellipsis = '\u2026', | ||
reTimeS = /^(\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|(-(?:0\d|1[0-2])|[+](?:0\d|1[0-4]))([0-5]\d)?)?$/, | ||
reTimeL = /^(\d\d\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|(-(?:0\d|1[0-2])|[+](?:0\d|1[0-4]))([0-5]\d)?)?$/; | ||
reTimeL = /^(\d\d\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|(-(?:0\d|1[0-2])|[+](?:0\d|1[0-4]))([0-5]\d)?)?$/, | ||
hexDigits = '0123456789ABCDEF', | ||
b64Safe = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_', | ||
tableT61 = [ | ||
['', ''], | ||
['AEIOUaeiou', 'ÀÈÌÒÙàèìòù'], // Grave | ||
['ACEILNORSUYZacegilnorsuyz', 'ÁĆÉÍĹŃÓŔŚÚÝŹáćéģíĺńóŕśúýź'], // Acute | ||
['ACEGHIJOSUWYaceghijosuwy', 'ÂĈÊĜĤÎĴÔŜÛŴŶâĉêĝĥîĵôŝûŵŷ'], // Circumflex | ||
['AINOUainou', 'ÃĨÑÕŨãĩñõũ'], // Tilde | ||
['AEIOUaeiou', 'ĀĒĪŌŪāēīōū'], // Macron | ||
['AGUagu', 'ĂĞŬăğŭ'], // Breve | ||
['CEGIZcegz', 'ĊĖĠİŻċėġż'], // Dot | ||
['AEIOUYaeiouy', 'ÄËÏÖÜŸäëïöüÿ'], // Umlaut or diæresis | ||
['', ''], | ||
['AUau', 'ÅŮåů'], // Ring | ||
['CGKLNRSTcklnrst', 'ÇĢĶĻŅŖŞŢçķļņŗşţ'], // Cedilla | ||
['', ''], | ||
['OUou', 'ŐŰőű'], // Double Acute | ||
['AEIUaeiu', 'ĄĘĮŲąęįų'], // Ogonek | ||
['CDELNRSTZcdelnrstz', 'ČĎĚĽŇŘŠŤŽčďěľňřšťž'] // Caron | ||
]; | ||
@@ -35,344 +51,280 @@ function stringCut(str, len) { | ||
function Stream(enc, pos) { | ||
if (enc instanceof Stream) { | ||
this.enc = enc.enc; | ||
this.pos = enc.pos; | ||
} else { | ||
// enc should be an array or a binary string | ||
this.enc = enc; | ||
this.pos = pos; | ||
function checkPrintable(s) { | ||
let i, v; | ||
for (i = 0; i < s.length; ++i) { | ||
v = s.charCodeAt(i); | ||
if (v < 32 && v != 9 && v != 10 && v != 13) // [\t\r\n] are (kinda) printable | ||
throw new Error('Unprintable character at index ' + i + ' (code ' + s.str.charCodeAt(i) + ')'); | ||
} | ||
} | ||
Stream.prototype.get = function (pos) { | ||
if (pos === undefined) | ||
pos = this.pos++; | ||
if (pos >= this.enc.length) | ||
throw 'Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length; | ||
return (typeof this.enc == "string") ? this.enc.charCodeAt(pos) : this.enc[pos]; | ||
}; | ||
Stream.prototype.hexDigits = "0123456789ABCDEF"; | ||
Stream.prototype.hexByte = function (b) { | ||
return this.hexDigits.charAt((b >> 4) & 0xF) + this.hexDigits.charAt(b & 0xF); | ||
}; | ||
Stream.prototype.hexDump = function (start, end, raw) { | ||
var s = ""; | ||
for (var i = start; i < end; ++i) { | ||
s += this.hexByte(this.get(i)); | ||
if (raw !== true) | ||
switch (i & 0xF) { | ||
case 0x7: s += " "; break; | ||
case 0xF: s += "\n"; break; | ||
default: s += " "; | ||
} | ||
class Stream { | ||
constructor(enc, pos) { | ||
if (enc instanceof Stream) { | ||
this.enc = enc.enc; | ||
this.pos = enc.pos; | ||
} else { | ||
// enc should be an array or a binary string | ||
this.enc = enc; | ||
this.pos = pos; | ||
} | ||
} | ||
return s; | ||
}; | ||
var b64Safe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; | ||
Stream.prototype.b64Dump = function (start, end) { | ||
var extra = (end - start) % 3, | ||
s = '', | ||
i, c; | ||
for (i = start; i + 2 < end; i += 3) { | ||
c = this.get(i) << 16 | this.get(i + 1) << 8 | this.get(i + 2); | ||
s += b64Safe.charAt(c >> 18 & 0x3F); | ||
s += b64Safe.charAt(c >> 12 & 0x3F); | ||
s += b64Safe.charAt(c >> 6 & 0x3F); | ||
s += b64Safe.charAt(c & 0x3F); | ||
get(pos) { | ||
if (pos === undefined) | ||
pos = this.pos++; | ||
if (pos >= this.enc.length) | ||
throw 'Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length; | ||
return (typeof this.enc == 'string') ? this.enc.charCodeAt(pos) : this.enc[pos]; | ||
} | ||
if (extra > 0) { | ||
c = this.get(i) << 16; | ||
if (extra > 1) c |= this.get(i + 1) << 8; | ||
s += b64Safe.charAt(c >> 18 & 0x3F); | ||
s += b64Safe.charAt(c >> 12 & 0x3F); | ||
if (extra == 2) s += b64Safe.charAt(c >> 6 & 0x3F); | ||
hexByte(b) { | ||
return hexDigits.charAt((b >> 4) & 0xF) + hexDigits.charAt(b & 0xF); | ||
} | ||
return s; | ||
}; | ||
Stream.prototype.isASCII = function (start, end) { | ||
for (var i = start; i < end; ++i) { | ||
var c = this.get(i); | ||
if (c < 32 || c > 176) | ||
return false; | ||
hexDump(start, end, raw) { | ||
let s = ''; | ||
for (let i = start; i < end; ++i) { | ||
s += this.hexByte(this.get(i)); | ||
if (raw !== true) | ||
switch (i & 0xF) { | ||
case 0x7: s += ' '; break; | ||
case 0xF: s += '\n'; break; | ||
default: s += ' '; | ||
} | ||
} | ||
return s; | ||
} | ||
return true; | ||
}; | ||
Stream.prototype.parseStringISO = function (start, end, maxLength) { | ||
var s = ""; | ||
for (var i = start; i < end; ++i) | ||
s += String.fromCharCode(this.get(i)); | ||
return { size: s.length, str: stringCut(s, maxLength) }; | ||
}; | ||
var tableT61 = [ | ||
['', ''], | ||
['AEIOUaeiou', 'ÀÈÌÒÙàèìòù'], // Grave | ||
['ACEILNORSUYZacegilnorsuyz', 'ÁĆÉÍĹŃÓŔŚÚÝŹáćéģíĺńóŕśúýź'], // Acute | ||
['ACEGHIJOSUWYaceghijosuwy', 'ÂĈÊĜĤÎĴÔŜÛŴŶâĉêĝĥîĵôŝûŵŷ'], // Circumflex | ||
['AINOUainou', 'ÃĨÑÕŨãĩñõũ'], // Tilde | ||
['AEIOUaeiou', 'ĀĒĪŌŪāēīōū'], // Macron | ||
['AGUagu', 'ĂĞŬăğŭ'], // Breve | ||
['CEGIZcegz', 'ĊĖĠİŻċėġż'], // Dot | ||
['AEIOUYaeiouy', 'ÄËÏÖÜŸäëïöüÿ'], // Umlaut or diæresis | ||
['', ''], | ||
['AUau', 'ÅŮåů'], // Ring | ||
['CGKLNRSTcklnrst', 'ÇĢĶĻŅŖŞŢçķļņŗşţ'], // Cedilla | ||
['', ''], | ||
['OUou', 'ŐŰőű'], // Double Acute | ||
['AEIUaeiu', 'ĄĘĮŲąęįų'], // Ogonek | ||
['CDELNRSTZcdelnrstz', 'ČĎĚĽŇŘŠŤŽčďěľňřšťž'] // Caron | ||
]; | ||
Stream.prototype.parseStringT61 = function (start, end, maxLength) { | ||
// warning: this code is not very well tested so far | ||
function merge(c, d) { | ||
var t = tableT61[c - 0xC0]; | ||
var i = t[0].indexOf(String.fromCharCode(d)); | ||
return (i < 0) ? '\0' : t[1].charAt(i); | ||
b64Dump(start, end) { | ||
let extra = (end - start) % 3, | ||
s = '', | ||
i, c; | ||
for (i = start; i + 2 < end; i += 3) { | ||
c = this.get(i) << 16 | this.get(i + 1) << 8 | this.get(i + 2); | ||
s += b64Safe.charAt(c >> 18 & 0x3F); | ||
s += b64Safe.charAt(c >> 12 & 0x3F); | ||
s += b64Safe.charAt(c >> 6 & 0x3F); | ||
s += b64Safe.charAt(c & 0x3F); | ||
} | ||
if (extra > 0) { | ||
c = this.get(i) << 16; | ||
if (extra > 1) c |= this.get(i + 1) << 8; | ||
s += b64Safe.charAt(c >> 18 & 0x3F); | ||
s += b64Safe.charAt(c >> 12 & 0x3F); | ||
if (extra == 2) s += b64Safe.charAt(c >> 6 & 0x3F); | ||
} | ||
return s; | ||
} | ||
var s = "", c; | ||
for (var i = start; i < end; ++i) { | ||
c = this.get(i); | ||
if (c >= 0xA4 && c <= 0xBF) | ||
s += '$¥#§¤\0\0«\0\0\0\0°±²³×µ¶·÷\0\0»¼½¾¿'.charAt(c - 0xA4); | ||
else if (c >= 0xE0 && c <= 0xFF) | ||
s += 'ΩÆЪĦ\0IJĿŁØŒºÞŦŊʼnĸæđðħıijŀłøœßþŧŋ\0'.charAt(c - 0xE0); | ||
else if (c >= 0xC0 && c <= 0xCF) | ||
s += merge(c, this.get(++i)); | ||
else // using ISO 8859-1 for characters undefined (or equal) in T.61 | ||
s += String.fromCharCode(c); | ||
isASCII(start, end) { | ||
for (let i = start; i < end; ++i) { | ||
let c = this.get(i); | ||
if (c < 32 || c > 176) | ||
return false; | ||
} | ||
return true; | ||
} | ||
return { size: s.length, str: stringCut(s, maxLength) }; | ||
}; | ||
Stream.prototype.parseStringUTF = function (start, end, maxLength) { | ||
function ex(c) { // must be 10xxxxxx | ||
if ((c < 0x80) || (c >= 0xC0)) | ||
throw new Error('Invalid UTF-8 continuation byte: ' + c); | ||
return (c & 0x3F); | ||
parseStringISO(start, end, maxLength) { | ||
let s = ''; | ||
for (let i = start; i < end; ++i) | ||
s += String.fromCharCode(this.get(i)); | ||
return { size: s.length, str: stringCut(s, maxLength) }; | ||
} | ||
function surrogate(cp) { | ||
if (cp < 0x10000) | ||
throw new Error('UTF-8 overlong encoding, codepoint encoded in 4 bytes: ' + cp); | ||
// we could use String.fromCodePoint(cp) but let's be nice to older browsers and use surrogate pairs | ||
cp -= 0x10000; | ||
return String.fromCharCode((cp >> 10) + 0xD800, (cp & 0x3FF) + 0xDC00); | ||
parseStringT61(start, end, maxLength) { | ||
// warning: this code is not very well tested so far | ||
function merge(c, d) { | ||
let t = tableT61[c - 0xC0]; | ||
let i = t[0].indexOf(String.fromCharCode(d)); | ||
return (i < 0) ? '\0' : t[1].charAt(i); | ||
} | ||
let s = '', c; | ||
for (let i = start; i < end; ++i) { | ||
c = this.get(i); | ||
if (c >= 0xA4 && c <= 0xBF) | ||
s += '$¥#§¤\0\0«\0\0\0\0°±²³×µ¶·÷\0\0»¼½¾¿'.charAt(c - 0xA4); | ||
else if (c >= 0xE0 && c <= 0xFF) | ||
s += 'ΩÆЪĦ\0IJĿŁØŒºÞŦŊʼnĸæđðħıijŀłøœßþŧŋ\0'.charAt(c - 0xE0); | ||
else if (c >= 0xC0 && c <= 0xCF) | ||
s += merge(c, this.get(++i)); | ||
else // using ISO 8859-1 for characters undefined (or equal) in T.61 | ||
s += String.fromCharCode(c); | ||
} | ||
return { size: s.length, str: stringCut(s, maxLength) }; | ||
} | ||
var s = ""; | ||
for (var i = start; i < end; ) { | ||
var c = this.get(i++); | ||
if (c < 0x80) // 0xxxxxxx (7 bit) | ||
s += String.fromCharCode(c); | ||
else if (c < 0xC0) | ||
throw new Error('Invalid UTF-8 starting byte: ' + c); | ||
else if (c < 0xE0) // 110xxxxx 10xxxxxx (11 bit) | ||
s += String.fromCharCode(((c & 0x1F) << 6) | ex(this.get(i++))); | ||
else if (c < 0xF0) // 1110xxxx 10xxxxxx 10xxxxxx (16 bit) | ||
s += String.fromCharCode(((c & 0x0F) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++))); | ||
else if (c < 0xF8) // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (21 bit) | ||
s += surrogate(((c & 0x07) << 18) | (ex(this.get(i++)) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++))); | ||
else | ||
throw new Error('Invalid UTF-8 starting byte (since 2003 it is restricted to 4 bytes): ' + c); | ||
parseStringUTF(start, end, maxLength) { | ||
function ex(c) { // must be 10xxxxxx | ||
if ((c < 0x80) || (c >= 0xC0)) | ||
throw new Error('Invalid UTF-8 continuation byte: ' + c); | ||
return (c & 0x3F); | ||
} | ||
function surrogate(cp) { | ||
if (cp < 0x10000) | ||
throw new Error('UTF-8 overlong encoding, codepoint encoded in 4 bytes: ' + cp); | ||
// we could use String.fromCodePoint(cp) but let's be nice to older browsers and use surrogate pairs | ||
cp -= 0x10000; | ||
return String.fromCharCode((cp >> 10) + 0xD800, (cp & 0x3FF) + 0xDC00); | ||
} | ||
let s = ''; | ||
for (let i = start; i < end; ) { | ||
let c = this.get(i++); | ||
if (c < 0x80) // 0xxxxxxx (7 bit) | ||
s += String.fromCharCode(c); | ||
else if (c < 0xC0) | ||
throw new Error('Invalid UTF-8 starting byte: ' + c); | ||
else if (c < 0xE0) // 110xxxxx 10xxxxxx (11 bit) | ||
s += String.fromCharCode(((c & 0x1F) << 6) | ex(this.get(i++))); | ||
else if (c < 0xF0) // 1110xxxx 10xxxxxx 10xxxxxx (16 bit) | ||
s += String.fromCharCode(((c & 0x0F) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++))); | ||
else if (c < 0xF8) // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (21 bit) | ||
s += surrogate(((c & 0x07) << 18) | (ex(this.get(i++)) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++))); | ||
else | ||
throw new Error('Invalid UTF-8 starting byte (since 2003 it is restricted to 4 bytes): ' + c); | ||
} | ||
return { size: s.length, str: stringCut(s, maxLength) }; | ||
} | ||
return { size: s.length, str: stringCut(s, maxLength) }; | ||
}; | ||
Stream.prototype.parseStringBMP = function (start, end, maxLength) { | ||
var s = "", hi, lo; | ||
for (var i = start; i < end; ) { | ||
hi = this.get(i++); | ||
lo = this.get(i++); | ||
s += String.fromCharCode((hi << 8) | lo); | ||
parseStringBMP(start, end, maxLength) { | ||
let s = '', hi, lo; | ||
for (let i = start; i < end; ) { | ||
hi = this.get(i++); | ||
lo = this.get(i++); | ||
s += String.fromCharCode((hi << 8) | lo); | ||
} | ||
return { size: s.length, str: stringCut(s, maxLength) }; | ||
} | ||
return { size: s.length, str: stringCut(s, maxLength) }; | ||
}; | ||
Stream.prototype.parseTime = function (start, end, shortYear) { | ||
var s = this.parseStringISO(start, end).str, | ||
m = (shortYear ? reTimeS : reTimeL).exec(s); | ||
if (!m) | ||
return "Unrecognized time: " + s; | ||
if (shortYear) { | ||
// to avoid querying the timer, use the fixed range [1970, 2069] | ||
// it will conform with ITU X.400 [-10, +40] sliding window until 2030 | ||
m[1] = +m[1]; | ||
m[1] += (m[1] < 70) ? 2000 : 1900; | ||
parseTime(start, end, shortYear) { | ||
let s = this.parseStringISO(start, end).str, | ||
m = (shortYear ? reTimeS : reTimeL).exec(s); | ||
if (!m) | ||
return 'Unrecognized time: ' + s; | ||
if (shortYear) { | ||
// to avoid querying the timer, use the fixed range [1970, 2069] | ||
// it will conform with ITU X.400 [-10, +40] sliding window until 2030 | ||
m[1] = +m[1]; | ||
m[1] += (m[1] < 70) ? 2000 : 1900; | ||
} | ||
s = m[1] + '-' + m[2] + '-' + m[3] + ' ' + m[4]; | ||
if (m[5]) { | ||
s += ':' + m[5]; | ||
if (m[6]) { | ||
s += ':' + m[6]; | ||
if (m[7]) | ||
s += '.' + m[7]; | ||
} | ||
} | ||
if (m[8]) { | ||
s += ' UTC'; | ||
if (m[9]) | ||
s += m[9] + ':' + (m[10] || '00'); | ||
} | ||
return s; | ||
} | ||
s = m[1] + "-" + m[2] + "-" + m[3] + " " + m[4]; | ||
if (m[5]) { | ||
s += ":" + m[5]; | ||
if (m[6]) { | ||
s += ":" + m[6]; | ||
if (m[7]) | ||
s += "." + m[7]; | ||
parseInteger(start, end) { | ||
let v = this.get(start), | ||
neg = (v > 127), | ||
pad = neg ? 255 : 0, | ||
len, | ||
s = ''; | ||
// skip unuseful bits (not allowed in DER) | ||
while (v == pad && ++start < end) | ||
v = this.get(start); | ||
len = end - start; | ||
if (len === 0) | ||
return neg ? '-1' : '0'; | ||
// show bit length of huge integers | ||
if (len > 4) { | ||
s = v; | ||
len <<= 3; | ||
while (((s ^ pad) & 0x80) == 0) { | ||
s <<= 1; | ||
--len; | ||
} | ||
s = '(' + len + ' bit)\n'; | ||
} | ||
// decode the integer | ||
if (neg) v = v - 256; | ||
let n = new Int10(v); | ||
for (let i = start + 1; i < end; ++i) | ||
n.mulAdd(256, this.get(i)); | ||
return s + n.toString(); | ||
} | ||
if (m[8]) { | ||
s += " UTC"; | ||
if (m[9]) | ||
s += m[9] + ":" + (m[10] || "00"); | ||
parseBitString(start, end, maxLength) { | ||
let unusedBits = this.get(start); | ||
if (unusedBits > 7) | ||
throw 'Invalid BitString with unusedBits=' + unusedBits; | ||
let lenBit = ((end - start - 1) << 3) - unusedBits, | ||
s = ''; | ||
for (let i = start + 1; i < end; ++i) { | ||
let b = this.get(i), | ||
skip = (i == end - 1) ? unusedBits : 0; | ||
for (let j = 7; j >= skip; --j) | ||
s += (b >> j) & 1 ? '1' : '0'; | ||
if (s.length > maxLength) | ||
s = stringCut(s, maxLength); | ||
} | ||
return { size: lenBit, str: s }; | ||
} | ||
return s; | ||
}; | ||
Stream.prototype.parseInteger = function (start, end) { | ||
var v = this.get(start), | ||
neg = (v > 127), | ||
pad = neg ? 255 : 0, | ||
len, | ||
parseOctetString(start, end, maxLength) { | ||
let len = end - start, | ||
s; | ||
try { | ||
s = this.parseStringUTF(start, end, maxLength); | ||
checkPrintable(s.str); | ||
return { size: end - start, str: s.str }; | ||
} catch (e) { | ||
// ignore | ||
} | ||
maxLength /= 2; // we work in bytes | ||
if (len > maxLength) | ||
end = start + maxLength; | ||
s = ''; | ||
// skip unuseful bits (not allowed in DER) | ||
while (v == pad && ++start < end) | ||
v = this.get(start); | ||
len = end - start; | ||
if (len === 0) | ||
return neg ? '-1' : '0'; | ||
// show bit length of huge integers | ||
if (len > 4) { | ||
s = v; | ||
len <<= 3; | ||
while (((s ^ pad) & 0x80) == 0) { | ||
s <<= 1; | ||
--len; | ||
} | ||
s = "(" + len + " bit)\n"; | ||
for (let i = start; i < end; ++i) | ||
s += this.hexByte(this.get(i)); | ||
if (len > maxLength) | ||
s += ellipsis; | ||
return { size: len, str: s }; | ||
} | ||
// decode the integer | ||
if (neg) v = v - 256; | ||
var n = new Int10(v); | ||
for (var i = start + 1; i < end; ++i) | ||
n.mulAdd(256, this.get(i)); | ||
return s + n.toString(); | ||
}; | ||
Stream.prototype.parseBitString = function (start, end, maxLength) { | ||
var unusedBits = this.get(start); | ||
if (unusedBits > 7) | ||
throw 'Invalid BitString with unusedBits=' + unusedBits; | ||
var lenBit = ((end - start - 1) << 3) - unusedBits, | ||
s = ""; | ||
for (var i = start + 1; i < end; ++i) { | ||
var b = this.get(i), | ||
skip = (i == end - 1) ? unusedBits : 0; | ||
for (var j = 7; j >= skip; --j) | ||
s += (b >> j) & 1 ? "1" : "0"; | ||
if (s.length > maxLength) | ||
s = stringCut(s, maxLength); | ||
} | ||
return { size: lenBit, str: s }; | ||
}; | ||
function checkPrintable(s) { | ||
var i, v; | ||
for (i = 0; i < s.length; ++i) { | ||
v = s.charCodeAt(i); | ||
if (v < 32 && v != 9 && v != 10 && v != 13) // [\t\r\n] are (kinda) printable | ||
throw new Error('Unprintable character at index ' + i + ' (code ' + s.str.charCodeAt(i) + ")"); | ||
} | ||
} | ||
Stream.prototype.parseOctetString = function (start, end, maxLength) { | ||
var len = end - start, | ||
s; | ||
try { | ||
s = this.parseStringUTF(start, end, maxLength); | ||
checkPrintable(s.str); | ||
return { size: end - start, str: s.str }; | ||
} catch (e) { | ||
// ignore | ||
} | ||
maxLength /= 2; // we work in bytes | ||
if (len > maxLength) | ||
end = start + maxLength; | ||
s = ''; | ||
for (var i = start; i < end; ++i) | ||
s += this.hexByte(this.get(i)); | ||
if (len > maxLength) | ||
s += ellipsis; | ||
return { size: len, str: s }; | ||
}; | ||
Stream.prototype.parseOID = function (start, end, maxLength, isRelative) { | ||
var s = '', | ||
n = new Int10(), | ||
bits = 0; | ||
for (var i = start; i < end; ++i) { | ||
var v = this.get(i); | ||
n.mulAdd(128, v & 0x7F); | ||
bits += 7; | ||
if (!(v & 0x80)) { // finished | ||
if (s === '') { | ||
n = n.simplify(); | ||
if (isRelative) { | ||
s = (n instanceof Int10) ? n.toString() : "" + n; | ||
} else if (n instanceof Int10) { | ||
n.sub(80); | ||
s = "2." + n.toString(); | ||
} else { | ||
var m = n < 80 ? n < 40 ? 0 : 1 : 2; | ||
s = m + "." + (n - m * 40); | ||
} | ||
} else | ||
s += "." + n.toString(); | ||
if (s.length > maxLength) | ||
return stringCut(s, maxLength); | ||
n = new Int10(); | ||
parseOID(start, end, maxLength, isRelative) { | ||
let s = '', | ||
n = new Int10(), | ||
bits = 0; | ||
for (let i = start; i < end; ++i) { | ||
let v = this.get(i); | ||
n.mulAdd(128, v & 0x7F); | ||
bits += 7; | ||
if (!(v & 0x80)) { // finished | ||
if (s === '') { | ||
n = n.simplify(); | ||
if (isRelative) { | ||
s = (n instanceof Int10) ? n.toString() : '' + n; | ||
} else if (n instanceof Int10) { | ||
n.sub(80); | ||
s = '2.' + n.toString(); | ||
} else { | ||
let m = n < 80 ? n < 40 ? 0 : 1 : 2; | ||
s = m + '.' + (n - m * 40); | ||
} | ||
} else | ||
s += '.' + n.toString(); | ||
if (s.length > maxLength) | ||
return stringCut(s, maxLength); | ||
n = new Int10(); | ||
bits = 0; | ||
} | ||
} | ||
} | ||
if (bits > 0) | ||
s += ".incomplete"; | ||
if (typeof oids === 'object' && !isRelative) { | ||
var oid = oids[s]; | ||
if (oid) { | ||
if (oid.d) s += "\n" + oid.d; | ||
if (oid.c) s += "\n" + oid.c; | ||
if (oid.w) s += "\n(warning!)"; | ||
if (bits > 0) | ||
s += '.incomplete'; | ||
if (typeof oids === 'object' && !isRelative) { | ||
let oid = oids[s]; | ||
if (oid) { | ||
if (oid.d) s += '\n' + oid.d; | ||
if (oid.c) s += '\n' + oid.c; | ||
if (oid.w) s += '\n(warning!)'; | ||
} | ||
} | ||
return s; | ||
} | ||
return s; | ||
}; | ||
Stream.prototype.parseRelativeOID = function (start, end, maxLength) { | ||
return this.parseOID(start, end, maxLength, true); | ||
}; | ||
parseRelativeOID(start, end, maxLength) { | ||
return this.parseOID(start, end, maxLength, true); | ||
} | ||
} | ||
function ASN1(stream, header, length, tag, tagLen, sub) { | ||
if (!(tag instanceof ASN1Tag)) throw 'Invalid tag value.'; | ||
this.stream = stream; | ||
this.header = header; | ||
this.length = length; | ||
this.tag = tag; | ||
this.tagLen = tagLen; | ||
this.sub = sub; | ||
} | ||
ASN1.prototype.typeName = function () { | ||
switch (this.tag.tagClass) { | ||
case 0: // universal | ||
switch (this.tag.tagNumber) { | ||
case 0x00: return "EOC"; | ||
case 0x01: return "BOOLEAN"; | ||
case 0x02: return "INTEGER"; | ||
case 0x03: return "BIT_STRING"; | ||
case 0x04: return "OCTET_STRING"; | ||
case 0x05: return "NULL"; | ||
case 0x06: return "OBJECT_IDENTIFIER"; | ||
case 0x07: return "ObjectDescriptor"; | ||
case 0x08: return "EXTERNAL"; | ||
case 0x09: return "REAL"; | ||
case 0x0A: return "ENUMERATED"; | ||
case 0x0B: return "EMBEDDED_PDV"; | ||
case 0x0C: return "UTF8String"; | ||
case 0x0D: return "RELATIVE_OID"; | ||
case 0x10: return "SEQUENCE"; | ||
case 0x11: return "SET"; | ||
case 0x12: return "NumericString"; | ||
case 0x13: return "PrintableString"; // ASCII subset | ||
case 0x14: return "TeletexString"; // aka T61String | ||
case 0x15: return "VideotexString"; | ||
case 0x16: return "IA5String"; // ASCII | ||
case 0x17: return "UTCTime"; | ||
case 0x18: return "GeneralizedTime"; | ||
case 0x19: return "GraphicString"; | ||
case 0x1A: return "VisibleString"; // ASCII subset | ||
case 0x1B: return "GeneralString"; | ||
case 0x1C: return "UniversalString"; | ||
case 0x1E: return "BMPString"; | ||
} | ||
return "Universal_" + this.tag.tagNumber.toString(); | ||
case 1: return "Application_" + this.tag.tagNumber.toString(); | ||
case 2: return "[" + this.tag.tagNumber.toString() + "]"; // Context | ||
case 3: return "Private_" + this.tag.tagNumber.toString(); | ||
} | ||
}; | ||
function recurse(el, parser, maxLength) { | ||
var avoidRecurse = true; | ||
let avoidRecurse = true; | ||
if (el.tag.tagConstructed && el.sub) { | ||
@@ -387,5 +339,5 @@ avoidRecurse = false; | ||
return el.stream[parser](el.posContent(), el.posContent() + Math.abs(el.length), maxLength); | ||
var d = { size: 0, str: '' }; | ||
let d = { size: 0, str: '' }; | ||
el.sub.forEach(function (el) { | ||
var d1 = recurse(el, parser, maxLength - d.str.length); | ||
let d1 = recurse(el, parser, maxLength - d.str.length); | ||
d.size += d1.size; | ||
@@ -396,207 +348,262 @@ d.str += d1.str; | ||
} | ||
/** A string preview of the content (intended for humans). */ | ||
ASN1.prototype.content = function (maxLength) { | ||
if (this.tag === undefined) | ||
class ASN1Tag { | ||
constructor(stream) { | ||
let buf = stream.get(); | ||
this.tagClass = buf >> 6; | ||
this.tagConstructed = ((buf & 0x20) !== 0); | ||
this.tagNumber = buf & 0x1F; | ||
if (this.tagNumber == 0x1F) { // long tag | ||
let n = new Int10(); | ||
do { | ||
buf = stream.get(); | ||
n.mulAdd(128, buf & 0x7F); | ||
} while (buf & 0x80); | ||
this.tagNumber = n.simplify(); | ||
} | ||
} | ||
isUniversal() { | ||
return this.tagClass === 0x00; | ||
} | ||
isEOC() { | ||
return this.tagClass === 0x00 && this.tagNumber === 0x00; | ||
} | ||
} | ||
export class ASN1 { | ||
constructor(stream, header, length, tag, tagLen, sub) { | ||
if (!(tag instanceof ASN1Tag)) throw 'Invalid tag value.'; | ||
this.stream = stream; | ||
this.header = header; | ||
this.length = length; | ||
this.tag = tag; | ||
this.tagLen = tagLen; | ||
this.sub = sub; | ||
} | ||
typeName() { | ||
switch (this.tag.tagClass) { | ||
case 0: // universal | ||
switch (this.tag.tagNumber) { | ||
case 0x00: return 'EOC'; | ||
case 0x01: return 'BOOLEAN'; | ||
case 0x02: return 'INTEGER'; | ||
case 0x03: return 'BIT_STRING'; | ||
case 0x04: return 'OCTET_STRING'; | ||
case 0x05: return 'NULL'; | ||
case 0x06: return 'OBJECT_IDENTIFIER'; | ||
case 0x07: return 'ObjectDescriptor'; | ||
case 0x08: return 'EXTERNAL'; | ||
case 0x09: return 'REAL'; | ||
case 0x0A: return 'ENUMERATED'; | ||
case 0x0B: return 'EMBEDDED_PDV'; | ||
case 0x0C: return 'UTF8String'; | ||
case 0x0D: return 'RELATIVE_OID'; | ||
case 0x10: return 'SEQUENCE'; | ||
case 0x11: return 'SET'; | ||
case 0x12: return 'NumericString'; | ||
case 0x13: return 'PrintableString'; // ASCII subset | ||
case 0x14: return 'TeletexString'; // aka T61String | ||
case 0x15: return 'VideotexString'; | ||
case 0x16: return 'IA5String'; // ASCII | ||
case 0x17: return 'UTCTime'; | ||
case 0x18: return 'GeneralizedTime'; | ||
case 0x19: return 'GraphicString'; | ||
case 0x1A: return 'VisibleString'; // ASCII subset | ||
case 0x1B: return 'GeneralString'; | ||
case 0x1C: return 'UniversalString'; | ||
case 0x1E: return 'BMPString'; | ||
} | ||
return 'Universal_' + this.tag.tagNumber.toString(); | ||
case 1: return 'Application_' + this.tag.tagNumber.toString(); | ||
case 2: return '[' + this.tag.tagNumber.toString() + ']'; // Context | ||
case 3: return 'Private_' + this.tag.tagNumber.toString(); | ||
} | ||
} | ||
/** A string preview of the content (intended for humans). */ | ||
content(maxLength) { | ||
if (this.tag === undefined) | ||
return null; | ||
if (maxLength === undefined) | ||
maxLength = Infinity; | ||
let content = this.posContent(), | ||
len = Math.abs(this.length); | ||
if (!this.tag.isUniversal()) { | ||
if (this.sub !== null) | ||
return '(' + this.sub.length + ' elem)'; | ||
let d1 = this.stream.parseOctetString(content, content + len, maxLength); | ||
return '(' + d1.size + ' byte)\n' + d1.str; | ||
} | ||
switch (this.tag.tagNumber) { | ||
case 0x01: // BOOLEAN | ||
return (this.stream.get(content) === 0) ? 'false' : 'true'; | ||
case 0x02: // INTEGER | ||
return this.stream.parseInteger(content, content + len); | ||
case 0x03: { // BIT_STRING | ||
let d = recurse(this, 'parseBitString', maxLength); | ||
return '(' + d.size + ' bit)\n' + d.str; | ||
} | ||
case 0x04: { // OCTET_STRING | ||
let d = recurse(this, 'parseOctetString', maxLength); | ||
return '(' + d.size + ' byte)\n' + d.str; | ||
} | ||
//case 0x05: // NULL | ||
case 0x06: // OBJECT_IDENTIFIER | ||
return this.stream.parseOID(content, content + len, maxLength); | ||
//case 0x07: // ObjectDescriptor | ||
//case 0x08: // EXTERNAL | ||
//case 0x09: // REAL | ||
case 0x0A: // ENUMERATED | ||
return this.stream.parseInteger(content, content + len); | ||
//case 0x0B: // EMBEDDED_PDV | ||
case 0x0D: // RELATIVE-OID | ||
return this.stream.parseRelativeOID(content, content + len, maxLength); | ||
case 0x10: // SEQUENCE | ||
case 0x11: // SET | ||
if (this.sub !== null) | ||
return '(' + this.sub.length + ' elem)'; | ||
else | ||
return '(no elem)'; | ||
case 0x0C: // UTF8String | ||
return recurse(this, 'parseStringUTF', maxLength).str; | ||
case 0x14: // TeletexString | ||
return recurse(this, 'parseStringT61', maxLength).str; | ||
case 0x12: // NumericString | ||
case 0x13: // PrintableString | ||
case 0x15: // VideotexString | ||
case 0x16: // IA5String | ||
case 0x1A: // VisibleString | ||
case 0x1B: // GeneralString | ||
//case 0x19: // GraphicString | ||
//case 0x1C: // UniversalString | ||
return recurse(this, 'parseStringISO', maxLength).str; | ||
case 0x1E: // BMPString | ||
return recurse(this, 'parseStringBMP', maxLength).str; | ||
case 0x17: // UTCTime | ||
case 0x18: // GeneralizedTime | ||
return this.stream.parseTime(content, content + len, (this.tag.tagNumber == 0x17)); | ||
} | ||
return null; | ||
if (maxLength === undefined) | ||
maxLength = Infinity; | ||
var content = this.posContent(), | ||
len = Math.abs(this.length); | ||
if (!this.tag.isUniversal()) { | ||
if (this.sub !== null) | ||
return "(" + this.sub.length + " elem)"; | ||
var d1 = this.stream.parseOctetString(content, content + len, maxLength); | ||
return "(" + d1.size + " byte)\n" + d1.str; | ||
} | ||
switch (this.tag.tagNumber) { | ||
case 0x01: // BOOLEAN | ||
return (this.stream.get(content) === 0) ? "false" : "true"; | ||
case 0x02: // INTEGER | ||
return this.stream.parseInteger(content, content + len); | ||
case 0x03: // BIT_STRING | ||
var d = recurse(this, 'parseBitString', maxLength); | ||
return "(" + d.size + " bit)\n" + d.str; | ||
case 0x04: // OCTET_STRING | ||
d = recurse(this, 'parseOctetString', maxLength); | ||
return "(" + d.size + " byte)\n" + d.str; | ||
//case 0x05: // NULL | ||
case 0x06: // OBJECT_IDENTIFIER | ||
return this.stream.parseOID(content, content + len, maxLength); | ||
//case 0x07: // ObjectDescriptor | ||
//case 0x08: // EXTERNAL | ||
//case 0x09: // REAL | ||
case 0x0A: // ENUMERATED | ||
return this.stream.parseInteger(content, content + len); | ||
//case 0x0B: // EMBEDDED_PDV | ||
case 0x0D: // RELATIVE-OID | ||
return this.stream.parseRelativeOID(content, content + len, maxLength); | ||
case 0x10: // SEQUENCE | ||
case 0x11: // SET | ||
if (this.sub !== null) | ||
return "(" + this.sub.length + " elem)"; | ||
else | ||
return "(no elem)"; | ||
case 0x0C: // UTF8String | ||
return recurse(this, 'parseStringUTF', maxLength).str; | ||
case 0x14: // TeletexString | ||
return recurse(this, 'parseStringT61', maxLength).str; | ||
case 0x12: // NumericString | ||
case 0x13: // PrintableString | ||
case 0x15: // VideotexString | ||
case 0x16: // IA5String | ||
case 0x1A: // VisibleString | ||
case 0x1B: // GeneralString | ||
//case 0x19: // GraphicString | ||
//case 0x1C: // UniversalString | ||
return recurse(this, 'parseStringISO', maxLength).str; | ||
case 0x1E: // BMPString | ||
return recurse(this, 'parseStringBMP', maxLength).str; | ||
case 0x17: // UTCTime | ||
case 0x18: // GeneralizedTime | ||
return this.stream.parseTime(content, content + len, (this.tag.tagNumber == 0x17)); | ||
toString() { | ||
return this.typeName() + '@' + this.stream.pos + '[header:' + this.header + ',length:' + this.length + ',sub:' + ((this.sub === null) ? 'null' : this.sub.length) + ']'; | ||
} | ||
return null; | ||
}; | ||
ASN1.prototype.toString = function () { | ||
return this.typeName() + "@" + this.stream.pos + "[header:" + this.header + ",length:" + this.length + ",sub:" + ((this.sub === null) ? 'null' : this.sub.length) + "]"; | ||
}; | ||
ASN1.prototype.toPrettyString = function (indent) { | ||
if (indent === undefined) indent = ''; | ||
var s = indent + this.typeName() + " @" + this.stream.pos; | ||
if (this.length >= 0) | ||
s += "+"; | ||
s += this.length; | ||
if (this.tag.tagConstructed) | ||
s += " (constructed)"; | ||
else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null)) | ||
s += " (encapsulates)"; | ||
var content = this.content(); | ||
if (content) | ||
s += ": " + content.replace(/\n/g, '|'); | ||
s += "\n"; | ||
if (this.sub !== null) { | ||
indent += ' '; | ||
for (var i = 0, max = this.sub.length; i < max; ++i) | ||
s += this.sub[i].toPrettyString(indent); | ||
toPrettyString(indent) { | ||
if (indent === undefined) indent = ''; | ||
let s = indent + this.typeName() + ' @' + this.stream.pos; | ||
if (this.length >= 0) | ||
s += '+'; | ||
s += this.length; | ||
if (this.tag.tagConstructed) | ||
s += ' (constructed)'; | ||
else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null)) | ||
s += ' (encapsulates)'; | ||
let content = this.content(); | ||
if (content) | ||
s += ': ' + content.replace(/\n/g, '|'); | ||
s += '\n'; | ||
if (this.sub !== null) { | ||
indent += ' '; | ||
for (let i = 0, max = this.sub.length; i < max; ++i) | ||
s += this.sub[i].toPrettyString(indent); | ||
} | ||
return s; | ||
} | ||
return s; | ||
}; | ||
ASN1.prototype.posStart = function () { | ||
return this.stream.pos; | ||
}; | ||
ASN1.prototype.posContent = function () { | ||
return this.stream.pos + this.header; | ||
}; | ||
ASN1.prototype.posEnd = function () { | ||
return this.stream.pos + this.header + Math.abs(this.length); | ||
}; | ||
/** Position of the length. */ | ||
ASN1.prototype.posLen = function() { | ||
return this.stream.pos + this.tagLen; | ||
}; | ||
ASN1.prototype.toHexString = function () { | ||
return this.stream.hexDump(this.posStart(), this.posEnd(), true); | ||
}; | ||
ASN1.prototype.toB64String = function () { | ||
return this.stream.b64Dump(this.posStart(), this.posEnd()); | ||
}; | ||
ASN1.decodeLength = function (stream) { | ||
var buf = stream.get(), | ||
len = buf & 0x7F; | ||
if (len == buf) // first bit was 0, short form | ||
return len; | ||
if (len === 0) // long form with length 0 is a special case | ||
return null; // undefined length | ||
if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways | ||
throw "Length over 48 bits not supported at position " + (stream.pos - 1); | ||
buf = 0; | ||
for (var i = 0; i < len; ++i) | ||
buf = (buf * 256) + stream.get(); | ||
return buf; | ||
}; | ||
function ASN1Tag(stream) { | ||
var buf = stream.get(); | ||
this.tagClass = buf >> 6; | ||
this.tagConstructed = ((buf & 0x20) !== 0); | ||
this.tagNumber = buf & 0x1F; | ||
if (this.tagNumber == 0x1F) { // long tag | ||
var n = new Int10(); | ||
do { | ||
buf = stream.get(); | ||
n.mulAdd(128, buf & 0x7F); | ||
} while (buf & 0x80); | ||
this.tagNumber = n.simplify(); | ||
posStart() { | ||
return this.stream.pos; | ||
} | ||
} | ||
ASN1Tag.prototype.isUniversal = function () { | ||
return this.tagClass === 0x00; | ||
}; | ||
ASN1Tag.prototype.isEOC = function () { | ||
return this.tagClass === 0x00 && this.tagNumber === 0x00; | ||
}; | ||
ASN1.decode = function (stream, offset) { | ||
if (!(stream instanceof Stream)) | ||
stream = new Stream(stream, offset || 0); | ||
var streamStart = new Stream(stream), | ||
tag = new ASN1Tag(stream), | ||
tagLen = stream.pos - streamStart.pos, | ||
len = ASN1.decodeLength(stream), | ||
start = stream.pos, | ||
header = start - streamStart.pos, | ||
sub = null, | ||
getSub = function () { | ||
sub = []; | ||
if (len !== null) { | ||
// definite length | ||
var end = start + len; | ||
if (end > stream.enc.length) | ||
throw 'Container at offset ' + start + ' has a length of ' + len + ', which is past the end of the stream'; | ||
while (stream.pos < end) | ||
sub[sub.length] = ASN1.decode(stream); | ||
if (stream.pos != end) | ||
throw 'Content size is not correct for container at offset ' + start; | ||
} else { | ||
// undefined length | ||
try { | ||
for (;;) { | ||
var s = ASN1.decode(stream); | ||
if (s.tag.isEOC()) | ||
break; | ||
sub[sub.length] = s; | ||
posContent() { | ||
return this.stream.pos + this.header; | ||
} | ||
posEnd() { | ||
return this.stream.pos + this.header + Math.abs(this.length); | ||
} | ||
/** Position of the length. */ | ||
posLen() { | ||
return this.stream.pos + this.tagLen; | ||
} | ||
toHexString() { | ||
return this.stream.hexDump(this.posStart(), this.posEnd(), true); | ||
} | ||
toB64String() { | ||
return this.stream.b64Dump(this.posStart(), this.posEnd()); | ||
} | ||
static decodeLength(stream) { | ||
let buf = stream.get(), | ||
len = buf & 0x7F; | ||
if (len == buf) // first bit was 0, short form | ||
return len; | ||
if (len === 0) // long form with length 0 is a special case | ||
return null; // undefined length | ||
if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways | ||
throw 'Length over 48 bits not supported at position ' + (stream.pos - 1); | ||
buf = 0; | ||
for (let i = 0; i < len; ++i) | ||
buf = (buf * 256) + stream.get(); | ||
return buf; | ||
} | ||
static decode(stream, offset, type = ASN1) { | ||
if (!(type == ASN1 || type.prototype instanceof ASN1)) | ||
throw 'Must pass a class that extends ASN1'; | ||
if (!(stream instanceof Stream)) | ||
stream = new Stream(stream, offset || 0); | ||
let streamStart = new Stream(stream), | ||
tag = new ASN1Tag(stream), | ||
tagLen = stream.pos - streamStart.pos, | ||
len = ASN1.decodeLength(stream), | ||
start = stream.pos, | ||
header = start - streamStart.pos, | ||
sub = null, | ||
getSub = function () { | ||
sub = []; | ||
if (len !== null) { | ||
// definite length | ||
let end = start + len; | ||
if (end > stream.enc.length) | ||
throw 'Container at offset ' + start + ' has a length of ' + len + ', which is past the end of the stream'; | ||
while (stream.pos < end) | ||
sub[sub.length] = type.decode(stream); | ||
if (stream.pos != end) | ||
throw 'Content size is not correct for container at offset ' + start; | ||
} else { | ||
// undefined length | ||
try { | ||
for (;;) { | ||
let s = type.decode(stream); | ||
if (s.tag.isEOC()) | ||
break; | ||
sub[sub.length] = s; | ||
} | ||
len = start - stream.pos; // undefined lengths are represented as negative values | ||
} catch (e) { | ||
throw 'Exception while decoding undefined length content at offset ' + start + ': ' + e; | ||
} | ||
len = start - stream.pos; // undefined lengths are represented as negative values | ||
} catch (e) { | ||
throw 'Exception while decoding undefined length content at offset ' + start + ': ' + e; | ||
} | ||
}; | ||
if (tag.tagConstructed) { | ||
// must have valid content | ||
getSub(); | ||
} else if (tag.isUniversal() && ((tag.tagNumber == 0x03) || (tag.tagNumber == 0x04))) { | ||
// sometimes BitString and OctetString are used to encapsulate ASN.1 | ||
try { | ||
if (tag.tagNumber == 0x03) | ||
if (stream.get() != 0) | ||
throw 'BIT STRINGs with unused bits cannot encapsulate.'; | ||
getSub(); | ||
for (let i = 0; i < sub.length; ++i) | ||
if (sub[i].tag.isEOC()) | ||
throw 'EOC is not supposed to be actual content.'; | ||
} catch (e) { | ||
// but silently ignore when they don't | ||
sub = null; | ||
//DEBUG console.log('Could not decode structure at ' + start + ':', e); | ||
} | ||
}; | ||
if (tag.tagConstructed) { | ||
// must have valid content | ||
getSub(); | ||
} else if (tag.isUniversal() && ((tag.tagNumber == 0x03) || (tag.tagNumber == 0x04))) { | ||
// sometimes BitString and OctetString are used to encapsulate ASN.1 | ||
try { | ||
if (tag.tagNumber == 0x03) | ||
if (stream.get() != 0) | ||
throw "BIT STRINGs with unused bits cannot encapsulate."; | ||
getSub(); | ||
for (var i = 0; i < sub.length; ++i) | ||
if (sub[i].tag.isEOC()) | ||
throw 'EOC is not supposed to be actual content.'; | ||
} catch (e) { | ||
// but silently ignore when they don't | ||
sub = null; | ||
//DEBUG console.log('Could not decode structure at ' + start + ':', e); | ||
} | ||
if (sub === null) { | ||
if (len === null) | ||
throw "We can't skip over an invalid tag with undefined length at offset " + start; | ||
stream.pos = start + Math.abs(len); | ||
} | ||
return new type(streamStart, header, len, tag, tagLen, sub); | ||
} | ||
if (sub === null) { | ||
if (len === null) | ||
throw "We can't skip over an invalid tag with undefined length at offset " + start; | ||
stream.pos = start + Math.abs(len); | ||
} | ||
return new ASN1(streamStart, header, len, tag, tagLen, sub); | ||
}; | ||
return ASN1; | ||
}); | ||
} |
158
base64.js
// Base64 JavaScript decoder | ||
// Copyright (c) 2008-2022 Lapo Luchini <lapo@lapo.it> | ||
// Copyright (c) 2008-2024 Lapo Luchini <lapo@lapo.it> | ||
@@ -16,93 +16,89 @@ // Permission to use, copy, modify, and/or distribute this software for any | ||
(typeof define != 'undefined' ? define : function (factory) { 'use strict'; | ||
if (typeof module == 'object') module.exports = factory(); | ||
else window.base64 = factory(); | ||
})(function () { | ||
"use strict"; | ||
var Base64 = {}, | ||
decoder, // populated on first usage | ||
const | ||
haveU8 = (typeof Uint8Array == 'function'); | ||
Base64.decode = function (a) { | ||
var isString = (typeof a == 'string'); | ||
var i; | ||
if (decoder === undefined) { | ||
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", | ||
ignore = "= \f\n\r\t\u00A0\u2028\u2029"; | ||
decoder = []; | ||
for (i = 0; i < 64; ++i) | ||
decoder[b64.charCodeAt(i)] = i; | ||
for (i = 0; i < ignore.length; ++i) | ||
decoder[ignore.charCodeAt(i)] = -1; | ||
// RFC 3548 URL & file safe encoding | ||
decoder['-'.charCodeAt(0)] = decoder['+'.charCodeAt(0)]; | ||
decoder['_'.charCodeAt(0)] = decoder['/'.charCodeAt(0)]; | ||
} | ||
var out = haveU8 ? new Uint8Array(a.length * 3 >> 2) : []; | ||
var bits = 0, char_count = 0, len = 0; | ||
for (i = 0; i < a.length; ++i) { | ||
var c = isString ? a.charCodeAt(i) : a[i]; | ||
if (c == 61) // '='.charCodeAt(0) | ||
let decoder; // populated on first usage | ||
export class Base64 { | ||
static decode(a) { | ||
let isString = (typeof a == 'string'); | ||
let i; | ||
if (decoder === undefined) { | ||
let b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', | ||
ignore = '= \f\n\r\t\u00A0\u2028\u2029'; | ||
decoder = []; | ||
for (i = 0; i < 64; ++i) | ||
decoder[b64.charCodeAt(i)] = i; | ||
for (i = 0; i < ignore.length; ++i) | ||
decoder[ignore.charCodeAt(i)] = -1; | ||
// RFC 3548 URL & file safe encoding | ||
decoder['-'.charCodeAt(0)] = decoder['+'.charCodeAt(0)]; | ||
decoder['_'.charCodeAt(0)] = decoder['/'.charCodeAt(0)]; | ||
} | ||
let out = haveU8 ? new Uint8Array(a.length * 3 >> 2) : []; | ||
let bits = 0, char_count = 0, len = 0; | ||
for (i = 0; i < a.length; ++i) { | ||
let c = isString ? a.charCodeAt(i) : a[i]; | ||
if (c == 61) // '='.charCodeAt(0) | ||
break; | ||
c = decoder[c]; | ||
if (c == -1) | ||
continue; | ||
if (c === undefined) | ||
throw 'Illegal character at offset ' + i; | ||
bits |= c; | ||
if (++char_count >= 4) { | ||
out[len++] = (bits >> 16); | ||
out[len++] = (bits >> 8) & 0xFF; | ||
out[len++] = bits & 0xFF; | ||
bits = 0; | ||
char_count = 0; | ||
} else { | ||
bits <<= 6; | ||
} | ||
} | ||
switch (char_count) { | ||
case 1: | ||
throw 'Base64 encoding incomplete: at least 2 bits missing'; | ||
case 2: | ||
out[len++] = (bits >> 10); | ||
break; | ||
c = decoder[c]; | ||
if (c == -1) | ||
continue; | ||
if (c === undefined) | ||
throw 'Illegal character at offset ' + i; | ||
bits |= c; | ||
if (++char_count >= 4) { | ||
case 3: | ||
out[len++] = (bits >> 16); | ||
out[len++] = (bits >> 8) & 0xFF; | ||
out[len++] = bits & 0xFF; | ||
bits = 0; | ||
char_count = 0; | ||
} else { | ||
bits <<= 6; | ||
break; | ||
} | ||
if (haveU8 && out.length > len) // in case it was originally longer because of ignored characters | ||
out = out.subarray(0, len); | ||
return out; | ||
} | ||
switch (char_count) { | ||
case 1: | ||
throw "Base64 encoding incomplete: at least 2 bits missing"; | ||
case 2: | ||
out[len++] = (bits >> 10); | ||
break; | ||
case 3: | ||
out[len++] = (bits >> 16); | ||
out[len++] = (bits >> 8) & 0xFF; | ||
break; | ||
static pretty(str) { | ||
// fix padding | ||
if (str.length % 4 > 0) | ||
str = (str + '===').slice(0, str.length + str.length % 4); | ||
// convert RFC 3548 to standard Base64 | ||
str = str.replace(/-/g, '+').replace(/_/g, '/'); | ||
// 80 column width | ||
return str.replace(/(.{80})/g, '$1\n'); | ||
} | ||
if (haveU8 && out.length > len) // in case it was originally longer because of ignored characters | ||
out = out.subarray(0, len); | ||
return out; | ||
}; | ||
Base64.pretty = function (str) { | ||
// fix padding | ||
if (str.length % 4 > 0) | ||
str = (str + '===').slice(0, str.length + str.length % 4); | ||
// convert RFC 3548 to standard Base64 | ||
str = str.replace(/-/g, '+').replace(/_/g, '/'); | ||
// 80 column width | ||
return str.replace(/(.{80})/g, '$1\n'); | ||
}; | ||
Base64.re = /-----BEGIN [^-]+-----([A-Za-z0-9+/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+/=\s]+)====|^([A-Za-z0-9+/=\s]+)$/; | ||
Base64.unarmor = function (a) { | ||
var m = Base64.re.exec(a); | ||
if (m) { | ||
if (m[1]) | ||
a = m[1]; | ||
else if (m[2]) | ||
a = m[2]; | ||
else if (m[3]) | ||
a = m[3]; | ||
else | ||
throw "RegExp out of sync"; | ||
static unarmor(a) { | ||
let m = Base64.re.exec(a); | ||
if (m) { | ||
if (m[1]) | ||
a = m[1]; | ||
else if (m[2]) | ||
a = m[2]; | ||
else if (m[3]) | ||
a = m[3]; | ||
else | ||
throw 'RegExp out of sync'; | ||
} | ||
return Base64.decode(a); | ||
} | ||
return Base64.decode(a); | ||
}; | ||
return Base64; | ||
} | ||
}); | ||
Base64.re = /-----BEGIN [^-]+-----([A-Za-z0-9+/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+/=\s]+)====|^([A-Za-z0-9+/=\s]+)$/; |
105
hex.js
// Hex JavaScript decoder | ||
// Copyright (c) 2008-2022 Lapo Luchini <lapo@lapo.it> | ||
// Copyright (c) 2008-2024 Lapo Luchini <lapo@lapo.it> | ||
@@ -16,60 +16,55 @@ // Permission to use, copy, modify, and/or distribute this software for any | ||
(typeof define != 'undefined' ? define : function (factory) { 'use strict'; | ||
if (typeof module == 'object') module.exports = factory(); | ||
else window.hex = factory(); | ||
})(function () { | ||
"use strict"; | ||
var Hex = {}, | ||
decoder, // populated on first usage | ||
const | ||
haveU8 = (typeof Uint8Array == 'function'); | ||
/** | ||
* Decodes an hexadecimal value. | ||
* @param {string|Array|Uint8Array} a - a string representing hexadecimal data, or an array representation of its charcodes | ||
*/ | ||
Hex.decode = function(a) { | ||
var isString = (typeof a == 'string'); | ||
var i; | ||
if (decoder === undefined) { | ||
var hex = "0123456789ABCDEF", | ||
ignore = " \f\n\r\t\u00A0\u2028\u2029"; | ||
decoder = []; | ||
for (i = 0; i < 16; ++i) | ||
decoder[hex.charCodeAt(i)] = i; | ||
hex = hex.toLowerCase(); | ||
for (i = 10; i < 16; ++i) | ||
decoder[hex.charCodeAt(i)] = i; | ||
for (i = 0; i < ignore.length; ++i) | ||
decoder[ignore.charCodeAt(i)] = -1; | ||
} | ||
var out = haveU8 ? new Uint8Array(a.length >> 1) : [], | ||
bits = 0, | ||
char_count = 0, | ||
len = 0; | ||
for (i = 0; i < a.length; ++i) { | ||
var c = isString ? a.charCodeAt(i) : a[i]; | ||
c = decoder[c]; | ||
if (c == -1) | ||
continue; | ||
if (c === undefined) | ||
throw 'Illegal character at offset ' + i; | ||
bits |= c; | ||
if (++char_count >= 2) { | ||
out[len++] = bits; | ||
bits = 0; | ||
char_count = 0; | ||
} else { | ||
bits <<= 4; | ||
let decoder; // populated on first usage | ||
export class Hex { | ||
/** | ||
* Decodes an hexadecimal value. | ||
* @param {string|Array|Uint8Array} a - a string representing hexadecimal data, or an array representation of its charcodes | ||
*/ | ||
static decode(a) { | ||
let isString = (typeof a == 'string'); | ||
let i; | ||
if (decoder === undefined) { | ||
let hex = '0123456789ABCDEF', | ||
ignore = ' \f\n\r\t\u00A0\u2028\u2029'; | ||
decoder = []; | ||
for (i = 0; i < 16; ++i) | ||
decoder[hex.charCodeAt(i)] = i; | ||
hex = hex.toLowerCase(); | ||
for (i = 10; i < 16; ++i) | ||
decoder[hex.charCodeAt(i)] = i; | ||
for (i = 0; i < ignore.length; ++i) | ||
decoder[ignore.charCodeAt(i)] = -1; | ||
} | ||
let out = haveU8 ? new Uint8Array(a.length >> 1) : [], | ||
bits = 0, | ||
char_count = 0, | ||
len = 0; | ||
for (i = 0; i < a.length; ++i) { | ||
let c = isString ? a.charCodeAt(i) : a[i]; | ||
c = decoder[c]; | ||
if (c == -1) | ||
continue; | ||
if (c === undefined) | ||
throw 'Illegal character at offset ' + i; | ||
bits |= c; | ||
if (++char_count >= 2) { | ||
out[len++] = bits; | ||
bits = 0; | ||
char_count = 0; | ||
} else { | ||
bits <<= 4; | ||
} | ||
} | ||
if (char_count) | ||
throw 'Hex encoding incomplete: 4 bits missing'; | ||
if (haveU8 && out.length > len) // in case it was originally longer because of ignored characters | ||
out = out.subarray(0, len); | ||
return out; | ||
} | ||
if (char_count) | ||
throw "Hex encoding incomplete: 4 bits missing"; | ||
if (haveU8 && out.length > len) // in case it was originally longer because of ignored characters | ||
out = out.subarray(0, len); | ||
return out; | ||
}; | ||
return Hex; | ||
}); | ||
} |
171
int10.js
// Big integer base-10 printing library | ||
// Copyright (c) 2008-2022 Lapo Luchini <lapo@lapo.it> | ||
// Copyright (c) 2008-2024 Lapo Luchini <lapo@lapo.it> | ||
@@ -16,99 +16,92 @@ // Permission to use, copy, modify, and/or distribute this software for any | ||
(typeof define != 'undefined' ? define : function (factory) { 'use strict'; | ||
if (typeof module == 'object') module.exports = factory(); | ||
else window.int10 = factory(); | ||
})(function () { | ||
"use strict"; | ||
let max = 10000000000000; // biggest 10^n integer that can still fit 2^53 when multiplied by 256 | ||
var max = 10000000000000; // biggest 10^n integer that can still fit 2^53 when multiplied by 256 | ||
export class Int10 { | ||
/** | ||
* Arbitrary length base-10 value. | ||
* @param {number} value - Optional initial value (will be 0 otherwise). | ||
*/ | ||
constructor(value) { | ||
this.buf = [+value || 0]; | ||
} | ||
/** | ||
* Arbitrary length base-10 value. | ||
* @param {number} value - Optional initial value (will be 0 otherwise). | ||
*/ | ||
function Int10(value) { | ||
this.buf = [+value || 0]; | ||
} | ||
/** | ||
* Multiply value by m and add c. | ||
* @param {number} m - multiplier, must be < =256 | ||
* @param {number} c - value to add | ||
*/ | ||
mulAdd(m, c) { | ||
// assert(m <= 256) | ||
let b = this.buf, | ||
l = b.length, | ||
i, t; | ||
for (i = 0; i < l; ++i) { | ||
t = b[i] * m + c; | ||
if (t < max) | ||
c = 0; | ||
else { | ||
c = 0|(t / max); | ||
t -= c * max; | ||
} | ||
b[i] = t; | ||
} | ||
if (c > 0) | ||
b[i] = c; | ||
} | ||
/** | ||
* Multiply value by m and add c. | ||
* @param {number} m - multiplier, must be < =256 | ||
* @param {number} c - value to add | ||
*/ | ||
Int10.prototype.mulAdd = function (m, c) { | ||
// assert(m <= 256) | ||
var b = this.buf, | ||
l = b.length, | ||
i, t; | ||
for (i = 0; i < l; ++i) { | ||
t = b[i] * m + c; | ||
if (t < max) | ||
c = 0; | ||
else { | ||
c = 0|(t / max); | ||
t -= c * max; | ||
/** | ||
* Subtract value. | ||
* @param {number} c - value to subtract | ||
*/ | ||
sub(c) { | ||
let b = this.buf, | ||
l = b.length, | ||
i, t; | ||
for (i = 0; i < l; ++i) { | ||
t = b[i] - c; | ||
if (t < 0) { | ||
t += max; | ||
c = 1; | ||
} else | ||
c = 0; | ||
b[i] = t; | ||
} | ||
b[i] = t; | ||
while (b[b.length - 1] === 0) | ||
b.pop(); | ||
} | ||
if (c > 0) | ||
b[i] = c; | ||
}; | ||
/** | ||
* Subtract value. | ||
* @param {number} c - value to subtract | ||
*/ | ||
Int10.prototype.sub = function (c) { | ||
var b = this.buf, | ||
l = b.length, | ||
i, t; | ||
for (i = 0; i < l; ++i) { | ||
t = b[i] - c; | ||
if (t < 0) { | ||
t += max; | ||
c = 1; | ||
} else | ||
c = 0; | ||
b[i] = t; | ||
/** | ||
* Convert to decimal string representation. | ||
* @param {*} base - optional value, only value accepted is 10 | ||
*/ | ||
toString(base) { | ||
if ((base || 10) != 10) | ||
throw 'only base 10 is supported'; | ||
let b = this.buf, | ||
s = b[b.length - 1].toString(); | ||
for (let i = b.length - 2; i >= 0; --i) | ||
s += (max + b[i]).toString().substring(1); | ||
return s; | ||
} | ||
while (b[b.length - 1] === 0) | ||
b.pop(); | ||
}; | ||
/** | ||
* Convert to decimal string representation. | ||
* @param {*} base - optional value, only value accepted is 10 | ||
*/ | ||
Int10.prototype.toString = function (base) { | ||
if ((base || 10) != 10) | ||
throw 'only base 10 is supported'; | ||
var b = this.buf, | ||
s = b[b.length - 1].toString(); | ||
for (var i = b.length - 2; i >= 0; --i) | ||
s += (max + b[i]).toString().substring(1); | ||
return s; | ||
}; | ||
/** | ||
* Convert to Number value representation. | ||
* Will probably overflow 2^53 and thus become approximate. | ||
*/ | ||
valueOf() { | ||
let b = this.buf, | ||
v = 0; | ||
for (let i = b.length - 1; i >= 0; --i) | ||
v = v * max + b[i]; | ||
return v; | ||
} | ||
/** | ||
* Convert to Number value representation. | ||
* Will probably overflow 2^53 and thus become approximate. | ||
*/ | ||
Int10.prototype.valueOf = function () { | ||
var b = this.buf, | ||
v = 0; | ||
for (var i = b.length - 1; i >= 0; --i) | ||
v = v * max + b[i]; | ||
return v; | ||
}; | ||
/** | ||
* Return value as a simple Number (if it is <= 10000000000000), or return this. | ||
*/ | ||
simplify() { | ||
let b = this.buf; | ||
return (b.length == 1) ? b[0] : this; | ||
} | ||
/** | ||
* Return value as a simple Number (if it is <= 10000000000000), or return this. | ||
*/ | ||
Int10.prototype.simplify = function () { | ||
var b = this.buf; | ||
return (b.length == 1) ? b[0] : this; | ||
}; | ||
return Int10; | ||
}); | ||
} |
{ | ||
"name": "@lapo/asn1js", | ||
"version": "1.2.4", | ||
"version": "1.3.0", | ||
"description": "Generic ASN.1 parser/decoder that can decode any valid ASN.1 DER or BER structures.", | ||
"type": "module", | ||
"main": "asn1.js", | ||
@@ -16,12 +17,24 @@ "repository": { | ||
"files": [ "asn1.js", "base64.js", "hex.js", "int10.js", "oids.js" ], | ||
"scripts" : { | ||
"lint" : "npx eslint asn1.js base64.js hex.js int10.js oids.js parseRFC.js dumpASN1.js", | ||
"test" : "node test" | ||
"scripts": { | ||
"lint": "npx eslint asn1.js base64.js hex.js int10.js oids.js tags.js index.js parseRFC.js dumpASN1.js", | ||
"lint-action": "npx @action-validator/cli .github/workflows/node.js.yml", | ||
"serve": "echo 'Connect to http://localhost:3000/' ; npx statik --port 3000 .", | ||
"test": "node test" | ||
}, | ||
"engines": { | ||
"node": ">=12.20.0" | ||
}, | ||
"devDependencies": { | ||
"eslint": "^8.34.0" | ||
}, | ||
"eslintConfig": { | ||
"env": { | ||
"amd": true, | ||
"es6": true, | ||
"browser": true, | ||
"node": true | ||
}, | ||
"parserOptions": { | ||
"ecmaVersion": 2015, | ||
"sourceType": "module" | ||
}, | ||
"extends": [ "eslint:recommended" ], | ||
@@ -33,5 +46,7 @@ "globals": { | ||
"strict": [ "error", "function" ], | ||
"indent": [ "error", 4, { "ignoredNodes": [ "Program > ExpressionStatement > CallExpression > FunctionExpression > BlockStatement > ExpressionStatement[directive='use strict']:first-child" ] } ], | ||
"indent": [ "error", 4 ], | ||
"linebreak-style": [ "error", "unix" ], | ||
"semi": [ "warn", "always" ], | ||
"quotes": [ "error", "single", { "avoidEscape": true } ], | ||
"no-var": [ "warn" ], | ||
"comma-dangle": [ "error", "never" ] | ||
@@ -43,3 +58,3 @@ }, | ||
"parserOptions": { | ||
"ecmaVersion": 2020 | ||
"ecmaVersion": 2021 | ||
}, | ||
@@ -53,4 +68,16 @@ "rules": { | ||
"rules": { | ||
"indent": "off" | ||
"indent": "off", | ||
"quotes": [ "warn", "double" ] | ||
} | ||
}, { | ||
"files": [ "tags.js" ], | ||
"rules": { | ||
"comma-dangle": [ "error", "always-multiline" ], | ||
"quotes": [ "warn", "double" ] | ||
} | ||
}, { | ||
"files": [ "defs.js" ], | ||
"parserOptions": { | ||
"ecmaVersion": 2021 | ||
} | ||
} | ||
@@ -57,0 +84,0 @@ ] |
@@ -55,3 +55,3 @@ asn1js | ||
ASN.1 JavaScript decoder Copyright (c) 2008-2022 Lapo Luchini <lapo@lapo.it> | ||
ASN.1 JavaScript decoder Copyright (c) 2008-2024 Lapo Luchini <lapo@lapo.it> | ||
@@ -70,2 +70,4 @@ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. | ||
- Relative OID support added by [Mistial Developer](https://github.com/mistial-dev) | ||
- dark mode support added by [Oliver Burgmaier](https://github.com/olibu/) | ||
- patches by [Nicolai Søborg](https://github.com/NicolaiSoeborg) | ||
@@ -76,4 +78,5 @@ links | ||
- [official website](https://lapo.it/asn1js/) | ||
- [dedicated domain](https://asn1js.eu/) | ||
- [InDefero tracker](http://idf.lapo.it/p/asn1js/) | ||
- [GitHub mirror](https://github.com/lapo-luchini/asn1js) | ||
- [Ohloh code stats](https://www.openhub.net/p/asn1js) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
268513
3574
80
0
Yes
1
1