···1+# ChangeLog
2+3+## 2.1.1 - 2025-10-24
4+5+### Changed
6+7+- update dev dependencies
8+- fix test suite that was reporting no error with empty responses
9+10+### Added
11+12+- add content length check for BOOLEAN, INTEGER, OID ([GitHub #104](https://github.com/lapo-luchini/asn1js/pull/104))
13+14+## 2.1.0 - 2025-08-03
15+16+### Changed
17+18+- when fields are CHOICEs now both the field name and the choice name are shown (fixes [GitHub #102](https://github.com/lapo-luchini/asn1js/issues/102))
19+- upgrade minimum NodeJS version supported from 12.20.0 to 14.6.0 due to usage of ?. and ?? operators in defs.js (ECMAScript 2020); older code is still linted against ECMAScript 2015 for now
20+21+### Added
22+23+- add tests to check expected decoding
24+25+## 2.0.6 - 2025-07-29
26+27+### Added
28+29+- add proper support for standard Base64 (we previously only supported Base64url) (fixes [GitHub #99](https://github.com/lapo-luchini/asn1js/pull/99))
30+- improve test harness
31+32+## 2.0.5 - 2025-04-12
33+34+### Added
35+36+- add `index-local.html` for local `file://` usage without needing a web server
37+- add definitions support for `LDAPMessage`
38+- #TODO continue producing old ChangeLog entries
+1-1
LICENSE
···1ISC License
23-Copyright (c) 2008-2024 Lapo Luchini <lapo@lapo.it>
45Permission to use, copy, modify, and/or distribute this software for any
6purpose with or without fee is hereby granted, provided that the above
···1ISC License
23+Copyright (c) 2008-2025 Lapo Luchini <lapo@lapo.it>
45Permission to use, copy, modify, and/or distribute this software for any
6purpose with or without fee is hereby granted, provided that the above
+8-6
README.md
···34asn1js is a JavaScript generic ASN.1 parser/decoder that can decode any valid ASN.1 DER or BER structures.
56-An example page that can decode Base64-encoded (raw base64, PEM armoring and `begin-base64` are recognized) or Hex-encoded (or local files with some browsers) is included and can be used both [online on the official website](https://lapo.it/asn1js/) or [offline (ZIP file)](https://lapo.it/asn1js/asn1js.zip).
78Usage with `nodejs`
9-------------------
···67Local usage
68--------------------
6970-Since unfortunately ESM modules are not working on `file:` protocol due to [CORS issues](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#other_differences_between_modules_and_standard_scripts), there is a bundled [single-file version working locally](https://asn1js.eu/index-local.html). It doesn't work online (due to [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) restrictions about inline content) but can be saved locally and opened in a browser. ([known bug](https://github.com/lapo-luchini/asn1js/issues/89): dark mode is currently broken in this mode)
7172Usage from CLI
73--------------------
···81ISC license
82-----------
8384-ASN.1 JavaScript decoder Copyright (c) 2008-2024 Lapo Luchini <lapo@lapo.it>
8586Permission 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.
87···101links
102-----
103104-- [official website](https://lapo.it/asn1js/)
105-- [dedicated domain](https://asn1js.eu/)
106-- [InDefero tracker](http://idf.lapo.it/p/asn1js/)
0107- [GitHub mirror](https://github.com/lapo-luchini/asn1js)
0108- [Ohloh code stats](https://www.openhub.net/p/asn1js)
···34asn1js is a JavaScript generic ASN.1 parser/decoder that can decode any valid ASN.1 DER or BER structures.
56+An example page that can decode Base64-encoded (raw base64, PEM armoring and `begin-base64` are recognized) or Hex-encoded (or local files with some browsers) is included and can be used both [online on the official website](https://asn1js.eu/) or [offline (ZIP file)](https://lapo.it/asn1js/asn1js.zip) by opening `index-local.html`.
78Usage with `nodejs`
9-------------------
···67Local usage
68--------------------
6970+Since unfortunately ESM modules are not working on `file:` protocol due to [CORS issues](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#other_differences_between_modules_and_standard_scripts), there is a bundled [single-file version working locally](https://asn1js.eu/index-local.html). It doesn't work online (due to [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) restrictions about inline content) but can be saved locally and opened in a browser.
7172Usage from CLI
73--------------------
···81ISC license
82-----------
8384+ASN.1 JavaScript decoder Copyright (c) 2008-2025 Lapo Luchini <lapo@lapo.it>
8586Permission 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.
87···101links
102-----
103104+- [official website](https://asn1js.eu/)
105+- [alternate website](https://lapo.it/asn1js/)
106+- [single-file version working locally](https://asn1js.eu/index-local.html) (just save this link)
107+- [InDefero tracker](http://idf.lapo.it/p/asn1js/) (currently offline)
108- [GitHub mirror](https://github.com/lapo-luchini/asn1js)
109+- [ChangeLog on GitHub](https://github.com/lapo-luchini/asn1js/blob/trunk/CHANGELOG.md)
110- [Ohloh code stats](https://www.openhub.net/p/asn1js)
+307-75
asn1.js
···1// ASN.1 JavaScript decoder
2-// Copyright (c) 2008-2024 Lapo Luchini <lapo@lapo.it>
34// Permission to use, copy, modify, and/or distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7-//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
···13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1516-import { Int10 } from './int10.js';
17import { oids } from './oids.js';
1819const
···21 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)?)?$/,
22 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)?)?$/,
23 hexDigits = '0123456789ABCDEF',
24- b64Safe = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
025 tableT61 = [
26 ['', ''],
27 ['AEIOUaeiou', 'รรรรรร รจรฌรฒรน'], // Grave
···41 ['CDELNRSTZcdelnrstz', 'ฤฤฤฤฝลลล ลคลฝฤฤฤฤพลลลกลฅลพ'], // Caron
42 ];
4300000044function stringCut(str, len) {
45 if (str.length > len)
46 str = str.substring(0, len) + ellipsis;
47 return str;
48}
490000050function checkPrintable(s) {
51 let i, v;
52 for (i = 0; i < s.length; ++i) {
···56 }
57}
5859-class Stream {
0000600000061 constructor(enc, pos) {
62 if (enc instanceof Stream) {
63 this.enc = enc.enc;
64 this.pos = enc.pos;
65 } else {
66- // enc should be an array or a binary string
67 this.enc = enc;
68 this.pos = pos;
69 }
00000000070 }
00000071 get(pos) {
72 if (pos === undefined)
73 pos = this.pos++;
74 if (pos >= this.enc.length)
75 throw new Error('Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length);
76- return (typeof this.enc == 'string') ? this.enc.charCodeAt(pos) : this.enc[pos];
77 }
78- hexByte(b) {
00000079 return hexDigits.charAt((b >> 4) & 0xF) + hexDigits.charAt(b & 0xF);
80 }
81- /** Hexadecimal dump.
82- * @param type 'raw', 'byte' or 'dump' */
00000083 hexDump(start, end, type = 'dump') {
84 let s = '';
85 for (let i = start; i < end; ++i) {
86 if (type == 'byte' && i > start)
87 s += ' ';
88- s += this.hexByte(this.get(i));
89 if (type == 'dump')
90 switch (i & 0xF) {
91 case 0x7: s += ' '; break;
···95 }
96 return s;
97 }
98- b64Dump(start, end) {
99- let extra = (end - start) % 3,
100- s = '',
000000000101 i, c;
102 for (i = start; i + 2 < end; i += 3) {
103 c = this.get(i) << 16 | this.get(i + 1) << 8 | this.get(i + 2);
104- s += b64Safe.charAt(c >> 18 & 0x3F);
105- s += b64Safe.charAt(c >> 12 & 0x3F);
106- s += b64Safe.charAt(c >> 6 & 0x3F);
107- s += b64Safe.charAt(c & 0x3F);
108 }
109 if (extra > 0) {
110 c = this.get(i) << 16;
111 if (extra > 1) c |= this.get(i + 1) << 8;
112- s += b64Safe.charAt(c >> 18 & 0x3F);
113- s += b64Safe.charAt(c >> 12 & 0x3F);
114- if (extra == 2) s += b64Safe.charAt(c >> 6 & 0x3F);
0115 }
116 return s;
117 }
0000000118 isASCII(start, end) {
119 for (let i = start; i < end; ++i) {
120 let c = this.get(i);
···123 }
124 return true;
125 }
00000000126 parseStringISO(start, end, maxLength) {
127 let s = '';
128 for (let i = start; i < end; ++i)
129 s += String.fromCharCode(this.get(i));
130 return { size: s.length, str: stringCut(s, maxLength) };
131 }
00000000132 parseStringT61(start, end, maxLength) {
133 // warning: this code is not very well tested so far
134 function merge(c, d) {
135- let t = tableT61[c - 0xC0];
136- let i = t[0].indexOf(String.fromCharCode(d));
137 return (i < 0) ? '\0' : t[1].charAt(i);
138 }
139 let s = '', c;
···150 }
151 return { size: s.length, str: stringCut(s, maxLength) };
152 }
00000000153 parseStringUTF(start, end, maxLength) {
00000154 function ex(c) { // must be 10xxxxxx
155 if ((c < 0x80) || (c >= 0xC0))
156 throw new Error('Invalid UTF-8 continuation byte: ' + c);
157 return (c & 0x3F);
158 }
00000159 function surrogate(cp) {
160 if (cp < 0x10000)
161 throw new Error('UTF-8 overlong encoding, codepoint encoded in 4 bytes: ' + cp);
···165 }
166 let s = '';
167 for (let i = start; i < end; ) {
168- let c = this.get(i++);
169 if (c < 0x80) // 0xxxxxxx (7 bit)
170 s += String.fromCharCode(c);
171 else if (c < 0xC0)
···181 }
182 return { size: s.length, str: stringCut(s, maxLength) };
183 }
00000000184 parseStringBMP(start, end, maxLength) {
185 let s = '', hi, lo;
186 for (let i = start; i < end; ) {
···190 }
191 return { size: s.length, str: stringCut(s, maxLength) };
192 }
00000000193 parseTime(start, end, shortYear) {
194 let s = this.parseStringISO(start, end).str,
195 m = (shortYear ? reTimeS : reTimeL).exec(s);
···217 }
218 return s;
219 }
0000000220 parseInteger(start, end) {
221 let v = this.get(start),
222- neg = (v > 127),
223- pad = neg ? 255 : 0,
224- len,
225 s = '';
00226 // skip unuseful bits (not allowed in DER)
227 while (v == pad && ++start < end)
228 v = this.get(start);
229- len = end - start;
230 if (len === 0)
231 return neg ? '-1' : '0';
232 // show bit length of huge integers
233 if (len > 4) {
234- s = v;
235- len <<= 3;
236- while (((s ^ pad) & 0x80) == 0) {
237- s <<= 1;
238- --len;
239 }
240- s = '(' + len + ' bit)\n';
241 }
242 // decode the integer
243 if (neg) v = v - 256;
244- let n = new Int10(v);
245 for (let i = start + 1; i < end; ++i)
246- n.mulAdd(256, this.get(i));
247- return s + n.toString();
248 }
00000000249 parseBitString(start, end, maxLength) {
250- let unusedBits = this.get(start);
251 if (unusedBits > 7)
252 throw new Error('Invalid BitString with unusedBits=' + unusedBits);
253- let lenBit = ((end - start - 1) << 3) - unusedBits,
254- s = '';
255 for (let i = start + 1; i < end; ++i) {
256 let b = this.get(i),
257 skip = (i == end - 1) ? unusedBits : 0;
···262 }
263 return { size: lenBit, str: s };
264 }
00000000265 parseOctetString(start, end, maxLength) {
266- let len = end - start,
267- s;
268 try {
269- s = this.parseStringUTF(start, end, maxLength);
270 checkPrintable(s.str);
271 return { size: end - start, str: s.str };
272- } catch (e) {
273- // ignore
274 }
0275 maxLength /= 2; // we work in bytes
276 if (len > maxLength)
277 end = start + maxLength;
278- s = '';
279 for (let i = start; i < end; ++i)
280- s += this.hexByte(this.get(i));
281 if (len > maxLength)
282 s += ellipsis;
283 return { size: len, str: s };
284 }
000000000285 parseOID(start, end, maxLength, isRelative) {
286 let s = '',
287- n = new Int10(),
288 bits = 0;
289 for (let i = start; i < end; ++i) {
290 let v = this.get(i);
291- n.mulAdd(128, v & 0x7F);
0292 bits += 7;
0293 if (!(v & 0x80)) { // finished
0294 if (s === '') {
295- n = n.simplify();
296 if (isRelative) {
297- s = (n instanceof Int10) ? n.toString() : '' + n;
298- } else if (n instanceof Int10) {
299- n.sub(80);
300- s = '2.' + n.toString();
301 } else {
302- let m = n < 80 ? n < 40 ? 0 : 1 : 2;
303- s = m + '.' + (n - m * 40);
304 }
305 } else
306- s += '.' + n.toString();
307 if (s.length > maxLength)
308 return stringCut(s, maxLength);
309- n = new Int10();
310 bits = 0;
311 }
312 }
313 if (bits > 0)
314 s += '.incomplete';
0315 if (typeof oids === 'object' && !isRelative) {
316 let oid = oids[s];
317 if (oid) {
···322 }
323 return s;
324 }
00000000325 parseRelativeOID(start, end, maxLength) {
326 return this.parseOID(start, end, maxLength, true);
327 }
···354 this.tagConstructed = ((buf & 0x20) !== 0);
355 this.tagNumber = buf & 0x1F;
356 if (this.tagNumber == 0x1F) { // long tag
357- let n = new Int10();
358 do {
359 buf = stream.get();
360- n.mulAdd(128, buf & 0x7F);
361 } while (buf & 0x80);
362- this.tagNumber = n.simplify();
363 }
364 }
365 isUniversal() {
···370 }
371}
3720000373export class ASN1 {
000000000374 constructor(stream, header, length, tag, tagLen, sub) {
375 if (!(tag instanceof ASN1Tag)) throw new Error('Invalid tag value.');
376 this.stream = stream;
···380 this.tagLen = tagLen;
381 this.sub = sub;
382 }
00000383 typeName() {
384 switch (this.tag.tagClass) {
385 case 0: // universal
···419 case 3: return 'Private_' + this.tag.tagNumber.toString();
420 }
421 }
422- /** A string preview of the content (intended for humans). */
00000423 content(maxLength) {
424 if (this.tag === undefined)
425 return null;
426 if (maxLength === undefined)
427 maxLength = Infinity;
428- let content = this.posContent(),
429 len = Math.abs(this.length);
430 if (!this.tag.isUniversal()) {
431 if (this.sub !== null)
···435 }
436 switch (this.tag.tagNumber) {
437 case 0x01: // BOOLEAN
0438 return (this.stream.get(content) === 0) ? 'false' : 'true';
439 case 0x02: // INTEGER
0440 return this.stream.parseInteger(content, content + len);
441 case 0x03: { // BIT_STRING
442 let d = recurse(this, 'parseBitString', maxLength);
···448 }
449 //case 0x05: // NULL
450 case 0x06: // OBJECT_IDENTIFIER
0451 return this.stream.parseOID(content, content + len, maxLength);
452 //case 0x07: // ObjectDescriptor
453 //case 0x08: // EXTERNAL
···484 }
485 return null;
486 }
00000487 toString() {
488 return this.typeName() + '@' + this.stream.pos + '[header:' + this.header + ',length:' + this.length + ',sub:' + ((this.sub === null) ? 'null' : this.sub.length) + ']';
489 }
000000490 toPrettyString(indent) {
491 if (indent === undefined) indent = '';
492 let s = indent;
···517 }
518 return s;
519 }
00000520 posStart() {
521 return this.stream.pos;
522 }
00000523 posContent() {
524 return this.stream.pos + this.header;
525 }
00000526 posEnd() {
527 return this.stream.pos + this.header + Math.abs(this.length);
528 }
529- /** Position of the length. */
0000530 posLen() {
531 return this.stream.pos + this.tagLen;
532 }
533- /** Hexadecimal dump of the node.
534- * @param type 'raw', 'byte' or 'dump' */
0000535 toHexString(type = 'raw') {
536 return this.stream.hexDump(this.posStart(), this.posEnd(), type);
537 }
538- /** Base64 dump of the node. */
539- toB64String() {
540- return this.stream.b64Dump(this.posStart(), this.posEnd());
00000541 }
0000000542 static decodeLength(stream) {
543- let buf = stream.get(),
544 len = buf & 0x7F;
545 if (len == buf) // first bit was 0, short form
546 return len;
547 if (len === 0) // long form with length 0 is a special case
548 return null; // undefined length
549- if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways
550 throw new Error('Length over 48 bits not supported at position ' + (stream.pos - 1));
551- buf = 0;
552 for (let i = 0; i < len; ++i)
553- buf = (buf * 256) + stream.get();
554- return buf;
555 }
000000000556 static decode(stream, offset, type = ASN1) {
557 if (!(type == ASN1 || type.prototype instanceof ASN1))
558 throw new Error('Must pass a class that extends ASN1');
···610 throw new Error('Unable to parse content: ' + e);
611 }
612 }
613- } catch (e) {
614 // but silently ignore when they don't
615 sub = null;
616 //DEBUG console.log('Could not decode structure at ' + start + ':', e);
···1// ASN.1 JavaScript decoder
2+// Copyright (c) 2008 Lapo Luchini <lapo@lapo.it>
34// Permission to use, copy, modify, and/or distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7+//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
···13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15016import { oids } from './oids.js';
1718const
···20 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)?)?$/,
21 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)?)?$/,
22 hexDigits = '0123456789ABCDEF',
23+ b64Std = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
24+ b64URL = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
25 tableT61 = [
26 ['', ''],
27 ['AEIOUaeiou', 'รรรรรร รจรฌรฒรน'], // Grave
···41 ['CDELNRSTZcdelnrstz', 'ฤฤฤฤฝลลล ลคลฝฤฤฤฤพลลลกลฅลพ'], // Caron
42 ];
4344+/**
45+ * Truncates a string to a specified length and adds an ellipsis if needed.
46+ * @param {string} str - The input string to truncate
47+ * @param {number} len - The maximum length of the string
48+ * @returns {string} The truncated string
49+ */
50function stringCut(str, len) {
51 if (str.length > len)
52 str = str.substring(0, len) + ellipsis;
53 return str;
54}
5556+/**
57+ * Checks if a string contains only printable characters (ASCII 32-126, plus tab, newline, carriage return)
58+ * @param {string} s - The string to check
59+ * @throws {Error} If an unprintable character is found
60+ */
61function checkPrintable(s) {
62 let i, v;
63 for (i = 0; i < s.length; ++i) {
···67 }
68}
6970+/**
71+ * Class to manage a stream of bytes, with a zero-copy approach.
72+ * It uses an existing array or binary string and advances a position index.
73+ */
74+export class Stream {
7576+ /**
77+ * Creates a new Stream object.
78+ * @param {Stream|array|string} enc data (will not be copied)
79+ * @param {?number} pos starting position (mandatory when `end` is not a Stream)
80+ */
81 constructor(enc, pos) {
82 if (enc instanceof Stream) {
83 this.enc = enc.enc;
84 this.pos = enc.pos;
85 } else {
086 this.enc = enc;
87 this.pos = pos;
88 }
89+ if (typeof this.pos != 'number')
90+ throw new Error('"pos" must be a numeric value');
91+ // Set up the raw byte access function based on the type of data
92+ if (typeof this.enc == 'string')
93+ this.getRaw = pos => this.enc.charCodeAt(pos);
94+ else if (typeof this.enc[0] == 'number')
95+ this.getRaw = pos => this.enc[pos];
96+ else
97+ throw new Error('"enc" must be a numeric array or a string');
98 }
99+100+ /**
101+ * Get the byte at current position (and increment it) or at a specified position (and avoid moving current position).
102+ * @param {?number} pos read position if specified, else current position (and increment it)
103+ * @returns {number} The byte value at the specified position
104+ */
105 get(pos) {
106 if (pos === undefined)
107 pos = this.pos++;
108 if (pos >= this.enc.length)
109 throw new Error('Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length);
110+ return this.getRaw(pos);
111 }
112+113+ /**
114+ * Convert a single byte to a hexadecimal string (of length 2).
115+ * @param {number} b - The byte to convert
116+ * @returns {string} Hexadecimal representation of the byte
117+ */
118+ static hexByte(b) {
119 return hexDigits.charAt((b >> 4) & 0xF) + hexDigits.charAt(b & 0xF);
120 }
121+122+ /**
123+ * Hexadecimal dump of a specified region of the stream.
124+ * @param {number} start - starting position (included)
125+ * @param {number} end - ending position (excluded)
126+ * @param {string} type - 'raw', 'byte' or 'dump' (default)
127+ * @returns {string} Hexadecimal representation of the data
128+ */
129 hexDump(start, end, type = 'dump') {
130 let s = '';
131 for (let i = start; i < end; ++i) {
132 if (type == 'byte' && i > start)
133 s += ' ';
134+ s += Stream.hexByte(this.get(i));
135 if (type == 'dump')
136 switch (i & 0xF) {
137 case 0x7: s += ' '; break;
···141 }
142 return s;
143 }
144+145+ /**
146+ * Base64url dump of a specified region of the stream (according to RFC 4648 section 5).
147+ * @param {number} start - starting position (included)
148+ * @param {number} end - ending position (excluded)
149+ * @param {string} type - 'url' (default, section 5 without padding) or 'std' (section 4 with padding)
150+ * @returns {string} Base64 encoded representation of the data
151+ */
152+ b64Dump(start, end, type = 'url') {
153+ const b64 = type === 'url' ? b64URL : b64Std,
154+ extra = (end - start) % 3;
155+ let s = '',
156 i, c;
157 for (i = start; i + 2 < end; i += 3) {
158 c = this.get(i) << 16 | this.get(i + 1) << 8 | this.get(i + 2);
159+ s += b64.charAt(c >> 18 & 0x3F);
160+ s += b64.charAt(c >> 12 & 0x3F);
161+ s += b64.charAt(c >> 6 & 0x3F);
162+ s += b64.charAt(c & 0x3F);
163 }
164 if (extra > 0) {
165 c = this.get(i) << 16;
166 if (extra > 1) c |= this.get(i + 1) << 8;
167+ s += b64.charAt(c >> 18 & 0x3F);
168+ s += b64.charAt(c >> 12 & 0x3F);
169+ if (extra == 2) s += b64.charAt(c >> 6 & 0x3F);
170+ if (b64 === b64Std) s += '==='.slice(0, 3 - extra);
171 }
172 return s;
173 }
174+175+ /**
176+ * Check if a region of the stream contains only ASCII characters (32-176)
177+ * @param {number} start - starting position (included)
178+ * @param {number} end - ending position (excluded)
179+ * @returns {boolean} True if all characters are ASCII, false otherwise
180+ */
181 isASCII(start, end) {
182 for (let i = start; i < end; ++i) {
183 let c = this.get(i);
···186 }
187 return true;
188 }
189+190+ /**
191+ * Parse a region of the stream as an ISO string
192+ * @param {number} start - starting position (included)
193+ * @param {number} end - ending position (excluded)
194+ * @param {number} maxLength - maximum length of the output string
195+ * @returns {Object} Object with size and str properties
196+ */
197 parseStringISO(start, end, maxLength) {
198 let s = '';
199 for (let i = start; i < end; ++i)
200 s += String.fromCharCode(this.get(i));
201 return { size: s.length, str: stringCut(s, maxLength) };
202 }
203+204+ /**
205+ * Parse a region of the stream as a T.61 string
206+ * @param {number} start - starting position (included)
207+ * @param {number} end - ending position (excluded)
208+ * @param {number} maxLength - maximum length of the output string
209+ * @returns {Object} Object with size and str properties
210+ */
211 parseStringT61(start, end, maxLength) {
212 // warning: this code is not very well tested so far
213 function merge(c, d) {
214+ const t = tableT61[c - 0xC0];
215+ const i = t[0].indexOf(String.fromCharCode(d));
216 return (i < 0) ? '\0' : t[1].charAt(i);
217 }
218 let s = '', c;
···229 }
230 return { size: s.length, str: stringCut(s, maxLength) };
231 }
232+233+ /**
234+ * Parse a region of the stream as a UTF-8 string
235+ * @param {number} start - starting position (included)
236+ * @param {number} end - ending position (excluded)
237+ * @param {number} maxLength - maximum length of the output string
238+ * @returns {Object} Object with size and str properties
239+ */
240 parseStringUTF(start, end, maxLength) {
241+ /**
242+ * Helper function to process UTF-8 continuation bytes
243+ * @param {number} c - The continuation byte
244+ * @returns {number} The extracted data bits
245+ */
246 function ex(c) { // must be 10xxxxxx
247 if ((c < 0x80) || (c >= 0xC0))
248 throw new Error('Invalid UTF-8 continuation byte: ' + c);
249 return (c & 0x3F);
250 }
251+ /**
252+ * Helper function to convert a code point to a surrogate pair
253+ * @param {number} cp - The code point to convert
254+ * @returns {string} The surrogate pair as a string
255+ */
256 function surrogate(cp) {
257 if (cp < 0x10000)
258 throw new Error('UTF-8 overlong encoding, codepoint encoded in 4 bytes: ' + cp);
···262 }
263 let s = '';
264 for (let i = start; i < end; ) {
265+ const c = this.get(i++);
266 if (c < 0x80) // 0xxxxxxx (7 bit)
267 s += String.fromCharCode(c);
268 else if (c < 0xC0)
···278 }
279 return { size: s.length, str: stringCut(s, maxLength) };
280 }
281+282+ /**
283+ * Parse a region of the stream as a BMP (Basic Multilingual Plane) string
284+ * @param {number} start - starting position (included)
285+ * @param {number} end - ending position (excluded)
286+ * @param {number} maxLength - maximum length of the output string
287+ * @returns {Object} Object with size and str properties
288+ */
289 parseStringBMP(start, end, maxLength) {
290 let s = '', hi, lo;
291 for (let i = start; i < end; ) {
···295 }
296 return { size: s.length, str: stringCut(s, maxLength) };
297 }
298+299+ /**
300+ * Parse a region of the stream as a time string
301+ * @param {number} start - starting position (included)
302+ * @param {number} end - ending position (excluded)
303+ * @param {boolean} shortYear - Whether to parse as short year (2-digit)
304+ * @returns {string} Formatted time string
305+ */
306 parseTime(start, end, shortYear) {
307 let s = this.parseStringISO(start, end).str,
308 m = (shortYear ? reTimeS : reTimeL).exec(s);
···330 }
331 return s;
332 }
333+334+ /**
335+ * Parse a region of the stream as an integer
336+ * @param {number} start - starting position (included)
337+ * @param {number} end - ending position (excluded)
338+ * @returns {string} Formatted integer string
339+ */
340 parseInteger(start, end) {
341 let v = this.get(start),
000342 s = '';
343+ const neg = (v > 127),
344+ pad = neg ? 255 : 0;
345 // skip unuseful bits (not allowed in DER)
346 while (v == pad && ++start < end)
347 v = this.get(start);
348+ const len = end - start;
349 if (len === 0)
350 return neg ? '-1' : '0';
351 // show bit length of huge integers
352 if (len > 4) {
353+ let v2 = v,
354+ lenBit = len << 3;
355+ while (((v2 ^ pad) & 0x80) == 0) {
356+ v2 <<= 1;
357+ --lenBit;
358 }
359+ s = '(' + lenBit + ' bit)\n';
360 }
361 // decode the integer
362 if (neg) v = v - 256;
363+ let n = BigInt(v);
364 for (let i = start + 1; i < end; ++i)
365+ n = (n << 8n) | BigInt(this.get(i));
366+ return s + n;
367 }
368+369+ /**
370+ * Parse a region of the stream as a bit string.
371+ * @param {number} start - starting position (included)
372+ * @param {number} end - ending position (excluded)
373+ * @param {number} maxLength - maximum length of the output string
374+ * @returns {Object} Object with size and str properties
375+ */
376 parseBitString(start, end, maxLength) {
377+ const unusedBits = this.get(start);
378 if (unusedBits > 7)
379 throw new Error('Invalid BitString with unusedBits=' + unusedBits);
380+ const lenBit = ((end - start - 1) << 3) - unusedBits;
381+ let s = '';
382 for (let i = start + 1; i < end; ++i) {
383 let b = this.get(i),
384 skip = (i == end - 1) ? unusedBits : 0;
···389 }
390 return { size: lenBit, str: s };
391 }
392+393+ /**
394+ * Parse a region of the stream as an octet string.
395+ * @param {number} start - starting position (included)
396+ * @param {number} end - ending position (excluded)
397+ * @param {number} maxLength - maximum length of the output string
398+ * @returns {Object} Object with size and str properties
399+ */
400 parseOctetString(start, end, maxLength) {
00401 try {
402+ let s = this.parseStringUTF(start, end, maxLength);
403 checkPrintable(s.str);
404 return { size: end - start, str: s.str };
405+ } catch (ignore) {
406+ // If UTF-8 parsing fails, fall back to hexadecimal dump
407 }
408+ const len = end - start;
409 maxLength /= 2; // we work in bytes
410 if (len > maxLength)
411 end = start + maxLength;
412+ let s = '';
413 for (let i = start; i < end; ++i)
414+ s += Stream.hexByte(this.get(i));
415 if (len > maxLength)
416 s += ellipsis;
417 return { size: len, str: s };
418 }
419+420+ /**
421+ * Parse a region of the stream as an OID (Object Identifier).
422+ * @param {number} start - starting position (included)
423+ * @param {number} end - ending position (excluded)
424+ * @param {number} maxLength - maximum length of the output string
425+ * @param {boolean} isRelative - Whether the OID is relative
426+ * @returns {string} Formatted OID string
427+ */
428 parseOID(start, end, maxLength, isRelative) {
429 let s = '',
430+ n = 0n,
431 bits = 0;
432 for (let i = start; i < end; ++i) {
433 let v = this.get(i);
434+ // Shift bits and add the lower 7 bits of the byte
435+ n = (n << 7n) | BigInt(v & 0x7F);
436 bits += 7;
437+ // If the most significant bit is 0, this is the last byte of the OID component
438 if (!(v & 0x80)) { // finished
439+ // If this is the first component, handle it specially
440 if (s === '') {
0441 if (isRelative) {
442+ s = n.toString();
000443 } else {
444+ let m = n < 80 ? n < 40 ? 0n : 1n : 2n;
445+ s = m + '.' + (n - m * 40n);
446 }
447 } else
448+ s += '.' + n;
449 if (s.length > maxLength)
450 return stringCut(s, maxLength);
451+ n = 0n;
452 bits = 0;
453 }
454 }
455 if (bits > 0)
456 s += '.incomplete';
457+ // If OIDs mapping is available and the OID is absolute, try to resolve it
458 if (typeof oids === 'object' && !isRelative) {
459 let oid = oids[s];
460 if (oid) {
···465 }
466 return s;
467 }
468+469+ /**
470+ * Parse a region of the stream as a relative OID (Object Identifier).
471+ * @param {number} start - starting position (included)
472+ * @param {number} end - ending position (excluded)
473+ * @param {number} maxLength - maximum length of the output string
474+ * @returns {string} Formatted relative OID string
475+ */
476 parseRelativeOID(start, end, maxLength) {
477 return this.parseOID(start, end, maxLength, true);
478 }
···505 this.tagConstructed = ((buf & 0x20) !== 0);
506 this.tagNumber = buf & 0x1F;
507 if (this.tagNumber == 0x1F) { // long tag
508+ let n = 0n;
509 do {
510 buf = stream.get();
511+ n = (n << 7n) | BigInt(buf & 0x7F);
512 } while (buf & 0x80);
513+ this.tagNumber = n <= Number.MAX_SAFE_INTEGER ? Number(n) : n;
514 }
515 }
516 isUniversal() {
···521 }
522}
523524+/**
525+ * ASN1 class for parsing ASN.1 encoded data.
526+ * Instances of this class represent an ASN.1 element and provides methods to parse and display its content.
527+ */
528export class ASN1 {
529+ /**
530+ * Creates an ASN1 parser object.
531+ * @param {Stream} stream - The stream containing the ASN.1 data.
532+ * @param {number} header - The header length.
533+ * @param {number} length - The length of the data.
534+ * @param {ASN1Tag} tag - The ASN.1 tag.
535+ * @param {number} tagLen - The length of the tag.
536+ * @param {Array} sub - The sub-elements.
537+ */
538 constructor(stream, header, length, tag, tagLen, sub) {
539 if (!(tag instanceof ASN1Tag)) throw new Error('Invalid tag value.');
540 this.stream = stream;
···544 this.tagLen = tagLen;
545 this.sub = sub;
546 }
547+548+ /**
549+ * Get the type name of the ASN.1 element.
550+ * @returns {string} The type name.
551+ */
552 typeName() {
553 switch (this.tag.tagClass) {
554 case 0: // universal
···588 case 3: return 'Private_' + this.tag.tagNumber.toString();
589 }
590 }
591+592+ /**
593+ * Get a string preview of the content (intended for humans).
594+ * @param {number} maxLength - The maximum length of the content.
595+ * @returns {string|null} The content preview or null if not supported.
596+ */
597 content(maxLength) {
598 if (this.tag === undefined)
599 return null;
600 if (maxLength === undefined)
601 maxLength = Infinity;
602+ const content = this.posContent(),
603 len = Math.abs(this.length);
604 if (!this.tag.isUniversal()) {
605 if (this.sub !== null)
···609 }
610 switch (this.tag.tagNumber) {
611 case 0x01: // BOOLEAN
612+ if (len != 1) return 'invalid length ' + len;
613 return (this.stream.get(content) === 0) ? 'false' : 'true';
614 case 0x02: // INTEGER
615+ if (len < 1) return 'invalid length ' + len;
616 return this.stream.parseInteger(content, content + len);
617 case 0x03: { // BIT_STRING
618 let d = recurse(this, 'parseBitString', maxLength);
···624 }
625 //case 0x05: // NULL
626 case 0x06: // OBJECT_IDENTIFIER
627+ if (len < 1) return 'invalid length ' + len; // pgut001's dumpasn1.c enforces a minimum lenght of 3
628 return this.stream.parseOID(content, content + len, maxLength);
629 //case 0x07: // ObjectDescriptor
630 //case 0x08: // EXTERNAL
···661 }
662 return null;
663 }
664+665+ /**
666+ * Get a string representation of the ASN.1 element.
667+ * @returns {string} The string representation.
668+ */
669 toString() {
670 return this.typeName() + '@' + this.stream.pos + '[header:' + this.header + ',length:' + this.length + ',sub:' + ((this.sub === null) ? 'null' : this.sub.length) + ']';
671 }
672+673+ /**
674+ * Get a pretty string representation of the ASN.1 element.
675+ * @param {string} indent - The indentation string.
676+ * @returns {string} The pretty string representation.
677+ */
678 toPrettyString(indent) {
679 if (indent === undefined) indent = '';
680 let s = indent;
···705 }
706 return s;
707 }
708+709+ /**
710+ * Get the starting position of the element in the stream.
711+ * @returns {number} The starting position.
712+ */
713 posStart() {
714 return this.stream.pos;
715 }
716+717+ /**
718+ * Get the position of the content in the stream.
719+ * @returns {number} The content position.
720+ */
721 posContent() {
722 return this.stream.pos + this.header;
723 }
724+725+ /**
726+ * Get the ending position of the element in the stream.
727+ * @returns {number} The ending position.
728+ */
729 posEnd() {
730 return this.stream.pos + this.header + Math.abs(this.length);
731 }
732+733+ /**
734+ * Get the position of the length in the stream.
735+ * @returns {number} The length position.
736+ */
737 posLen() {
738 return this.stream.pos + this.tagLen;
739 }
740+741+ /**
742+ * Get a hexadecimal dump of the node.
743+ * @param {string} [type='raw'] - The dump type: 'raw', 'byte', or 'dump'.
744+ * @returns {string} The hexadecimal dump.
745+ */
746 toHexString(type = 'raw') {
747 return this.stream.hexDump(this.posStart(), this.posEnd(), type);
748 }
749+750+ /**
751+ * Get a base64url dump of the node (according to RFC 4648 section 5).
752+ * @param {string} [type='url'] - The dump type: 'url' (section 5 without padding) or 'std' (section 4 with padding).
753+ * @returns {string} The base64 encoded representation.
754+ */
755+ toB64String(type = 'url') {
756+ return this.stream.b64Dump(this.posStart(), this.posEnd(), type);
757 }
758+759+ /**
760+ * Decode the length field of an ASN.1 element.
761+ * @param {Stream} stream - The stream to read from.
762+ * @returns {number|null} The decoded length, or null for indefinite length.
763+ * @throws {Error} If the length is invalid or exceeds 48 bits.
764+ */
765 static decodeLength(stream) {
766+ const buf = stream.get(),
767 len = buf & 0x7F;
768 if (len == buf) // first bit was 0, short form
769 return len;
770 if (len === 0) // long form with length 0 is a special case
771 return null; // undefined length
772+ if (len > 6) // no reason to use BigInt, as it would be a huge buffer anyways
773 throw new Error('Length over 48 bits not supported at position ' + (stream.pos - 1));
774+ let value = 0;
775 for (let i = 0; i < len; ++i)
776+ value = (value << 8) | stream.get();
777+ return value;
778 }
779+780+ /**
781+ * Decode an ASN.1 element from a stream.
782+ * @param {Stream|array|string} stream - The input data.
783+ * @param {number} [offset=0] - The offset to start decoding from.
784+ * @param {Function} [type=ASN1] - The class to instantiate.
785+ * @returns {ASN1} The decoded ASN.1 element.
786+ * @throws {Error} If the decoding fails.
787+ */
788 static decode(stream, offset, type = ASN1) {
789 if (!(type == ASN1 || type.prototype instanceof ASN1))
790 throw new Error('Must pass a class that extends ASN1');
···842 throw new Error('Unable to parse content: ' + e);
843 }
844 }
845+ } catch (ignore) {
846 // but silently ignore when they don't
847 sub = null;
848 //DEBUG console.log('Could not decode structure at ' + start + ':', e);
+8-7
base64.js
···1// Base64 JavaScript decoder
2-// Copyright (c) 2008-2024 Lapo Luchini <lapo@lapo.it>
34// Permission to use, copy, modify, and/or distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7-//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
···31 decoder[b64.charCodeAt(i)] = i;
32 for (i = 0; i < ignore.length; ++i)
33 decoder[ignore.charCodeAt(i)] = -1;
34- // RFC 3548 URL & file safe encoding
35 decoder['-'.charCodeAt(0)] = decoder['+'.charCodeAt(0)];
36 decoder['_'.charCodeAt(0)] = decoder['/'.charCodeAt(0)];
37 }
···7576 static pretty(str) {
77 // fix padding
78- if (str.length % 4 > 0)
79- str = (str + '===').slice(0, str.length + str.length % 4);
80- // convert RFC 3548 to standard Base64
081 str = str.replace(/-/g, '+').replace(/_/g, '/');
82 // 80 column width
83- return str.replace(/(.{80})/g, '$1\n');
84 }
8586 static unarmor(a) {
···1// Base64 JavaScript decoder
2+// Copyright (c) 2008 Lapo Luchini <lapo@lapo.it>
34// Permission to use, copy, modify, and/or distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7+//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
···31 decoder[b64.charCodeAt(i)] = i;
32 for (i = 0; i < ignore.length; ++i)
33 decoder[ignore.charCodeAt(i)] = -1;
34+ // also support decoding Base64url (RFC 4648 section 5)
35 decoder['-'.charCodeAt(0)] = decoder['+'.charCodeAt(0)];
36 decoder['_'.charCodeAt(0)] = decoder['/'.charCodeAt(0)];
37 }
···7576 static pretty(str) {
77 // fix padding
78+ let pad = 4 - str.length % 4;
79+ if (pad < 4)
80+ str += '==='.slice(0, pad);
81+ // convert Base64url (RFC 4648 section 5) to standard Base64 (RFC 4648 section 4)
82 str = str.replace(/-/g, '+').replace(/_/g, '/');
83 // 80 column width
84+ return str.replace(/.{80}/g, '$&\n');
85 }
8687 static unarmor(a) {
···1+CRL example from RFC5280 as found here:
2+https://csrc.nist.gov/projects/pki-testing/sample-certificates-and-crls
3+4+begin-base64 644 crl-rfc5280.der
5+MIIBYDCBygIBATANBgkqhkiG9w0BAQUFADBDMRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZIm
6+iZPyLGQBGRYHZXhhbXBsZTETMBEGA1UEAxMKRXhhbXBsZSBDQRcNMDUwMjA1MTIwMDAwWhcNMDUw
7+MjA2MTIwMDAwWjAiMCACARIXDTA0MTExOTE1NTcwM1owDDAKBgNVHRUEAwoBAaAvMC0wHwYDVR0j
8+BBgwFoAUCGivhTPIOUp6+IKTjnBqSiCELDIwCgYDVR0UBAMCAQwwDQYJKoZIhvcNAQEFBQADgYEA
9+ItwYffcIzsx10NBqm60Q9HYjtIFutW2+DvsVFGzIF20f7pAXom9g5L2qjFXejoRvkvifEBInr0rU
10+L4XiNkR9qqNMJTgV/wD9Pn7uPSYS69jnK2LiK8NGgO94gtEVxtCccmrLznrtZ5mLbnCBfUNCdMGm
11+r8FVF6IzTNYGmCuk/C4=
12+====
+45
examples/crl-rfc5280.b64.dump
···000000000000000000000000000000000000000000000
···1+CertificateList SEQUENCE @0+352 (constructed): (3 elem)
2+ tbsCertList TBSCertList SEQUENCE @4+202 (constructed): (7 elem)
3+ version Version INTEGER @7+1: 1
4+ signature AlgorithmIdentifier SEQUENCE @10+13 (constructed): (2 elem)
5+ algorithm OBJECT_IDENTIFIER @12+9: 1.2.840.113549.1.1.5|sha1WithRSAEncryption|PKCS #1
6+ parameters ANY NULL @23+0
7+ issuer rdnSequence Name SEQUENCE @25+67 (constructed): (3 elem)
8+ RelativeDistinguishedName SET @27+19 (constructed): (1 elem)
9+ AttributeTypeAndValue SEQUENCE @29+17 (constructed): (2 elem)
10+ type AttributeType OBJECT_IDENTIFIER @31+10: 0.9.2342.19200300.100.1.25|domainComponent|Men are from Mars, this OID is from Pluto
11+ value AttributeValue [?] IA5String @43+3: com
12+ RelativeDistinguishedName SET @48+23 (constructed): (1 elem)
13+ AttributeTypeAndValue SEQUENCE @50+21 (constructed): (2 elem)
14+ type AttributeType OBJECT_IDENTIFIER @52+10: 0.9.2342.19200300.100.1.25|domainComponent|Men are from Mars, this OID is from Pluto
15+ value AttributeValue [?] IA5String @64+7: example
16+ RelativeDistinguishedName SET @73+19 (constructed): (1 elem)
17+ AttributeTypeAndValue SEQUENCE @75+17 (constructed): (2 elem)
18+ type AttributeType OBJECT_IDENTIFIER @77+3: 2.5.4.3|commonName|X.520 DN component
19+ value AttributeValue [?] PrintableString @82+10: Example CA
20+ thisUpdate utcTime Time UTCTime @94+13: 2005-02-05 12:00:00 UTC
21+ nextUpdate utcTime Time UTCTime @109+13: 2005-02-06 12:00:00 UTC
22+ revokedCertificates SEQUENCE @124+34 (constructed): (1 elem)
23+ SEQUENCE @126+32 (constructed): (3 elem)
24+ userCertificate CertificateSerialNumber INTEGER @128+1: 18
25+ revocationDate utcTime Time UTCTime @131+13: 2004-11-19 15:57:03 UTC
26+ crlEntryExtensions Extensions SEQUENCE @146+12 (constructed): (1 elem)
27+ Extension SEQUENCE @148+10 (constructed): (2 elem)
28+ extnID OBJECT_IDENTIFIER @150+3: 2.5.29.21|cRLReason|X.509 extension
29+ extnValue OCTET_STRING @155+3 (encapsulates): (3 byte)|0A0101
30+ ENUMERATED @157+1: 1
31+ crlExtensions [0] @160+47 (constructed): (1 elem)
32+ Extensions SEQUENCE @162+45 (constructed): (2 elem)
33+ Extension SEQUENCE @164+31 (constructed): (2 elem)
34+ extnID OBJECT_IDENTIFIER @166+3: 2.5.29.35|authorityKeyIdentifier|X.509 extension
35+ extnValue OCTET_STRING @171+24 (encapsulates): (24 byte)|301680140868AF8533C8394A7AF882938E706A4A20842C32
36+ SEQUENCE @173+22 (constructed): (1 elem)
37+ [0] @175+20: (20 byte)|0868AF8533C8394A7AF882938E706A4A20842C32
38+ Extension SEQUENCE @197+10 (constructed): (2 elem)
39+ extnID OBJECT_IDENTIFIER @199+3: 2.5.29.20|cRLNumber|X.509 extension
40+ extnValue OCTET_STRING @204+3 (encapsulates): (3 byte)|02010C
41+ INTEGER @206+1: 12
42+ signatureAlgorithm AlgorithmIdentifier SEQUENCE @209+13 (constructed): (2 elem)
43+ algorithm OBJECT_IDENTIFIER @211+9: 1.2.840.113549.1.1.5|sha1WithRSAEncryption|PKCS #1
44+ parameters ANY NULL @222+0
45+ signature BIT_STRING @224+129: (1024 bit)|0010001011011100000110000111110111110111000010001100111011001100011101011101000011010000011010101001101110101101000100001111010001110110001000111011010010000001011011101011010101101101101111100000111011111011000101010001010001101100110010000001011101101101000111111110111010010000000101111010001001101111011000001110010010111101101010101000110001010101110111101000111010000100011011111001001011111000100111110001000000010010001001111010111101001010110101000010111110000101111000100011011001000100011111011010101010100011010011000010010100111000000101011111111100000000111111010011111001111110111011100011110100100110000100101110101111011000111001110010101101100010111000100010101111000011010001101000000011101111011110001000001011010001000101011100011011010000100111000111001001101010110010111100111001111010111011010110011110011001100010110110111001110000100000010111110101000011010000100111010011000001101001101010111111000001010101010001011110100010001100110100110011010110000001101001100000101011101001001111110000101110
···1+LDAPMessage example as found on ldap.com.
2+3+Original link:
4+https://ldap.com/ldapv3-wire-protocol-reference-ldap-message/
5+6+begin-base64 644 ldapmessage.der
7+MDUCAQVKEWRjPWV4YW1wbGUsZGM9Y29toB0wGwQWMS4yLjg0MC4xMTM1NTYuMS40LjgwNQEB/w==
8+====
···1// Hex JavaScript decoder
2-// Copyright (c) 2008-2024 Lapo Luchini <lapo@lapo.it>
34// Permission to use, copy, modify, and/or distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7-//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
···1// Hex JavaScript decoder
2+// Copyright (c) 2008 Lapo Luchini <lapo@lapo.it>
34// Permission to use, copy, modify, and/or distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7+//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
···01import { ASN1DOM } from './dom.js';
2import { Base64 } from './base64.js';
3import { Hex } from './hex.js';
···15 area = id('area'),
16 file = id('file'),
17 examples = id('examples'),
18- selectTheme = id('theme-select'),
19 selectDefs = id('definitions'),
20 selectTag = id('tags');
21···41function show(asn1) {
42 tree.innerHTML = '';
43 dump.innerHTML = '';
44- tree.appendChild(asn1.toDOM());
00045 if (wantHex.checked) dump.appendChild(asn1.toHexDOM(undefined, trimHex.checked));
46}
47export function decode(der, offset) {
···83 if (area.value === '') area.value = Base64.pretty(b64);
84 try {
85 window.location.hash = hash = '#' + b64;
86- } catch (e) {
87 // fails with "Access Denied" on IE with URLs longer than ~2048 chars
88 window.location.hash = hash = '#';
89 }
···119 else if (Base64.re.test(str)) der = Base64.unarmor(str);
120 else der = str;
121 decode(der);
122- } catch (e) {
123 text(tree, 'Cannot decode file.');
124 dump.innerHTML = '';
125 }
···157 let elem = id(name);
158 if (elem)
159 elem.onclick = onClick;
160-}
161-// set dark theme depending on OS settings
162-function setTheme() {
163- if (!selectTheme) {
164- console.log('Themes are currently not working with single file version.');
165- return;
166- }
167- let storedTheme = localStorage.getItem('theme');
168- let theme = 'os';
169- if (storedTheme)
170- theme = storedTheme;
171- selectTheme.value = theme;
172- if (theme == 'os') {
173- let prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
174- theme = prefersDarkScheme.matches ? 'dark': 'light';
175- }
176- if (theme == 'dark') {
177- const css1 = id('theme-base');
178- const css2 = css1.cloneNode();
179- css2.id = 'theme-override';
180- css2.href = 'index-' + theme + '.css';
181- css1.parentElement.appendChild(css2);
182- } else {
183- const css2 = id('theme-override');
184- if (css2) css2.remove();
185- }
186-}
187-setTheme();
188-if (selectTheme) {
189- selectTheme.addEventListener('change', function () {
190- localStorage.setItem('theme', selectTheme.value);
191- setTheme();
192- });
193}
194// this is only used if window.FileReader
195function read(f) {
···1+import './theme.js';
2import { ASN1DOM } from './dom.js';
3import { Base64 } from './base64.js';
4import { Hex } from './hex.js';
···16 area = id('area'),
17 file = id('file'),
18 examples = id('examples'),
019 selectDefs = id('definitions'),
20 selectTag = id('tags');
21···41function show(asn1) {
42 tree.innerHTML = '';
43 dump.innerHTML = '';
44+ let ul = document.createElement('ul');
45+ ul.className = 'treecollapse';
46+ tree.appendChild(ul);
47+ ul.appendChild(asn1.toDOM());
48 if (wantHex.checked) dump.appendChild(asn1.toHexDOM(undefined, trimHex.checked));
49}
50export function decode(der, offset) {
···86 if (area.value === '') area.value = Base64.pretty(b64);
87 try {
88 window.location.hash = hash = '#' + b64;
89+ } catch (ignore) {
90 // fails with "Access Denied" on IE with URLs longer than ~2048 chars
91 window.location.hash = hash = '#';
92 }
···122 else if (Base64.re.test(str)) der = Base64.unarmor(str);
123 else der = str;
124 decode(der);
125+ } catch (ignore) {
126 text(tree, 'Cannot decode file.');
127 dump.innerHTML = '';
128 }
···160 let elem = id(name);
161 if (elem)
162 elem.onclick = onClick;
000000000000000000000000000000000163}
164// this is only used if window.FileReader
165function read(f) {
-106
int10.js
···1-// Big integer base-10 printing library
2-// Copyright (c) 2008-2024 Lapo Luchini <lapo@lapo.it>
3-4-// Permission to use, copy, modify, and/or distribute this software for any
5-// purpose with or without fee is hereby granted, provided that the above
6-// copyright notice and this permission notice appear in all copies.
7-//
8-// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9-// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10-// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11-// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12-// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13-// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14-// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15-16-let max = 10000000000000; // biggest 10^n integer that can still fit 2^53 when multiplied by 256
17-18-export class Int10 {
19- /**
20- * Arbitrary length base-10 value.
21- * @param {number} value - Optional initial value (will be 0 otherwise).
22- */
23- constructor(value) {
24- this.buf = [+value || 0];
25- }
26-27- /**
28- * Multiply value by m and add c.
29- * @param {number} m - multiplier, must be < =256
30- * @param {number} c - value to add
31- */
32- mulAdd(m, c) {
33- // assert(m <= 256)
34- let b = this.buf,
35- l = b.length,
36- i, t;
37- for (i = 0; i < l; ++i) {
38- t = b[i] * m + c;
39- if (t < max)
40- c = 0;
41- else {
42- c = 0|(t / max);
43- t -= c * max;
44- }
45- b[i] = t;
46- }
47- if (c > 0)
48- b[i] = c;
49- }
50-51- /**
52- * Subtract value.
53- * @param {number} c - value to subtract
54- */
55- sub(c) {
56- let b = this.buf,
57- l = b.length,
58- i, t;
59- for (i = 0; i < l; ++i) {
60- t = b[i] - c;
61- if (t < 0) {
62- t += max;
63- c = 1;
64- } else
65- c = 0;
66- b[i] = t;
67- }
68- while (b[b.length - 1] === 0)
69- b.pop();
70- }
71-72- /**
73- * Convert to decimal string representation.
74- * @param {*} base - optional value, only value accepted is 10
75- */
76- toString(base) {
77- if ((base || 10) != 10)
78- throw 'only base 10 is supported';
79- let b = this.buf,
80- s = b[b.length - 1].toString();
81- for (let i = b.length - 2; i >= 0; --i)
82- s += (max + b[i]).toString().substring(1);
83- return s;
84- }
85-86- /**
87- * Convert to Number value representation.
88- * Will probably overflow 2^53 and thus become approximate.
89- */
90- valueOf() {
91- let b = this.buf,
92- v = 0;
93- for (let i = b.length - 1; i >= 0; --i)
94- v = v * max + b[i];
95- return v;
96- }
97-98- /**
99- * Return value as a simple Number (if it is <= 10000000000000), or return this.
100- */
101- simplify() {
102- let b = this.buf;
103- return (b.length == 1) ? b[0] : this;
104- }
105-106-}
···1-// content parsed from ASN.1 definitions as found in the following RFCs: 5280 5208 3369 3161 2986 4211 4210 8017
2// Copyright (C) The IETF Trust (2008)
3// as far as I can tell this file is allowed under the following clause:
4// It is acceptable under the current IETF rules (RFC 5378) to modify extracted code if necessary.
···1983 "CountryName": {
1984 "name": "CountryName",
1985 "type": {
1986- "name": "[1]",
1987 "type": "tag",
1988 "class": "APPLICATION",
1989 "explicit": true,
···2015 "AdministrationDomainName": {
2016 "name": "AdministrationDomainName",
2017 "type": {
2018- "name": "[2]",
2019 "type": "tag",
2020 "class": "APPLICATION",
2021 "explicit": true,
···10369 "type": {
10370 "name": "AlgorithmIdentifier",
10371 "type": "defined"
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010372 }
10373 }
10374 }