···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
+42-9
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).
78-Usage with `npm` / `yarn`
9--------------------------
1011This package can be installed with either npm or yarn via the following commands:
12···17yarn add @lapo/asn1js
18```
1920-Assuming a standard javascript bundler is setup you can import it like so:
2122```js
23-import ASN1 from '@lapo/asn1js';
24```
2526A submodule of this package can also be imported:
2728```js
29-import Hex from '@lapo/asn1js/hex';
30```
3132-Unfortunately until [`require(esm)` gets released](https://joyeecheung.github.io/blog/2024/03/18/require-esm-in-node-js/) it is necessary to use async `import()` when used from CommonJS (legacy NodeJS) code.
00000000000000000003334Usage on the web
35--------------------
···3839```html
40<script>
41-import { ASN1} from 'https://unpkg.com/@lapo/asn1js@2.0.0/asn1.js';
42import { Hex } from 'https://unpkg.com/@lapo/asn1js@2.0.0/hex.js';
4344document.body.innerText = ASN1.decode(Hex.decode('06032B6570')).content();
45</script>
46```
470000000000000048ISC license
49-----------
5051-ASN.1 JavaScript decoder Copyright (c) 2008-2024 Lapo Luchini <lapo@lapo.it>
5253Permission 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.
54
···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`.
78+Usage with `nodejs`
9+-------------------
1011This package can be installed with either npm or yarn via the following commands:
12···17yarn add @lapo/asn1js
18```
1920+You can import the classes like this:
2122```js
23+import { ASN1 } from '@lapo/asn1js';
24```
2526A submodule of this package can also be imported:
2728```js
29+import { Hex } from '@lapo/asn1js/hex.js';
30```
3132+If your code is still not using ES6 Modules (and is using CommonJS) you can `require` it normally [since NodeJS 22](https://joyeecheung.github.io/blog/2024/03/18/require-esm-in-node-js/) (with parameter `--experimental-require-module`):
33+34+```js
35+const
36+ { ASN1 } = require('@lapo/asn1js'),
37+ { Hex } = require('@lapo/asn1js/hex.js');
38+console.log(ASN1.decode(Hex.decode('06032B6570')).content());
39+```
40+41+On older NodeJS you instead need to use async `import`:
42+43+```js
44+async function main() {
45+ const
46+ { ASN1 } = await import('@lapo/asn1js'),
47+ { Hex } = await import('@lapo/asn1js/hex.js');
48+ console.log(ASN1.decode(Hex.decode('06032B6570')).content());
49+}
50+main();
51+```
5253Usage on the web
54--------------------
···5758```html
59<script>
60+import { ASN1 } from 'https://unpkg.com/@lapo/asn1js@2.0.0/asn1.js';
61import { Hex } from 'https://unpkg.com/@lapo/asn1js@2.0.0/hex.js';
6263document.body.innerText = ASN1.decode(Hex.decode('06032B6570')).content();
64</script>
65```
6667+Local usage
68+--------------------
69+70+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.
71+72+Usage from CLI
73+--------------------
74+75+You can dump an ASN.1 structure from the command line using the following command (no need to even install it):
76+77+```sh
78+npx @lapo/asn1js ed25519.cer
79+```
80+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
+50-22
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
···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
···56 }
57}
5859-class Stream {
0060000061 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 }
0000000070 }
0071 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) {
0079 return hexDigits.charAt((b >> 4) & 0xF) + hexDigits.charAt(b & 0xF);
80 }
81- /** Hexadecimal dump.
82- * @param type 'raw', 'byte' or 'dump' */
0083 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) {
0000099 let extra = (end - start) % 3,
100 s = '',
101 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 }
···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 };
···535 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());
00541 }
542 static decodeLength(stream) {
543 let buf = stream.get(),
···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
···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+ b64Std = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
25+ b64URL = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
26 tableT61 = [
27 ['', ''],
28 ['AEIOUaeiou', 'รรรรรร รจรฌรฒรน'], // Grave
···57 }
58}
5960+/** Class to manage a stream of bytes, with a zero-copy approach.
61+ * It uses an existing array or binary string and advances a position index. */
62+export class Stream {
6364+ /**
65+ * @param {Stream|array|string} enc data (will not be copied)
66+ * @param {?number} pos starting position (mandatory when `end` is not a Stream)
67+ */
68 constructor(enc, pos) {
69 if (enc instanceof Stream) {
70 this.enc = enc.enc;
71 this.pos = enc.pos;
72 } else {
073 this.enc = enc;
74 this.pos = pos;
75 }
76+ if (typeof this.pos != 'number')
77+ throw new Error('"pos" must be a numeric value');
78+ if (typeof this.enc == 'string')
79+ this.getRaw = pos => this.enc.charCodeAt(pos);
80+ else if (typeof this.enc[0] == 'number')
81+ this.getRaw = pos => this.enc[pos];
82+ else
83+ throw new Error('"enc" must be a numeric array or a string');
84 }
85+ /** Get the byte at current position (and increment it) or at a specified position (and avoid moving current position).
86+ * @param {?number} pos read position if specified, else current position (and increment it) */
87 get(pos) {
88 if (pos === undefined)
89 pos = this.pos++;
90 if (pos >= this.enc.length)
91 throw new Error('Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length);
92+ return this.getRaw(pos);
93 }
94+ /** Convert a single byte to an hexadcimal string (of length 2).
95+ * @param {number} b */
96+ static hexByte(b) {
97 return hexDigits.charAt((b >> 4) & 0xF) + hexDigits.charAt(b & 0xF);
98 }
99+ /** Hexadecimal dump of a specified region of the stream.
100+ * @param {number} start starting position (included)
101+ * @param {number} end ending position (excluded)
102+ * @param {string} type 'raw', 'byte' or 'dump' (default) */
103 hexDump(start, end, type = 'dump') {
104 let s = '';
105 for (let i = start; i < end; ++i) {
106 if (type == 'byte' && i > start)
107 s += ' ';
108+ s += Stream.hexByte(this.get(i));
109 if (type == 'dump')
110 switch (i & 0xF) {
111 case 0x7: s += ' '; break;
···115 }
116 return s;
117 }
118+ /** Base64url dump of a specified region of the stream (according to RFC 4648 section 5).
119+ * @param {number} start starting position (included)
120+ * @param {number} end ending position (excluded)
121+ * @param {string} type 'url' (default, section 5 without padding) or 'std' (section 4 with padding) */
122+ b64Dump(start, end, type = 'url') {
123+ const b64 = type === 'url' ? b64URL : b64Std;
124 let extra = (end - start) % 3,
125 s = '',
126 i, c;
127 for (i = start; i + 2 < end; i += 3) {
128 c = this.get(i) << 16 | this.get(i + 1) << 8 | this.get(i + 2);
129+ s += b64.charAt(c >> 18 & 0x3F);
130+ s += b64.charAt(c >> 12 & 0x3F);
131+ s += b64.charAt(c >> 6 & 0x3F);
132+ s += b64.charAt(c & 0x3F);
133 }
134 if (extra > 0) {
135 c = this.get(i) << 16;
136 if (extra > 1) c |= this.get(i + 1) << 8;
137+ s += b64.charAt(c >> 18 & 0x3F);
138+ s += b64.charAt(c >> 12 & 0x3F);
139+ if (extra == 2) s += b64.charAt(c >> 6 & 0x3F);
140+ if (b64 === b64Std) s += '==='.slice(0, 3 - extra);
141 }
142 return s;
143 }
···303 end = start + maxLength;
304 s = '';
305 for (let i = start; i < end; ++i)
306+ s += Stream.hexByte(this.get(i));
307 if (len > maxLength)
308 s += ellipsis;
309 return { size: len, str: s };
···561 toHexString(type = 'raw') {
562 return this.stream.hexDump(this.posStart(), this.posEnd(), type);
563 }
564+ /** Base64url dump of the node (according to RFC 4648 section 5).
565+ * @param {string} type 'url' (default, section 5 without padding) or 'std' (section 4 with padding)
566+ */
567+ toB64String(type = 'url') {
568+ return this.stream.b64Dump(this.posStart(), this.posEnd(), type);
569 }
570 static decodeLength(stream) {
571 let buf = stream.get(),
+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) {
···1const
2 id = (elem) => document.getElementById(elem),
3 contextMenu = id('contextmenu'),
04 btnCopyHex = id('btnCopyHex'),
5 btnCopyB64 = id('btnCopyB64'),
6 btnCopyTree = id('btnCopyTree'),
···10 const type = node.asn1.typeName();
11 const valueEnabled = type != 'SET' && type != 'SEQUENCE';
12 node.onclick = function (event) {
13+ // do not show the menu in case of clicking the icon
14+ if (event.srcElement.nodeName != 'SPAN') return;
15 contextMenu.style.left = event.pageX + 'px';
16 contextMenu.style.top = event.pageY + 'px';
17 contextMenu.style.visibility = 'visible';
18 contextMenu.node = this;
0019 btnCopyValue.style.display = valueEnabled ? 'block' : 'none';
20+ event.preventDefault();
21 event.stopPropagation();
22 };
23}
2425+function close(event) {
26 contextMenu.style.visibility = 'hidden';
27+ event.stopPropagation();
28}
2930contextMenu.onmouseleave = close;
31000000032btnCopyHex.onclick = function (event) {
033 navigator.clipboard.writeText(contextMenu.node.asn1.toHexString('byte'));
34+ close(event);
35};
3637btnCopyB64.onclick = function (event) {
38 event.stopPropagation();
39 navigator.clipboard.writeText(contextMenu.node.asn1.toB64String());
40+ close(event);
41};
4243btnCopyTree.onclick = function (event) {
44 event.stopPropagation();
45 navigator.clipboard.writeText(contextMenu.node.asn1.toPrettyString());
46+ close(event);
47};
4849btnCopyValue.onclick = function (event) {
50 event.stopPropagation();
51 navigator.clipboard.writeText(contextMenu.node.asn1.content());
52+ close(event);
53};
+9-6
defs.js
···1// ASN.1 RFC definitions matcher
2-// Copyright (c) 2023-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
···29 def = Object.assign({}, def);
30 def.type = Defs.searchType(name).type;
31 }
32- if (def?.type?.name == 'CHOICE') {
33- for (let c of def.type.content) {
34 if (tn != c.type.name && tn != c.name)
35 c = translate(c);
36 if (tn == c.type.name || tn == c.name) {
37 def = Object.assign({}, def);
038 def.type = c.type.name ? c.type : c;
39 break;
40 }
···90 type = def.content[0];
91 else {
92 let tn = subval.typeName().replaceAll('_', ' ');
93- while (true) {
94 type = def.content[j++];
95 if (!type || typeof type != 'object') break;
96 if (type?.type?.type)
···127Defs.RFC = rfcdef;
128129Defs.commonTypes = [
130- [ 'X.509 certificate', '1.3.6.1.5.5.7.0.18', 'Certificate' ],
131 [ 'X.509 public key info', '1.3.6.1.5.5.7.0.18', 'SubjectPublicKeyInfo' ],
0132 [ 'CMS / PKCS#7 envelope', '1.2.840.113549.1.9.16.0.14', 'ContentInfo' ],
133 [ 'PKCS#1 RSA private key', '1.2.840.113549.1.1.0.1', 'RSAPrivateKey' ],
134 [ 'PKCS#8 encrypted private key', '1.2.840.113549.1.8.1.1', 'EncryptedPrivateKeyInfo' ],
135 [ 'PKCS#8 private key', '1.2.840.113549.1.8.1.1', 'PrivateKeyInfo' ],
136 [ 'PKCS#10 certification request', '1.2.840.113549.1.10.1.1', 'CertificationRequest' ],
137 [ 'CMP PKI Message', '1.3.6.1.5.5.7.0.16', 'PKIMessage' ],
0138].map(arr => ({ description: arr[0], ...Defs.moduleAndType(rfcdef[arr[1]], arr[2]) }));
···1// ASN.1 RFC definitions matcher
2+// Copyright (c) 2023 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
···29 def = Object.assign({}, def);
30 def.type = Defs.searchType(name).type;
31 }
32+ if (def?.name == 'CHOICE' || def?.type?.name == 'CHOICE') {
33+ for (let c of def.content ?? def.type.content) {
34 if (tn != c.type.name && tn != c.name)
35 c = translate(c);
36 if (tn == c.type.name || tn == c.name) {
37 def = Object.assign({}, def);
38+ if (c.id) def.id = c.id;
39 def.type = c.type.name ? c.type : c;
40 break;
41 }
···91 type = def.content[0];
92 else {
93 let tn = subval.typeName().replaceAll('_', ' ');
94+ for (;;) {
95 type = def.content[j++];
96 if (!type || typeof type != 'object') break;
97 if (type?.type?.type)
···128Defs.RFC = rfcdef;
129130Defs.commonTypes = [
131+ [ 'X.509 certificate', '1.3.6.1.5.5.7.0.18', 'Certificate' ],
132 [ 'X.509 public key info', '1.3.6.1.5.5.7.0.18', 'SubjectPublicKeyInfo' ],
133+ [ 'X.509 certificate revocation list', '1.3.6.1.5.5.7.0.18', 'CertificateList' ],
134 [ 'CMS / PKCS#7 envelope', '1.2.840.113549.1.9.16.0.14', 'ContentInfo' ],
135 [ 'PKCS#1 RSA private key', '1.2.840.113549.1.1.0.1', 'RSAPrivateKey' ],
136 [ 'PKCS#8 encrypted private key', '1.2.840.113549.1.8.1.1', 'EncryptedPrivateKeyInfo' ],
137 [ 'PKCS#8 private key', '1.2.840.113549.1.8.1.1', 'PrivateKeyInfo' ],
138 [ 'PKCS#10 certification request', '1.2.840.113549.1.10.1.1', 'CertificationRequest' ],
139 [ 'CMP PKI Message', '1.3.6.1.5.5.7.0.16', 'PKIMessage' ],
140+ [ 'LDAP Message', '1.3.6.1.1.18', 'LDAPMessage' ],
141].map(arr => ({ description: arr[0], ...Defs.moduleAndType(rfcdef[arr[1]], arr[2]) }));
+27-11
dom.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
···24 ellipsis: '\u2026',
25 tag: function (tagName, className, text) {
26 let t = document.createElement(tagName);
27- t.className = className;
28 if (text) t.innerText = text;
29 return t;
30 },
···56 toDOM(spaces) {
57 spaces = spaces || '';
58 let isOID = (typeof oids === 'object') && (this.tag.isUniversal() && (this.tag.tagNumber == 0x06) || (this.tag.tagNumber == 0x0D));
59- let node = DOM.tag('div', 'node');
60 node.asn1 = this;
61 let head = DOM.tag('span', 'head');
62- head.appendChild(DOM.tag('span', 'spaces', spaces));
63 const typeName = this.typeName().replace(/_/g, ' ');
64 if (this.def) {
65 if (this.def.id) {
···111 content = content.replace(/</g, '<');
112 content = content.replace(/\n/g, '<br>');
113 }
114- node.appendChild(head);
115- this.node = node;
0000000000000000116 this.head = head;
117 let value = DOM.tag('div', 'value');
118 let s = 'Offset: ' + this.stream.pos + '<br>';
···135 }
136 }
137 value.innerHTML = s;
138- node.appendChild(value);
139- let sub = DOM.tag('div', 'sub');
140 if (this.sub !== null) {
00141 spaces += '\xA0 ';
142 for (let i = 0, max = this.sub.length; i < max; ++i)
143 sub.appendChild(this.sub[i].toDOM(spaces));
144 }
145- node.appendChild(sub);
146 bindContextMenu(node);
147 return node;
148 }
···169 this.head.onmouseover = function () { this.hexNode.className = 'hexCurrent'; };
170 this.head.onmouseout = function () { this.hexNode.className = 'hex'; };
171 node.asn1 = this;
172- node.onmouseover = function () {
173 let current = !root.selected;
174 if (current) {
175 root.selected = this.asn1;
176 this.className = 'hexCurrent';
177 }
178 this.asn1.fakeHover(current);
0179 };
180 node.onmouseout = function () {
181 let current = (root.selected == this.asn1);
···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
···24 ellipsis: '\u2026',
25 tag: function (tagName, className, text) {
26 let t = document.createElement(tagName);
27+ if (className) t.className = className;
28 if (text) t.innerText = text;
29 return t;
30 },
···56 toDOM(spaces) {
57 spaces = spaces || '';
58 let isOID = (typeof oids === 'object') && (this.tag.isUniversal() && (this.tag.tagNumber == 0x06) || (this.tag.tagNumber == 0x0D));
59+ let node = DOM.tag('li');
60 node.asn1 = this;
61 let head = DOM.tag('span', 'head');
062 const typeName = this.typeName().replace(/_/g, ' ');
63 if (this.def) {
64 if (this.def.id) {
···110 content = content.replace(/</g, '<');
111 content = content.replace(/\n/g, '<br>');
112 }
113+ // add the li and details section for this node
114+ let contentNode;
115+ let childNode;
116+ if (this.sub !== null) {
117+ let details = DOM.tag('details');
118+ details.setAttribute('open', '');
119+ node.appendChild(details);
120+ let summary = DOM.tag('summary', 'node');
121+ details.appendChild(summary);
122+ summary.appendChild(head);
123+ contentNode = summary;
124+ childNode = details;
125+ } else {
126+ contentNode = node;
127+ contentNode.classList.add('node');
128+ contentNode.appendChild(head);
129+ }
130+ this.node = contentNode;
131 this.head = head;
132 let value = DOM.tag('div', 'value');
133 let s = 'Offset: ' + this.stream.pos + '<br>';
···150 }
151 }
152 value.innerHTML = s;
153+ contentNode.appendChild(value);
0154 if (this.sub !== null) {
155+ let sub = DOM.tag('ul');
156+ childNode.appendChild(sub);
157 spaces += '\xA0 ';
158 for (let i = 0, max = this.sub.length; i < max; ++i)
159 sub.appendChild(this.sub[i].toDOM(spaces));
160 }
0161 bindContextMenu(node);
162 return node;
163 }
···184 this.head.onmouseover = function () { this.hexNode.className = 'hexCurrent'; };
185 this.head.onmouseout = function () { this.hexNode.className = 'hex'; };
186 node.asn1 = this;
187+ node.onmouseover = function (event) {
188 let current = !root.selected;
189 if (current) {
190 root.selected = this.asn1;
191 this.className = 'hexCurrent';
192 }
193 this.asn1.fakeHover(current);
194+ event.stopPropagation();
195 };
196 node.onmouseout = function () {
197 let current = (root.selected == this.asn1);
+4
dumpASN1.js
···1#!/usr/bin/env node
200003import * as fs from 'node:fs';
4import { Base64 } from './base64.js';
5import { ASN1 } from './asn1.js';
···1#!/usr/bin/env node
23+// usage:
4+// ./dumpASN1.js filename
5+// ./dumpASN1.js data:base64,MDMCAQFjLgQACgEACgEAAgEAAgEAAQEAoA+jDQQFTnRWZXIEBAEAAAAwCgQITmV0bG9nb24===
6+7import * as fs from 'node:fs';
8import { Base64 } from './base64.js';
9import { ASN1 } from './asn1.js';
+9
examples/cms-password.p7m
···000000000
···1+This is a PKCS#7/CMS encrypted with passwod.
2+$ echo content | openssl cms -encrypt -pwri_password test -aes256 -outform pem -out examples/cms-password.p7m
3+-----BEGIN CMS-----
4+MIHYBgkqhkiG9w0BBwOggcowgccCAQMxgYOjgYACAQCgGwYJKoZIhvcNAQUMMA4E
5+CED/DSxXMtH6AgIIADAsBgsqhkiG9w0BCRADCTAdBglghkgBZQMEASoEEDIQbJMC
6+Sfb3LpwHduj/meQEMKwrwq5M4V0stztm6OUTAsFY2zKDY20SApwSEeEcAh9TM42E
7+1palnHeqHTBpC8pIpjA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBByt+scPrdM
8+giR7WUOJyB3hgBDcD3UDMtZSep8X/3yy1/Yq
9+-----END CMS-----
+12
examples/crl-rfc5280.b64
···000000000000
···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+====
+8
examples/ldapmessage.b64
···00000000
···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}
47-function decode(der, offset) {
48 offset = offset || 0;
49 try {
50 const asn1 = ASN1DOM.decode(der, offset);
···103 text(tree, e);
104 }
105}
106-function decodeText(val) {
107 try {
108 let der = reHex.test(val) ? Hex.decode(val) : Base64.unarmor(val);
109 decode(der);
···112 dump.innerHTML = '';
113 }
114}
115-function decodeBinaryString(str) {
116 let der;
117 try {
118 if (reHex.test(str)) der = Hex.decode(str);
···125 }
126}
127// set up buttons
128-id('butDecode').onclick = function () {
129- decodeText(area.value);
0000000000000000000000000130};
131-id('butClear').onclick = function () {
132- area.value = '';
133- file.value = '';
134- tree.innerHTML = '';
135- dump.innerHTML = '';
136- selectDefs.innerHTML = '';
137- hash = window.location.hash = '';
138-};
139-id('butExample').onclick = function () {
140- console.log('Loading example:', examples.value);
141- let request = new XMLHttpRequest();
142- request.open('GET', 'examples/' + examples.value, true);
143- request.onreadystatechange = function () {
144- if (this.readyState !== 4) return;
145- if (this.status >= 200 && this.status < 400) {
146- area.value = this.responseText;
147- decodeText(this.responseText);
148- } else {
149- console.log('Error loading example.');
150- }
151- };
152- request.send();
153-};
154-// set dark theme depending on OS settings
155-function setTheme() {
156- let storedTheme = localStorage.getItem('theme');
157- let theme = 'os';
158- if (storedTheme)
159- theme = storedTheme;
160- selectTheme.value = theme;
161- if (theme == 'os') {
162- let prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
163- theme = prefersDarkScheme.matches ? 'dark': 'light';
164- }
165- if (theme == 'dark') {
166- const css1 = id('theme-base');
167- const css2 = css1.cloneNode();
168- css2.id = 'theme-override';
169- css2.href = 'index-' + theme + '.css';
170- css1.parentElement.appendChild(css2);
171- } else {
172- const css2 = id('theme-override');
173- if (css2) css2.remove();
174- }
175}
176-setTheme();
177-selectTheme.addEventListener('change', function () {
178- localStorage.setItem('theme', selectTheme.value);
179- setTheme();
180-});
181// this is only used if window.FileReader
182function read(f) {
183 area.value = ''; // clear text area, will get b64 content
···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}
50+export function decode(der, offset) {
51 offset = offset || 0;
52 try {
53 const asn1 = ASN1DOM.decode(der, offset);
···106 text(tree, e);
107 }
108}
109+export function decodeText(val) {
110 try {
111 let der = reHex.test(val) ? Hex.decode(val) : Base64.unarmor(val);
112 decode(der);
···115 dump.innerHTML = '';
116 }
117}
118+export function decodeBinaryString(str) {
119 let der;
120 try {
121 if (reHex.test(str)) der = Hex.decode(str);
···128 }
129}
130// set up buttons
131+const butClickHandlers = {
132+ butDecode: () => {
133+ decodeText(area.value);
134+ },
135+ butClear: () => {
136+ area.value = '';
137+ file.value = '';
138+ tree.innerHTML = '';
139+ dump.innerHTML = '';
140+ selectDefs.innerHTML = '';
141+ hash = window.location.hash = '';
142+ },
143+ butExample: () => {
144+ console.log('Loading example:', examples.value);
145+ let request = new XMLHttpRequest();
146+ request.open('GET', 'examples/' + examples.value, true);
147+ request.onreadystatechange = function () {
148+ if (this.readyState !== 4) return;
149+ if (this.status >= 200 && this.status < 400) {
150+ area.value = this.responseText;
151+ decodeText(this.responseText);
152+ } else {
153+ console.log('Error loading example.');
154+ }
155+ };
156+ request.send();
157+ },
158};
159+for (const [name, onClick] of Object.entries(butClickHandlers)) {
160+ let elem = id(name);
161+ if (elem)
162+ elem.onclick = onClick;
0000000000000000000000000000000000000000163}
00000164// this is only used if window.FileReader
165function read(f) {
166 area.value = ''; // clear text area, will get b64 content
+15-9
int10.js
···1// Big integer base-10 printing library
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-let max = 10000000000000; // biggest 10^n integer that can still fit 2^53 when multiplied by 256
01718export class Int10 {
19 /**
···2627 /**
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) {
033 // assert(m <= 256)
034 let b = this.buf,
35 l = b.length,
36 i, t;
···7172 /**
73 * Convert to decimal string representation.
74- * @param {*} base - optional value, only value accepted is 10
075 */
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)
···86 /**
87 * Convert to Number value representation.
88 * Will probably overflow 2^53 and thus become approximate.
089 */
90 valueOf() {
91 let b = this.buf,
···9798 /**
99 * Return value as a simple Number (if it is <= 10000000000000), or return this.
0100 */
101 simplify() {
102 let b = this.buf;
···1// Big integer base-10 printing library
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.
1516+/** Biggest 10^n integer that can still fit 2^53 when multiplied by 256. */
17+const max = 10000000000000;
1819export class Int10 {
20 /**
···2728 /**
29 * Multiply value by m and add c.
30+ * @param {number} m - multiplier, must be 0<m<=256
31+ * @param {number} c - value to add, must be c>=0
32 */
33 mulAdd(m, c) {
34+ // assert(m > 0)
35 // assert(m <= 256)
36+ // assert(c >= 0)
37 let b = this.buf,
38 l = b.length,
39 i, t;
···7475 /**
76 * Convert to decimal string representation.
77+ * @param {number} [base=10] - optional value, only value accepted is 10
78+ * @returns {string} The decimal string representation.
79 */
80+ toString(base = 10) {
81+ if (base != 10)
82+ throw new Error('only base 10 is supported');
83 let b = this.buf,
84 s = b[b.length - 1].toString();
85 for (let i = b.length - 2; i >= 0; --i)
···90 /**
91 * Convert to Number value representation.
92 * Will probably overflow 2^53 and thus become approximate.
93+ * @returns {number} The numeric value.
94 */
95 valueOf() {
96 let b = this.buf,
···102103 /**
104 * Return value as a simple Number (if it is <= 10000000000000), or return this.
105+ * @returns {number | Int10} The simplified value.
106 */
107 simplify() {
108 let b = this.buf;
···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 }