···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
+3-2
LICENSE
···1-ASN.1 JavaScript decoder Copyright (c) 2008-2018 Lapo Luchini <lapo@lapo.it>
0023Permission to use, copy, modify, and/or distribute this software for any
4purpose with or without fee is hereby granted, provided that the above
···11WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14-
···1+ISC License
2+3+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
···13WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
0
+84-5
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).
000000000000000000000000000000000000000000000000000000000000000000000000078ISC license
9-----------
1011-ASN.1 JavaScript decoder Copyright (c) 2008-2018 Lapo Luchini <lapo@lapo.it>
1213Permission 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.
14···21- BMPString support added by [Felipe Gasper](https://github.com/FGasper)
22- extended tag support added by [Pรฉter Budai](https://www.peterbudai.eu/)
23- patches by [Gergely Nagy](https://github.com/ngg)
0002425links
26-----
2728-- [official website](https://lapo.it/asn1js/)
29-- [InDefero tracker](http://idf.lapo.it/p/asn1js/)
0030- [GitHub mirror](https://github.com/lapo-luchini/asn1js)
31-- [Ohloh code stats](https://www.ohloh.net/p/asn1js)
0
···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`.
7+8+Usage with `nodejs`
9+-------------------
10+11+This package can be installed with either npm or yarn via the following commands:
12+13+```sh
14+npm install @lapo/asn1js
15+# or other tools
16+pnpm install @lapo/asn1js
17+yarn add @lapo/asn1js
18+```
19+20+You can import the classes like this:
21+22+```js
23+import { ASN1 } from '@lapo/asn1js';
24+```
25+26+A submodule of this package can also be imported:
27+28+```js
29+import { Hex } from '@lapo/asn1js/hex.js';
30+```
31+32+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+```
52+53+Usage on the web
54+--------------------
55+56+Can be [tested on JSFiddle](https://jsfiddle.net/lapo/y6t2wo7q/).
57+58+```html
59+<script>
60+import { ASN1 } from 'https://unpkg.com/@lapo/asn1js@2.0.0/asn1.js';
61+import { Hex } from 'https://unpkg.com/@lapo/asn1js@2.0.0/hex.js';
62+63+document.body.innerText = ASN1.decode(Hex.decode('06032B6570')).content();
64+</script>
65+```
66+67+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+```
8081ISC 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···94- BMPString support added by [Felipe Gasper](https://github.com/FGasper)
95- extended tag support added by [Pรฉter Budai](https://www.peterbudai.eu/)
96- patches by [Gergely Nagy](https://github.com/ngg)
97+- Relative OID support added by [Mistial Developer](https://github.com/mistial-dev)
98+- dark mode and other UI improvements by [Oliver Burgmaier](https://github.com/olibu/)
99+- patches by [Nicolai Sรธborg](https://github.com/NicolaiSoeborg)
100101links
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)
+802-432
asn1.js
···1// ASN.1 JavaScript decoder
2-// Copyright (c) 2008-2018 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-(function (undefined) {
17-"use strict";
1819-var Int10 = (typeof module !== 'undefined') ? require('./int10.js') : window.Int10,
20- oids = (typeof module !== 'undefined') ? require('./oids.js') : window.oids,
21- ellipsis = "\u2026",
22- 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-5]\d)?)?$/,
23- 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-5]\d)?)?$/;
000000000000000000002400000025function stringCut(str, len) {
26 if (str.length > len)
27 str = str.substring(0, len) + ellipsis;
28 return str;
29}
3031-function Stream(enc, pos) {
32- if (enc instanceof Stream) {
33- this.enc = enc.enc;
34- this.pos = enc.pos;
35- } else {
36- // enc should be an array or a binary string
37- this.enc = enc;
38- this.pos = pos;
00039 }
40}
41-Stream.prototype.get = function (pos) {
42- if (pos === undefined)
43- pos = this.pos++;
44- if (pos >= this.enc.length)
45- throw 'Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length;
46- return (typeof this.enc == "string") ? this.enc.charCodeAt(pos) : this.enc[pos];
47-};
48-Stream.prototype.hexDigits = "0123456789ABCDEF";
49-Stream.prototype.hexByte = function (b) {
50- return this.hexDigits.charAt((b >> 4) & 0xF) + this.hexDigits.charAt(b & 0xF);
51-};
52-Stream.prototype.hexDump = function (start, end, raw) {
53- var s = "";
54- for (var i = start; i < end; ++i) {
55- s += this.hexByte(this.get(i));
56- if (raw !== true)
57- switch (i & 0xF) {
58- case 0x7: s += " "; break;
59- case 0xF: s += "\n"; break;
60- default: s += " ";
61- }
00000000000000000000000000000062 }
63- return s;
64-};
65-var b64Safe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
66-Stream.prototype.b64Dump = function (start, end) {
67- var extra = (end - start) % 3,
68- s = '',
69- i, c;
70- for (i = start; i + 2 < end; i += 3) {
71- c = this.get(i) << 16 | this.get(i + 1) << 8 | this.get(i + 2);
72- s += b64Safe.charAt(c >> 18 & 0x3F);
73- s += b64Safe.charAt(c >> 12 & 0x3F);
74- s += b64Safe.charAt(c >> 6 & 0x3F);
75- s += b64Safe.charAt(c & 0x3F);
00000000076 }
77- if (extra > 0) {
78- c = this.get(i) << 16;
79- if (extra > 1) c |= this.get(i + 1) << 8;
80- s += b64Safe.charAt(c >> 18 & 0x3F);
81- s += b64Safe.charAt(c >> 12 & 0x3F);
82- if (extra == 2) s += b64Safe.charAt(c >> 6 & 0x3F);
0000000000000000000000083 }
84- return s;
85-};
86-Stream.prototype.isASCII = function (start, end) {
87- for (var i = start; i < end; ++i) {
88- var c = this.get(i);
89- if (c < 32 || c > 176)
90- return false;
000000091 }
92- return true;
93-};
94-Stream.prototype.parseStringISO = function (start, end) {
95- var s = "";
96- for (var i = start; i < end; ++i)
97- s += String.fromCharCode(this.get(i));
98- return s;
99-};
100-Stream.prototype.parseStringUTF = function (start, end) {
101- var s = "";
102- for (var i = start; i < end; ) {
103- var c = this.get(i++);
104- if (c < 128)
105- s += String.fromCharCode(c);
106- else if ((c > 191) && (c < 224))
107- s += String.fromCharCode(((c & 0x1F) << 6) | (this.get(i++) & 0x3F));
108- else
109- s += String.fromCharCode(((c & 0x0F) << 12) | ((this.get(i++) & 0x3F) << 6) | (this.get(i++) & 0x3F));
110 }
111- return s;
112-};
113-Stream.prototype.parseStringBMP = function (start, end) {
114- var str = "", hi, lo;
115- for (var i = start; i < end; ) {
116- hi = this.get(i++);
117- lo = this.get(i++);
118- str += String.fromCharCode((hi << 8) | lo);
00000000000000000000119 }
120- return str;
121-};
122-Stream.prototype.parseTime = function (start, end, shortYear) {
123- var s = this.parseStringISO(start, end),
124- m = (shortYear ? reTimeS : reTimeL).exec(s);
125- if (!m)
126- return "Unrecognized time: " + s;
127- if (shortYear) {
128- // to avoid querying the timer, use the fixed range [1970, 2069]
129- // it will conform with ITU X.400 [-10, +40] sliding window until 2030
130- m[1] = +m[1];
131- m[1] += (m[1] < 70) ? 2000 : 1900;
000000000000000000000000000000000000132 }
133- s = m[1] + "-" + m[2] + "-" + m[3] + " " + m[4];
134- if (m[5]) {
135- s += ":" + m[5];
136- if (m[6]) {
137- s += ":" + m[6];
138- if (m[7])
139- s += "." + m[7];
0000000140 }
0141 }
142- if (m[8]) {
143- s += " UTC";
144- if (m[8] != 'Z') {
145- s += m[8];
00000000000000000000000000146 if (m[9])
147- s += ":" + m[9];
148 }
0149 }
150- return s;
151-};
152-Stream.prototype.parseInteger = function (start, end) {
153- var v = this.get(start),
154- neg = (v > 127),
155- pad = neg ? 255 : 0,
156- len,
157- s = '';
158- // skip unuseful bits (not allowed in DER)
159- while (v == pad && ++start < end)
160- v = this.get(start);
161- len = end - start;
162- if (len === 0)
163- return neg ? '-1' : '0';
164- // show bit length of huge integers
165- if (len > 4) {
166- s = v;
167- len <<= 3;
168- while (((s ^ pad) & 0x80) == 0) {
169- s <<= 1;
170- --len;
000000171 }
172- s = "(" + len + " bit)\n";
173- }
174- // decode the integer
175- if (neg) v = v - 256;
176- var n = new Int10(v);
177- for (var i = start + 1; i < end; ++i)
178- n.mulAdd(256, this.get(i));
179- return s + n.toString();
180-};
181-Stream.prototype.parseBitString = function (start, end, maxLength) {
182- var unusedBit = this.get(start),
183- lenBit = ((end - start - 1) << 3) - unusedBit,
184- intro = "(" + lenBit + " bit)\n",
185- s = "";
186- for (var i = start + 1; i < end; ++i) {
187- var b = this.get(i),
188- skip = (i == end - 1) ? unusedBit : 0;
189- for (var j = 7; j >= skip; --j)
190- s += (b >> j) & 1 ? "1" : "0";
191- if (s.length > maxLength)
192- return intro + stringCut(s, maxLength);
193 }
194- return intro + s;
195-};
196-Stream.prototype.parseOctetString = function (start, end, maxLength) {
197- if (this.isASCII(start, end))
198- return stringCut(this.parseStringISO(start, end), maxLength);
199- var len = end - start,
200- s = "(" + len + " byte)\n";
201- maxLength /= 2; // we work in bytes
202- if (len > maxLength)
203- end = start + maxLength;
204- for (var i = start; i < end; ++i)
205- s += this.hexByte(this.get(i));
206- if (len > maxLength)
207- s += ellipsis;
208- return s;
209-};
210-Stream.prototype.parseOID = function (start, end, maxLength) {
211- var s = '',
212- n = new Int10(),
213- bits = 0;
214- for (var i = start; i < end; ++i) {
215- var v = this.get(i);
216- n.mulAdd(128, v & 0x7F);
217- bits += 7;
218- if (!(v & 0x80)) { // finished
219- if (s === '') {
220- n = n.simplify();
221- if (n instanceof Int10) {
222- n.sub(80);
223- s = "2." + n.toString();
224- } else {
225- var m = n < 80 ? n < 40 ? 0 : 1 : 2;
226- s = m + "." + (n - m * 40);
227- }
228- } else
229- s += "." + n.toString();
230 if (s.length > maxLength)
231- return stringCut(s, maxLength);
232- n = new Int10();
233- bits = 0;
234 }
0235 }
236- if (bits > 0)
237- s += ".incomplete";
238- if (typeof oids === 'object') {
239- var oid = oids[s];
240- if (oid) {
241- if (oid.d) s += "\n" + oid.d;
242- if (oid.c) s += "\n" + oid.c;
243- if (oid.w) s += "\n(warning!)";
0000000244 }
0000000000245 }
246- return s;
247-};
248249-function ASN1(stream, header, length, tag, sub) {
250- if (!(tag instanceof ASN1Tag)) throw 'Invalid tag value.';
251- this.stream = stream;
252- this.header = header;
253- this.length = length;
254- this.tag = tag;
255- this.sub = sub;
0000000000000000000000000000000000000000000000000000256}
257-ASN1.prototype.typeName = function () {
258- switch (this.tag.tagClass) {
259- case 0: // universal
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000260 switch (this.tag.tagNumber) {
261- case 0x00: return "EOC";
262- case 0x01: return "BOOLEAN";
263- case 0x02: return "INTEGER";
264- case 0x03: return "BIT_STRING";
265- case 0x04: return "OCTET_STRING";
266- case 0x05: return "NULL";
267- case 0x06: return "OBJECT_IDENTIFIER";
268- case 0x07: return "ObjectDescriptor";
269- case 0x08: return "EXTERNAL";
270- case 0x09: return "REAL";
271- case 0x0A: return "ENUMERATED";
272- case 0x0B: return "EMBEDDED_PDV";
273- case 0x0C: return "UTF8String";
274- case 0x10: return "SEQUENCE";
275- case 0x11: return "SET";
276- case 0x12: return "NumericString";
277- case 0x13: return "PrintableString"; // ASCII subset
278- case 0x14: return "TeletexString"; // aka T61String
279- case 0x15: return "VideotexString";
280- case 0x16: return "IA5String"; // ASCII
281- case 0x17: return "UTCTime";
282- case 0x18: return "GeneralizedTime";
283- case 0x19: return "GraphicString";
284- case 0x1A: return "VisibleString"; // ASCII subset
285- case 0x1B: return "GeneralString";
286- case 0x1C: return "UniversalString";
287- case 0x1E: return "BMPString";
288 }
289- return "Universal_" + this.tag.tagNumber.toString();
290- case 1: return "Application_" + this.tag.tagNumber.toString();
291- case 2: return "[" + this.tag.tagNumber.toString() + "]"; // Context
292- case 3: return "Private_" + this.tag.tagNumber.toString();
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000293 }
294-};
295-ASN1.prototype.content = function (maxLength) { // a preview of the content (intended for humans)
296- if (this.tag === undefined)
297- return null;
298- if (maxLength === undefined)
299- maxLength = Infinity;
300- var content = this.posContent(),
301- len = Math.abs(this.length);
302- if (!this.tag.isUniversal()) {
303- if (this.sub !== null)
304- return "(" + this.sub.length + " elem)";
305- return this.stream.parseOctetString(content, content + len, maxLength);
306 }
307- switch (this.tag.tagNumber) {
308- case 0x01: // BOOLEAN
309- return (this.stream.get(content) === 0) ? "false" : "true";
310- case 0x02: // INTEGER
311- return this.stream.parseInteger(content, content + len);
312- case 0x03: // BIT_STRING
313- return this.sub ? "(" + this.sub.length + " elem)" :
314- this.stream.parseBitString(content, content + len, maxLength);
315- case 0x04: // OCTET_STRING
316- return this.sub ? "(" + this.sub.length + " elem)" :
317- this.stream.parseOctetString(content, content + len, maxLength);
318- //case 0x05: // NULL
319- case 0x06: // OBJECT_IDENTIFIER
320- return this.stream.parseOID(content, content + len, maxLength);
321- //case 0x07: // ObjectDescriptor
322- //case 0x08: // EXTERNAL
323- //case 0x09: // REAL
324- //case 0x0A: // ENUMERATED
325- //case 0x0B: // EMBEDDED_PDV
326- case 0x10: // SEQUENCE
327- case 0x11: // SET
328- if (this.sub !== null)
329- return "(" + this.sub.length + " elem)";
330- else
331- return "(no elem)";
332- case 0x0C: // UTF8String
333- return stringCut(this.stream.parseStringUTF(content, content + len), maxLength);
334- case 0x12: // NumericString
335- case 0x13: // PrintableString
336- case 0x14: // TeletexString
337- case 0x15: // VideotexString
338- case 0x16: // IA5String
339- //case 0x19: // GraphicString
340- case 0x1A: // VisibleString
341- //case 0x1B: // GeneralString
342- //case 0x1C: // UniversalString
343- return stringCut(this.stream.parseStringISO(content, content + len), maxLength);
344- case 0x1E: // BMPString
345- return stringCut(this.stream.parseStringBMP(content, content + len), maxLength);
346- case 0x17: // UTCTime
347- case 0x18: // GeneralizedTime
348- return this.stream.parseTime(content, content + len, (this.tag.tagNumber == 0x17));
349 }
350- return null;
351-};
352-ASN1.prototype.toString = function () {
353- return this.typeName() + "@" + this.stream.pos + "[header:" + this.header + ",length:" + this.length + ",sub:" + ((this.sub === null) ? 'null' : this.sub.length) + "]";
354-};
355-ASN1.prototype.toPrettyString = function (indent) {
356- if (indent === undefined) indent = '';
357- var s = indent + this.typeName() + " @" + this.stream.pos;
358- if (this.length >= 0)
359- s += "+";
360- s += this.length;
361- if (this.tag.tagConstructed)
362- s += " (constructed)";
363- else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null))
364- s += " (encapsulates)";
365- var content = this.content();
366- if (content)
367- s += ": " + content.replace(/\n/g, '|');
368- s += "\n";
369- if (this.sub !== null) {
370- indent += ' ';
371- for (var i = 0, max = this.sub.length; i < max; ++i)
372- s += this.sub[i].toPrettyString(indent);
373 }
374- return s;
375-};
376-ASN1.prototype.posStart = function () {
377- return this.stream.pos;
378-};
379-ASN1.prototype.posContent = function () {
380- return this.stream.pos + this.header;
381-};
382-ASN1.prototype.posEnd = function () {
383- return this.stream.pos + this.header + Math.abs(this.length);
384-};
385-ASN1.prototype.toHexString = function () {
386- return this.stream.hexDump(this.posStart(), this.posEnd(), true);
387-};
388-ASN1.prototype.toB64String = function () {
389- return this.stream.b64Dump(this.posStart(), this.posEnd());
390-};
391-ASN1.decodeLength = function (stream) {
392- var buf = stream.get(),
393- len = buf & 0x7F;
394- if (len == buf)
395- return len;
396- if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways
397- throw "Length over 48 bits not supported at position " + (stream.pos - 1);
398- if (len === 0)
399- return null; // undefined
400- buf = 0;
401- for (var i = 0; i < len; ++i)
402- buf = (buf * 256) + stream.get();
403- return buf;
404-};
405-function ASN1Tag(stream) {
406- var buf = stream.get();
407- this.tagClass = buf >> 6;
408- this.tagConstructed = ((buf & 0x20) !== 0);
409- this.tagNumber = buf & 0x1F;
410- if (this.tagNumber == 0x1F) { // long tag
411- var n = new Int10();
412- do {
413- buf = stream.get();
414- n.mulAdd(128, buf & 0x7F);
415- } while (buf & 0x80);
416- this.tagNumber = n.simplify();
417 }
418-}
419-ASN1Tag.prototype.isUniversal = function () {
420- return this.tagClass === 0x00;
421-};
422-ASN1Tag.prototype.isEOC = function () {
423- return this.tagClass === 0x00 && this.tagNumber === 0x00;
424-};
425-ASN1.decode = function (stream) {
426- if (!(stream instanceof Stream))
427- stream = new Stream(stream, 0);
428- var streamStart = new Stream(stream),
429- tag = new ASN1Tag(stream),
430- len = ASN1.decodeLength(stream),
431- start = stream.pos,
432- header = start - streamStart.pos,
433- sub = null,
434- getSub = function () {
435- sub = [];
436- if (len !== null) {
437- // definite length
438- var end = start + len;
439- if (end > stream.enc.length)
440- throw 'Container at offset ' + start + ' has a length of ' + len + ', which is past the end of the stream';
441- while (stream.pos < end)
442- sub[sub.length] = ASN1.decode(stream);
443- if (stream.pos != end)
444- throw 'Content size is not correct for container at offset ' + start;
445- } else {
446- // undefined length
447- try {
448- for (;;) {
449- var s = ASN1.decode(stream);
450- if (s.tag.isEOC())
451- break;
452- sub[sub.length] = s;
000000000453 }
454- len = start - stream.pos; // undefined lengths are represented as negative values
455- } catch (e) {
456- throw 'Exception while decoding undefined length content at offset ' + start + ': ' + e;
00000000000000000457 }
0000458 }
459- };
460- if (tag.tagConstructed) {
461- // must have valid content
462- getSub();
463- } else if (tag.isUniversal() && ((tag.tagNumber == 0x03) || (tag.tagNumber == 0x04))) {
464- // sometimes BitString and OctetString are used to encapsulate ASN.1
465- try {
466- if (tag.tagNumber == 0x03)
467- if (stream.get() != 0)
468- throw "BIT STRINGs with unused bits cannot encapsulate.";
469- getSub();
470- for (var i = 0; i < sub.length; ++i)
471- if (sub[i].tag.isEOC())
472- throw 'EOC is not supposed to be actual content.';
473- } catch (e) {
474- // but silently ignore when they don't
475- sub = null;
476- //DEBUG console.log('Could not decode structure at ' + start + ':', e);
477 }
000000478 }
479- if (sub === null) {
480- if (len === null)
481- throw "We can't skip over an invalid tag with undefined length at offset " + start;
482- stream.pos = start + Math.abs(len);
483- }
484- return new ASN1(streamStart, header, len, tag, sub);
485-};
486487-// export globals
488-if (typeof module !== 'undefined') { module.exports = ASN1; } else { window.ASN1 = ASN1; }
489-})();
···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.
1516+import { oids } from './oids.js';
01718+const
19+ ellipsis = '\u2026',
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
28+ ['ACEILNORSUYZacegilnorsuyz', 'รฤรรฤนลรลลรรลนรกฤรฉฤฃรญฤบลรณลลรบรฝลบ'], // Acute
29+ ['ACEGHIJOSUWYaceghijosuwy', 'รฤรฤฤครฤดรลรลดลถรขฤรชฤฤฅรฎฤตรดลรปลตลท'], // Circumflex
30+ ['AINOUainou', 'รฤจรรลจรฃฤฉรฑรตลฉ'], // Tilde
31+ ['AEIOUaeiou', 'ฤฤฤชลลชฤฤฤซลลซ'], // Macron
32+ ['AGUagu', 'ฤฤลฌฤฤลญ'], // Breve
33+ ['CEGIZcegz', 'ฤฤฤ ฤฐลปฤฤฤกลผ'], // Dot
34+ ['AEIOUYaeiouy', 'รรรรรลธรครซรฏรถรผรฟ'], // Umlaut or diรฆresis
35+ ['', ''],
36+ ['AUau', 'ร ลฎรฅลฏ'], // Ring
37+ ['CGKLNRSTcklnrst', 'รฤขฤถฤปล ลลลขรงฤทฤผลลลลฃ'], // Cedilla
38+ ['', ''],
39+ ['OUou', 'ลลฐลลฑ'], // Double Acute
40+ ['AEIUaeiu', 'ฤฤฤฎลฒฤ ฤฤฏลณ'], // Ogonek
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+ */
61+function checkPrintable(s) {
62+ let i, v;
63+ for (i = 0; i < s.length; ++i) {
64+ v = s.charCodeAt(i);
65+ if (v < 32 && v != 9 && v != 10 && v != 13) // [\t\r\n] are (kinda) printable
66+ throw new Error('Unprintable character at index ' + i + ' (code ' + s.str.charCodeAt(i) + ')');
67 }
68}
69+70+/**
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 {
75+76+ /**
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 {
86+ 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;
138+ case 0xF: s += '\n'; break;
139+ default: s += ' ';
140+ }
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);
184+ if (c < 32 || c > 176)
185+ return false;
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) };
00000202 }
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;
219+ for (let i = start; i < end; ++i) {
220+ c = this.get(i);
221+ if (c >= 0xA4 && c <= 0xBF)
222+ s += '$ยฅ#ยงยค\0\0ยซ\0\0\0\0ยฐยฑยฒยณรยตยถยทรท\0\0ยปยผยฝยพยฟ'.charAt(c - 0xA4);
223+ else if (c >= 0xE0 && c <= 0xFF)
224+ s += 'โฆรรยชฤฆ\0ฤฒฤฟลรลยบรลฆลลฤธรฆฤรฐฤงฤฑฤณลลรธลรรพลงล\0'.charAt(c - 0xE0);
225+ else if (c >= 0xC0 && c <= 0xCF)
226+ s += merge(c, this.get(++i));
227+ else // using ISO 8859-1 for characters undefined (or equal) in T.61
228+ s += String.fromCharCode(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);
259+ // we could use String.fromCodePoint(cp) but let's be nice to older browsers and use surrogate pairs
260+ cp -= 0x10000;
261+ return String.fromCharCode((cp >> 10) + 0xD800, (cp & 0x3FF) + 0xDC00);
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)
269+ throw new Error('Invalid UTF-8 starting byte: ' + c);
270+ else if (c < 0xE0) // 110xxxxx 10xxxxxx (11 bit)
271+ s += String.fromCharCode(((c & 0x1F) << 6) | ex(this.get(i++)));
272+ else if (c < 0xF0) // 1110xxxx 10xxxxxx 10xxxxxx (16 bit)
273+ s += String.fromCharCode(((c & 0x0F) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++)));
274+ else if (c < 0xF8) // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (21 bit)
275+ s += surrogate(((c & 0x07) << 18) | (ex(this.get(i++)) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++)));
276+ else
277+ throw new Error('Invalid UTF-8 starting byte (since 2003 it is restricted to 4 bytes): ' + c);
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; ) {
292+ hi = this.get(i++);
293+ lo = this.get(i++);
294+ s += String.fromCharCode((hi << 8) | lo);
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);
309+ if (!m)
310+ throw new Error('Unrecognized time: ' + s);
311+ if (shortYear) {
312+ // to avoid querying the timer, use the fixed range [1970, 2069]
313+ // it will conform with ITU X.400 [-10, +40] sliding window until 2030
314+ m[1] = +m[1];
315+ m[1] += (m[1] < 70) ? 2000 : 1900;
316+ }
317+ s = m[1] + '-' + m[2] + '-' + m[3] + ' ' + m[4];
318+ if (m[5]) {
319+ s += ':' + m[5];
320+ if (m[6]) {
321+ s += ':' + m[6];
322+ if (m[7])
323+ s += '.' + m[7];
324+ }
325+ }
326+ if (m[8]) {
327+ s += ' UTC';
328 if (m[9])
329+ s += m[9] + ':' + (m[10] || '00');
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),
342+ 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;
000000000000000367 }
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;
385+ for (let j = 7; j >= skip; --j)
386+ s += (b >> j) & 1 ? '1' : '0';
00000000000000000387 if (s.length > maxLength)
388+ s = stringCut(s, maxLength);
00389 }
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) {
401+ 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 }
00419420+ /**
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 === '') {
441+ if (isRelative) {
442+ s = n.toString();
443+ } 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) {
461+ if (oid.d) s += '\n' + oid.d;
462+ if (oid.c) s += '\n' + oid.c;
463+ if (oid.w) s += '\n(warning!)';
464+ }
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+ }
479}
480+481+function recurse(el, parser, maxLength) {
482+ let avoidRecurse = true;
483+ if (el.tag.tagConstructed && el.sub) {
484+ avoidRecurse = false;
485+ el.sub.forEach(function (e1) {
486+ if (e1.tag.tagClass != el.tag.tagClass || e1.tag.tagNumber != el.tag.tagNumber)
487+ avoidRecurse = true;
488+ });
489+ }
490+ if (avoidRecurse)
491+ return el.stream[parser](el.posContent(), el.posContent() + Math.abs(el.length), maxLength);
492+ let d = { size: 0, str: '' };
493+ el.sub.forEach(function (el) {
494+ let d1 = recurse(el, parser, maxLength - d.str.length);
495+ d.size += d1.size;
496+ d.str += d1.str;
497+ });
498+ return d;
499+}
500+501+class ASN1Tag {
502+ constructor(stream) {
503+ let buf = stream.get();
504+ this.tagClass = buf >> 6;
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() {
517+ return this.tagClass === 0x00;
518+ }
519+ isEOC() {
520+ return this.tagClass === 0x00 && this.tagNumber === 0x00;
521+ }
522+}
523+524+/**
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+ */
528+export 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;
541+ this.header = header;
542+ this.length = length;
543+ this.tag = tag;
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
555+ switch (this.tag.tagNumber) {
556+ case 0x00: return 'EOC';
557+ case 0x01: return 'BOOLEAN';
558+ case 0x02: return 'INTEGER';
559+ case 0x03: return 'BIT_STRING';
560+ case 0x04: return 'OCTET_STRING';
561+ case 0x05: return 'NULL';
562+ case 0x06: return 'OBJECT_IDENTIFIER';
563+ case 0x07: return 'ObjectDescriptor';
564+ case 0x08: return 'EXTERNAL';
565+ case 0x09: return 'REAL';
566+ case 0x0A: return 'ENUMERATED';
567+ case 0x0B: return 'EMBEDDED_PDV';
568+ case 0x0C: return 'UTF8String';
569+ case 0x0D: return 'RELATIVE_OID';
570+ case 0x10: return 'SEQUENCE';
571+ case 0x11: return 'SET';
572+ case 0x12: return 'NumericString';
573+ case 0x13: return 'PrintableString'; // ASCII subset
574+ case 0x14: return 'TeletexString'; // aka T61String
575+ case 0x15: return 'VideotexString';
576+ case 0x16: return 'IA5String'; // ASCII
577+ case 0x17: return 'UTCTime';
578+ case 0x18: return 'GeneralizedTime';
579+ case 0x19: return 'GraphicString';
580+ case 0x1A: return 'VisibleString'; // ASCII subset
581+ case 0x1B: return 'GeneralString';
582+ case 0x1C: return 'UniversalString';
583+ case 0x1E: return 'BMPString';
584+ }
585+ return 'Universal_' + this.tag.tagNumber.toString();
586+ case 1: return 'Application_' + this.tag.tagNumber.toString();
587+ case 2: return '[' + this.tag.tagNumber.toString() + ']'; // Context
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)
606+ return '(' + this.sub.length + ' elem)';
607+ let d1 = this.stream.parseOctetString(content, content + len, maxLength);
608+ return '(' + d1.size + ' byte)\n' + d1.str;
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);
619+ return '(' + d.size + ' bit)\n' + d.str;
620+ }
621+ case 0x04: { // OCTET_STRING
622+ let d = recurse(this, 'parseOctetString', maxLength);
623+ return '(' + d.size + ' byte)\n' + d.str;
00000000000000624 }
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
631+ //case 0x09: // REAL
632+ case 0x0A: // ENUMERATED
633+ return this.stream.parseInteger(content, content + len);
634+ //case 0x0B: // EMBEDDED_PDV
635+ case 0x0D: // RELATIVE-OID
636+ return this.stream.parseRelativeOID(content, content + len, maxLength);
637+ case 0x10: // SEQUENCE
638+ case 0x11: // SET
639+ if (this.sub !== null)
640+ return '(' + this.sub.length + ' elem)';
641+ else
642+ return '(no elem)';
643+ case 0x0C: // UTF8String
644+ return recurse(this, 'parseStringUTF', maxLength).str;
645+ case 0x14: // TeletexString
646+ return recurse(this, 'parseStringT61', maxLength).str;
647+ case 0x12: // NumericString
648+ case 0x13: // PrintableString
649+ case 0x15: // VideotexString
650+ case 0x16: // IA5String
651+ case 0x1A: // VisibleString
652+ case 0x1B: // GeneralString
653+ //case 0x19: // GraphicString
654+ //case 0x1C: // UniversalString
655+ return recurse(this, 'parseStringISO', maxLength).str;
656+ case 0x1E: // BMPString
657+ return recurse(this, 'parseStringBMP', maxLength).str;
658+ case 0x17: // UTCTime
659+ case 0x18: // GeneralizedTime
660+ return this.stream.parseTime(content, content + len, (this.tag.tagNumber == 0x17));
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;
681+ if (this.def) {
682+ if (this.def.id)
683+ s += this.def.id + ' ';
684+ if (this.def.name && this.def.name != this.typeName().replace(/_/g, ' '))
685+ s+= this.def.name + ' ';
686+ if (this.def.mismatch)
687+ s += '[?] ';
688+ }
689+ s += this.typeName() + ' @' + this.stream.pos;
690+ if (this.length >= 0)
691+ s += '+';
692+ s += this.length;
693+ if (this.tag.tagConstructed)
694+ s += ' (constructed)';
695+ else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null))
696+ s += ' (encapsulates)';
697+ let content = this.content();
698+ if (content)
699+ s += ': ' + content.replace(/\n/g, '|');
700+ s += '\n';
701+ if (this.sub !== null) {
702+ indent += ' ';
703+ for (let i = 0, max = this.sub.length; i < max; ++i)
704+ s += this.sub[i].toPrettyString(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;
00000739 }
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);
0000000000000000000000000000000000748 }
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);
000000000000000757 }
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;
00000000000000000000000778 }
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');
791+ if (!(stream instanceof Stream))
792+ stream = new Stream(stream, offset || 0);
793+ let streamStart = new Stream(stream),
794+ tag = new ASN1Tag(stream),
795+ tagLen = stream.pos - streamStart.pos,
796+ len = ASN1.decodeLength(stream),
797+ start = stream.pos,
798+ header = start - streamStart.pos,
799+ sub = null,
800+ getSub = function () {
801+ sub = [];
802+ if (len !== null) {
803+ // definite length
804+ let end = start + len;
805+ if (end > stream.enc.length)
806+ throw new Error('Container at offset ' + start + ' has a length of ' + len + ', which is past the end of the stream');
807+ while (stream.pos < end)
808+ sub[sub.length] = type.decode(stream);
809+ if (stream.pos != end)
810+ throw new Error('Content size is not correct for container at offset ' + start);
811+ } else {
812+ // undefined length
813+ try {
814+ for (;;) {
815+ let s = type.decode(stream);
816+ if (s.tag.isEOC())
817+ break;
818+ sub[sub.length] = s;
819+ }
820+ len = start - stream.pos; // undefined lengths are represented as negative values
821+ } catch (e) {
822+ throw new Error('Exception while decoding undefined length content at offset ' + start + ': ' + e);
823 }
824+ }
825+ };
826+ if (tag.tagConstructed) {
827+ // must have valid content
828+ getSub();
829+ } else if (tag.isUniversal() && ((tag.tagNumber == 0x03) || (tag.tagNumber == 0x04))) {
830+ // sometimes BitString and OctetString are used to encapsulate ASN.1
831+ try {
832+ if (tag.tagNumber == 0x03)
833+ if (stream.get() != 0)
834+ throw new Error('BIT STRINGs with unused bits cannot encapsulate.');
835+ getSub();
836+ for (let s of sub) {
837+ if (s.tag.isEOC())
838+ throw new Error('EOC is not supposed to be actual content.');
839+ try {
840+ s.content();
841+ } catch (e) {
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);
849 }
000000000000000000850 }
851+ if (sub === null) {
852+ if (len === null)
853+ throw new Error("We can't skip over an invalid tag with undefined length at offset " + start);
854+ stream.pos = start + Math.abs(len);
855+ }
856+ return new type(streamStart, header, len, tag, tagLen, sub);
857 }
0000000858859+}
00
+82-76
base64.js
···1// Base64 JavaScript decoder
2-// Copyright (c) 2008-2018 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-(function (undefined) {
17-"use strict";
1819-var Base64 = {},
20- decoder;
2122-Base64.decode = function (a) {
23- var i;
24- if (decoder === undefined) {
25- var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
26- ignore = "= \f\n\r\t\u00A0\u2028\u2029";
27- decoder = [];
28- for (i = 0; i < 64; ++i)
29- decoder[b64.charAt(i)] = i;
30- for (i = 0; i < ignore.length; ++i)
31- decoder[ignore.charAt(i)] = -1;
32- // RFC 3548 URL & file safe encoding
33- decoder['-'] = decoder['+'];
34- decoder['_'] = decoder['/'];
35- }
36- var out = [];
37- var bits = 0, char_count = 0;
38- for (i = 0; i < a.length; ++i) {
39- var c = a.charAt(i);
40- if (c == '=')
000000000000000000000000041 break;
42- c = decoder[c];
43- if (c == -1)
44- continue;
45- if (c === undefined)
46- throw 'Illegal character at offset ' + i;
47- bits |= c;
48- if (++char_count >= 4) {
49- out[out.length] = (bits >> 16);
50- out[out.length] = (bits >> 8) & 0xFF;
51- out[out.length] = bits & 0xFF;
52- bits = 0;
53- char_count = 0;
54- } else {
55- bits <<= 6;
56 }
00057 }
58- switch (char_count) {
59- case 1:
60- throw "Base64 encoding incomplete: at least 2 bits missing";
61- case 2:
62- out[out.length] = (bits >> 10);
63- break;
64- case 3:
65- out[out.length] = (bits >> 16);
66- out[out.length] = (bits >> 8) & 0xFF;
67- break;
68 }
69- return out;
70-};
7172-Base64.pretty = function (str) {
73- // fix padding
74- if (str.length % 4 > 0)
75- str = (str + '===').slice(0, str.length + str.length % 4);
76- // convert RFC 3548 to standard Base64
77- str = str.replace(/-/g, '+').replace(/_/g, '/');
78- // 80 column width
79- return str.replace(/(.{80})/g, '$1\n');
80-};
000008182-Base64.re = /-----BEGIN [^-]+-----([A-Za-z0-9+\/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+\/=\s]+)====/;
83-Base64.unarmor = function (a) {
84- var m = Base64.re.exec(a);
85- if (m) {
86- if (m[1])
87- a = m[1];
88- else if (m[2])
89- a = m[2];
90- else
91- throw "RegExp out of sync";
92- }
93- return Base64.decode(a);
94-};
9596-// export globals
97-if (typeof module !== 'undefined') { module.exports = Base64; } else { window.Base64 = Base64; }
98-})();
···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
···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+const
17+ haveU8 = (typeof Uint8Array == 'function');
1819+let decoder; // populated on first usage
02021+export class Base64 {
22+23+ static decode(a) {
24+ let isString = (typeof a == 'string');
25+ let i;
26+ if (decoder === undefined) {
27+ let b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
28+ ignore = '= \f\n\r\t\u00A0\u2028\u2029';
29+ decoder = [];
30+ for (i = 0; i < 64; ++i)
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+ }
38+ let out = haveU8 ? new Uint8Array(a.length * 3 >> 2) : [];
39+ let bits = 0, char_count = 0, len = 0;
40+ for (i = 0; i < a.length; ++i) {
41+ let c = isString ? a.charCodeAt(i) : a[i];
42+ if (c == 61) // '='.charCodeAt(0)
43+ break;
44+ c = decoder[c];
45+ if (c == -1)
46+ continue;
47+ if (c === undefined)
48+ throw 'Illegal character at offset ' + i;
49+ bits |= c;
50+ if (++char_count >= 4) {
51+ out[len++] = (bits >> 16);
52+ out[len++] = (bits >> 8) & 0xFF;
53+ out[len++] = bits & 0xFF;
54+ bits = 0;
55+ char_count = 0;
56+ } else {
57+ bits <<= 6;
58+ }
59+ }
60+ switch (char_count) {
61+ case 1:
62+ throw 'Base64 encoding incomplete: at least 2 bits missing';
63+ case 2:
64+ out[len++] = (bits >> 10);
65 break;
66+ case 3:
67+ out[len++] = (bits >> 16);
68+ out[len++] = (bits >> 8) & 0xFF;
69+ break;
000000000070 }
71+ if (haveU8 && out.length > len) // in case it was originally longer because of ignored characters
72+ out = out.subarray(0, len);
73+ return out;
74 }
75+76+ 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 }
008687+ static unarmor(a) {
88+ let m = Base64.re.exec(a);
89+ if (m) {
90+ if (m[1])
91+ a = m[1];
92+ else if (m[2])
93+ a = m[2];
94+ else if (m[3])
95+ a = m[3];
96+ else
97+ throw 'RegExp out of sync';
98+ }
99+ return Base64.decode(a);
100+ }
101102+}
000000000000103104+Base64.re = /-----BEGIN [^-]+-----([A-Za-z0-9+/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+/=\s]+)====|^([A-Za-z0-9+/=\s]+)$/;
00
···1+CMPv2 example as found on Wireshark page.
2+3+Original link:
4+https://wiki.wireshark.org/CMP
5+6+Attachment found moved here:
7+https://wiki.wireshark.org/uploads/__moin_import__/attachments/SampleCaptures/cmp_IR_sequence_OpenSSL-Cryptlib.pcap
8+9+begin-base64 644 cmpv2.der
10+MIICPjCB1QIBAqQCMACkRjBEMQswCQYDVQQGEwJERTEMMAoGA1UEChMDTlNOMREwDwYDVQQLEwhQ
11+RyBSREUgMzEUMBIGA1UEAxMLTWFydGluJ3MgQ0GgERgPMjAxMDA3MDUwNzM1MzhaoTwwOgYJKoZI
12+hvZ9B0INMC0EEJ5EpSD3zKjvmzHEK5+aoAAwCQYFKw4DAhoFAAICAfQwCgYIKwYBBQUIAQKiCwQJ
13+b/KGO0ILNJqApBIEEJGOKFG/9crkwU+z/I5ICa6lEgQQnnbd7EB2QjRCwOHt9QWdBKCCAUkwggFF
14+MIIBQTCBqAIBADCBoqaBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqVTOtjEEYELkomc3sMOy
15+Too5a9YeC91IMn52cVx7doY4AeO6J9e8p+CtWNbzVF8aRgHUhh31m+/X3MkQOaY5i8nF33uxAxDL
16+MDXttHjsqrF/tsgYuuHSs/Znz4PA1kLkdhKE9DLiGlCFaJH5QY5Hzl6bcS3ApuWCny0RRzIA1/cC
17+AwEAAaGBkzANBgkqhkiG9w0BAQUFAAOBgQArOldjg75fDx7BaFp0oAknLDREvB1KyE+BV96R+lB+
18+tRRhwv3dyc/GTvRw4GtaeDjWCjNPaDCl9ZvvVljaR2aMZvhaQV+DUmCMjFSP3DPiGuszBA6R2azX
19+NKtnpJ3SGx2vk0+Iv05tXLhdnqQJZs5a3S3R30kn4Vw+4WQm3kb0fKAXAxUA9K8u+7hv5Rg6GDn6
20+aoPxbUo6fpU=
21+====
+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+====
+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
+13
examples/ed25519.cer
···0000000000000
···1+X.509 certificate based on Daniel J. Bernsteinโs Curve25519 (as per RFC 8410).
2+$ openssl req -x509 -newkey ed25519 -keyout test.key -out test.cer -days 3652 -subj '/C=IT/L=Milano/CN=Test ed25519'
3+-----BEGIN CERTIFICATE-----
4+MIIBfzCCATGgAwIBAgIUfI5kSdcO2S0+LkpdL3b2VUJG10YwBQYDK2VwMDUxCzAJ
5+BgNVBAYTAklUMQ8wDQYDVQQHDAZNaWxhbm8xFTATBgNVBAMMDFRlc3QgZWQyNTUx
6+OTAeFw0yMDA5MDIxMzI1MjZaFw0zMDA5MDIxMzI1MjZaMDUxCzAJBgNVBAYTAklU
7+MQ8wDQYDVQQHDAZNaWxhbm8xFTATBgNVBAMMDFRlc3QgZWQyNTUxOTAqMAUGAytl
8+cAMhADupL/3LF2beQKKS95PeMPgKI6gxIV3QB9hjJC7/aCGFo1MwUTAdBgNVHQ4E
9+FgQUa6W9z536I1l4EmQXrh5y2JqASugwHwYDVR0jBBgwFoAUa6W9z536I1l4EmQX
10+rh5y2JqASugwDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQBvc3e+KJZaMzbX5TT9
11+kPP9QH8fAvkAV/IWDxZrBL9lhLaY0tDSv0zWbw624uidBKPgmVD5wm3ec60dNVeF
12+ZYYG
13+-----END CERTIFICATE-----
···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+This is a PKCS#7/CMS detached digital signature.
2+It is an old example generated in 2008 and as such uses obsolete cryptography: RSA1024 with SHA1.
3+-----BEGIN PKCS7-----
4+MIIDUAYJKoZIhvcNAQcCoIIDQTCCAz0CAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3
5+DQEHAaCCAfMwggHvMIIBWKADAgECAhAvoXazbunwSfREtACZZhlFMA0GCSqGSIb3
6+DQEBBQUAMAwxCjAIBgNVBAMMAWEwHhcNMDgxMDE1MTUwMzQxWhcNMDkxMDE1MTUw
7+MzQxWjAMMQowCAYDVQQDDAFhMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJ
8+Uwlwhu5hR8X01f+vG0mKPRHsVRjpZNxSEmsmFPdDiD9kylE3ertTDf0gRkpIvWfN
9+J+eymuxoXF0Qgl5gXAVuSrjupGD6J+VapixJiwLXJHokmDihLs3zfGARz08O3qnO
10+5ofBy0pRxq5isu/bAAcjoByZ1sI/g0iAuotC1UFObwIDAQABo1IwUDAOBgNVHQ8B
11+Af8EBAMCBPAwHQYDVR0OBBYEFEIGXQB4h+04Z3y/n7Nv94+CqPitMB8GA1UdIwQY
12+MBaAFEIGXQB4h+04Z3y/n7Nv94+CqPitMA0GCSqGSIb3DQEBBQUAA4GBAE0G7tAi
13+aacJxvP3fhEj+yP9VDxL0omrRRAEaMXwWaBf/Ggk1T/u+8/CDAdjuGNCiF6ctooK
14+c8u8KpnZJsGqnpGQ4n6L2KjTtRUDh+hija0eJRBFdirPQe2HAebQGFnmOk6Mn7Ki
15+QfBIsOzXim/bFqaBSbf06bLTQNwFouSO+jwOMYIBJTCCASECAQEwIDAMMQowCAYD
16+VQQDDAFhAhAvoXazbunwSfREtACZZhlFMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0B
17+CQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0wODEwMTUxNTAzNDNaMCMG
18+CSqGSIb3DQEJBDEWBBQAAAAAAAAAAAAAAAAAAAAAAAAAADANBgkqhkiG9w0BAQEF
19+AASBgHQe0ocjBn+ZVXWbb8CpZ2CxzFKiVrgZxZO2kMBwJoNyZx+jnICHdhfsX4Or
20+cF5vIYVAZIRz5RxqFmuZELTfZ/K89zaK873DP9V7/ftBGpezWEp9h29AtAzI9lzS
21+GB9gugiyB5JstXoM1L87KJmT05MeZxg1pvvFhwo1m/QOpcqz
22+-----END PKCS7-----
···1// Hex JavaScript decoder
2-// Copyright (c) 2008-2018 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-(function (undefined) {
17-"use strict";
001819-var Hex = {},
20- decoder;
2122-Hex.decode = function(a) {
23- var i;
24- if (decoder === undefined) {
25- var hex = "0123456789ABCDEF",
26- ignore = " \f\n\r\t\u00A0\u2028\u2029";
27- decoder = [];
28- for (i = 0; i < 16; ++i)
29- decoder[hex.charAt(i)] = i;
30- hex = hex.toLowerCase();
31- for (i = 10; i < 16; ++i)
32- decoder[hex.charAt(i)] = i;
33- for (i = 0; i < ignore.length; ++i)
34- decoder[ignore.charAt(i)] = -1;
35- }
36- var out = [],
37- bits = 0,
38- char_count = 0;
39- for (i = 0; i < a.length; ++i) {
40- var c = a.charAt(i);
41- if (c == '=')
42- break;
43- c = decoder[c];
44- if (c == -1)
45- continue;
46- if (c === undefined)
47- throw 'Illegal character at offset ' + i;
48- bits |= c;
49- if (++char_count >= 2) {
50- out[out.length] = bits;
51- bits = 0;
52- char_count = 0;
53- } else {
54- bits <<= 4;
0000055 }
0000056 }
57- if (char_count)
58- throw "Hex encoding incomplete: 4 bits missing";
59- return out;
60-};
6162-// export globals
63-if (typeof module !== 'undefined') { module.exports = Hex; } else { window.Hex = Hex; }
64-})();
···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
···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+const
17+ haveU8 = (typeof Uint8Array == 'function');
18+19+let decoder; // populated on first usage
2021+export class Hex {
02223+ /**
24+ * Decodes an hexadecimal value.
25+ * @param {string|Array|Uint8Array} a - a string representing hexadecimal data, or an array representation of its charcodes
26+ */
27+ static decode(a) {
28+ let isString = (typeof a == 'string');
29+ let i;
30+ if (decoder === undefined) {
31+ let hex = '0123456789ABCDEF',
32+ ignore = ' \f\n\r\t\u00A0\u2028\u2029';
33+ decoder = [];
34+ for (i = 0; i < 16; ++i)
35+ decoder[hex.charCodeAt(i)] = i;
36+ hex = hex.toLowerCase();
37+ for (i = 10; i < 16; ++i)
38+ decoder[hex.charCodeAt(i)] = i;
39+ for (i = 0; i < ignore.length; ++i)
40+ decoder[ignore.charCodeAt(i)] = -1;
41+ }
42+ let out = haveU8 ? new Uint8Array(a.length >> 1) : [],
43+ bits = 0,
44+ char_count = 0,
45+ len = 0;
46+ for (i = 0; i < a.length; ++i) {
47+ let c = isString ? a.charCodeAt(i) : a[i];
48+ c = decoder[c];
49+ if (c == -1)
50+ continue;
51+ if (c === undefined)
52+ throw 'Illegal character at offset ' + i;
53+ bits |= c;
54+ if (++char_count >= 2) {
55+ out[len++] = bits;
56+ bits = 0;
57+ char_count = 0;
58+ } else {
59+ bits <<= 4;
60+ }
61 }
62+ if (char_count)
63+ throw 'Hex encoding incomplete: 4 bits missing';
64+ if (haveU8 && out.length > len) // in case it was originally longer because of ignored characters
65+ out = out.subarray(0, len);
66+ return out;
67 }
00006869+}
00
···1-๏ปฟ/*global Hex, Base64, ASN1 */
2-"use strict";
000034-var maxLength = 10240,
05 reHex = /^\s*(?:[0-9A-Fa-f][0-9A-Fa-f]\s*)+$/,
6 tree = id('tree'),
7 dump = id('dump'),
8- wantHex = id('wantHex'),
009 area = id('area'),
10 file = id('file'),
11- hash = null;
000000012function id(elem) {
13 return document.getElementById(elem);
14}
15function text(el, string) {
16- if ('textContent' in el)
17- el.textContent = string;
18- else
19- el.innerText = string;
00000020}
21-function decode(der) {
22 tree.innerHTML = '';
23 dump.innerHTML = '';
0000000024 try {
25- var asn1 = ASN1.decode(der);
26- tree.appendChild(asn1.toDOM());
27- if (wantHex.checked)
28- dump.appendChild(asn1.toHexDOM());
29- var b64 = (der.length < maxLength) ? asn1.toB64String() : '';
30- if (area.value === '')
31- area.value = Base64.pretty(b64);
00000000000000000000000000032 try {
33 window.location.hash = hash = '#' + b64;
34- } catch (e) { // fails with "Access Denied" on IE with URLs longer than ~2048 chars
035 window.location.hash = hash = '#';
36 }
00000000000037 } catch (e) {
38 text(tree, e);
39 }
40}
41-function decodeText(val) {
42 try {
43- var der = reHex.test(val) ? Hex.decode(val) : Base64.unarmor(val);
44 decode(der);
45 } catch (e) {
46 text(tree, e);
47 dump.innerHTML = '';
48 }
49}
50-function decodeBinaryString(str) {
51- var der;
52 try {
53- if (reHex.test(str))
54- der = Hex.decode(str);
55- else if (Base64.re.test(str))
56- der = Base64.unarmor(str);
57- else
58- der = str;
59 decode(der);
60- } catch (e) {
61 text(tree, 'Cannot decode file.');
62 dump.innerHTML = '';
63 }
64}
65// set up buttons
66-id('butDecode').onclick = function () { decodeText(area.value); };
67-id('butClear').onclick = function () {
68- area.value = '';
69- tree.innerHTML = '';
70- dump.innerHTML = '';
71- hash = window.location.hash = '';
72-}
73-id('butExample').onclick = function () {
74- var demo = 'MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIAwggHvMIIBWKADAgECAhAvoXazbunwSfREtACZZhlFMA0GCSqGSIb3DQEBBQUAMAwxCjAIBgNVBAMMAWEwHhcNMDgxMDE1MTUwMzQxWhcNMDkxMDE1MTUwMzQxWjAMMQowCAYDVQQDDAFhMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJUwlwhu5hR8X01f-vG0mKPRHsVRjpZNxSEmsmFPdDiD9kylE3ertTDf0gRkpIvWfNJ-eymuxoXF0Qgl5gXAVuSrjupGD6J-VapixJiwLXJHokmDihLs3zfGARz08O3qnO5ofBy0pRxq5isu_bAAcjoByZ1sI_g0iAuotC1UFObwIDAQABo1IwUDAOBgNVHQ8BAf8EBAMCBPAwHQYDVR0OBBYEFEIGXQB4h-04Z3y_n7Nv94-CqPitMB8GA1UdIwQYMBaAFEIGXQB4h-04Z3y_n7Nv94-CqPitMA0GCSqGSIb3DQEBBQUAA4GBAE0G7tAiaacJxvP3fhEj-yP9VDxL0omrRRAEaMXwWaBf_Ggk1T_u-8_CDAdjuGNCiF6ctooKc8u8KpnZJsGqnpGQ4n6L2KjTtRUDh-hija0eJRBFdirPQe2HAebQGFnmOk6Mn7KiQfBIsOzXim_bFqaBSbf06bLTQNwFouSO-jwOAAAxggElMIIBIQIBATAgMAwxCjAIBgNVBAMMAWECEC-hdrNu6fBJ9ES0AJlmGUUwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA4MTAxNTE1MDM0M1owIwYJKoZIhvcNAQkEMRYEFAAAAAAAAAAAAAAAAAAAAAAAAAAAMA0GCSqGSIb3DQEBAQUABIGAdB7ShyMGf5lVdZtvwKlnYLHMUqJWuBnFk7aQwHAmg3JnH6OcgId2F-xfg6twXm8hhUBkhHPlHGoWa5kQtN9n8rz3NorzvcM_1Xv9-0Eal7NYSn2Hb0C0DMj2XNIYH2C6CLIHkmy1egzUvzsomZPTkx5nGDWm-8WHCjWb9A6lyrMAAAAAAAA';
75- decodeText(demo);
0000000000000000076};
0000077// this is only used if window.FileReader
78function read(f) {
79 area.value = ''; // clear text area, will get b64 content
80- var r = new FileReader();
81 r.onloadend = function () {
82- if (r.error)
83- alert("Your browser couldn't read the specified file (error code " + r.error.code + ").");
84- else
85- decodeBinaryString(r.result);
86 };
87 r.readAsBinaryString(f);
88}
89function load() {
90- if (file.files.length === 0)
91- alert("Select a file to load first.");
92- else
93- read(file.files[0]);
94}
95function loadFromHash() {
96 if (window.location.hash && window.location.hash != hash) {
97 hash = window.location.hash;
98- // Firefox is not consistent with other browsers and return an
99 // already-decoded hash string so we risk double-decoding here,
100 // but since % is not allowed in base64 nor hexadecimal, it's ok
101- var val = decodeURIComponent(hash.substr(1));
102- decodeText(val);
103 }
104}
105function stop(e) {
···108}
109function dragAccept(e) {
110 stop(e);
111- if (e.dataTransfer.files.length > 0)
112- read(e.dataTransfer.files[0]);
113}
114// main
115-if ('onhashchange' in window)
116- window.onhashchange = loadFromHash;
117loadFromHash();
118document.ondragover = stop;
119document.ondragleave = stop;
120-if ('FileReader' in window) {
121 file.style.display = 'block';
122 file.onchange = load;
123 document.ondrop = dragAccept;
124}
00000000000
···1+import './theme.js';
2+import { ASN1DOM } from './dom.js';
3+import { Base64 } from './base64.js';
4+import { Hex } from './hex.js';
5+import { Defs } from './defs.js';
6+import { tags } from './tags.js';
78+const
9+ maxLength = 10240,
10 reHex = /^\s*(?:[0-9A-Fa-f][0-9A-Fa-f]\s*)+$/,
11 tree = id('tree'),
12 dump = id('dump'),
13+ wantHex = checkbox('wantHex'),
14+ trimHex = checkbox('trimHex'),
15+ wantDef = checkbox('wantDef'),
16 area = id('area'),
17 file = id('file'),
18+ examples = id('examples'),
19+ selectDefs = id('definitions'),
20+ selectTag = id('tags');
21+22+let hash = null;
23+24+if (!window.console || !window.console.log) // IE8 with closed developer tools
25+ window.console = { log: function () {} };
26function id(elem) {
27 return document.getElementById(elem);
28}
29function text(el, string) {
30+ if ('textContent' in el) el.textContent = string;
31+ else el.innerText = string;
32+}
33+function checkbox(name) {
34+ const el = id(name);
35+ const cfg = localStorage.getItem(name);
36+ if (cfg === 'false')
37+ el.checked = false;
38+ el.onchange = () => localStorage.setItem(name, el.checked);
39+ return el;
40}
41+function 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);
54+ if (wantDef.checked) {
55+ selectDefs.innerHTML = '';
56+ const types = Defs.commonTypes
57+ .map(type => {
58+ const stats = Defs.match(asn1, type);
59+ return { type, match: stats.recognized / stats.total };
60+ })
61+ .sort((a, b) => b.match - a.match);
62+ for (const t of types) {
63+ t.element = document.createElement('option');
64+ t.element.innerText = (t.match * 100).toFixed(1) + '% ' + t.type.description;
65+ selectDefs.appendChild(t.element);
66+ }
67+ let not = document.createElement('option');
68+ not.innerText = 'no definition';
69+ selectDefs.appendChild(not);
70+ Defs.match(asn1, types[0].type);
71+ selectDefs.onchange = () => {
72+ for (const t of types) {
73+ if (t.element == selectDefs.selectedOptions[0]) {
74+ Defs.match(asn1, t.type);
75+ show(asn1);
76+ return;
77+ }
78+ }
79+ Defs.match(asn1, null);
80+ show(asn1);
81+ };
82+ } else
83+ selectDefs.innerHTML = '<option>no definition</option>';
84+ show(asn1);
85+ let b64 = der.length < maxLength ? asn1.toB64String() : '';
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 }
93+ let endOffset = asn1.posEnd();
94+ if (endOffset < der.length) {
95+ let p = document.createElement('p');
96+ p.innerText = 'Input contains ' + (der.length - endOffset) + ' more bytes to decode.';
97+ let button = document.createElement('button');
98+ button.innerText = 'try to decode';
99+ button.onclick = function () {
100+ decode(der, endOffset);
101+ };
102+ p.appendChild(button);
103+ tree.insertBefore(p, tree.firstChild);
104+ }
105 } catch (e) {
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);
113 } catch (e) {
114 text(tree, e);
115 dump.innerHTML = '';
116 }
117}
118+export function decodeBinaryString(str) {
119+ let der;
120 try {
121+ if (reHex.test(str)) der = Hex.decode(str);
122+ else if (Base64.re.test(str)) der = Base64.unarmor(str);
123+ else der = str;
000124 decode(der);
125+ } catch (ignore) {
126 text(tree, 'Cannot decode file.');
127 dump.innerHTML = '';
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;
163+}
164// this is only used if window.FileReader
165function read(f) {
166 area.value = ''; // clear text area, will get b64 content
167+ let r = new FileReader();
168 r.onloadend = function () {
169+ if (r.error) alert("Your browser couldn't read the specified file (error code " + r.error.code + ').');
170+ else decodeBinaryString(r.result);
00171 };
172 r.readAsBinaryString(f);
173}
174function load() {
175+ if (file.files.length === 0) alert('Select a file to load first.');
176+ else read(file.files[0]);
00177}
178function loadFromHash() {
179 if (window.location.hash && window.location.hash != hash) {
180 hash = window.location.hash;
181+ // Firefox is not consistent with other browsers and returns an
182 // already-decoded hash string so we risk double-decoding here,
183 // but since % is not allowed in base64 nor hexadecimal, it's ok
184+ let val = decodeURIComponent(hash.substr(1));
185+ if (val.length) decodeText(val);
186 }
187}
188function stop(e) {
···191}
192function dragAccept(e) {
193 stop(e);
194+ if (e.dataTransfer.files.length > 0) read(e.dataTransfer.files[0]);
0195}
196// main
197+if ('onhashchange' in window) window.onhashchange = loadFromHash;
0198loadFromHash();
199document.ondragover = stop;
200document.ondragleave = stop;
201+if ('FileReader' in window && 'readAsBinaryString' in new FileReader()) {
202 file.style.display = 'block';
203 file.onchange = load;
204 document.ondrop = dragAccept;
205}
206+for (let tag in tags) {
207+ let date = tags[tag];
208+ let el = document.createElement('option');
209+ el.value = tag;
210+ el.innerText = date + ' ' + tag;
211+ selectTag.appendChild(el);
212+}
213+selectTag.onchange = function (ev) {
214+ let tag = ev.target.selectedOptions[0].value;
215+ window.location.href = 'https://rawcdn.githack.com/lapo-luchini/asn1js/' + tag + '/index.html';
216+};
-87
int10.js
···1-// Big integer base-10 printing library
2-// Copyright (c) 2014-2018 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-(function () {
17-"use strict";
18-19-var max = 10000000000000; // biggest 10^n integer that can still fit 2^53 when multiplied by 256
20-21-function Int10(value) {
22- this.buf = [+value || 0];
23-}
24-25-Int10.prototype.mulAdd = function (m, c) {
26- // assert(m <= 256)
27- var b = this.buf,
28- l = b.length,
29- i, t;
30- for (i = 0; i < l; ++i) {
31- t = b[i] * m + c;
32- if (t < max)
33- c = 0;
34- else {
35- c = 0|(t / max);
36- t -= c * max;
37- }
38- b[i] = t;
39- }
40- if (c > 0)
41- b[i] = c;
42-};
43-44-Int10.prototype.sub = function (c) {
45- // assert(m <= 256)
46- var b = this.buf,
47- l = b.length,
48- i, t;
49- for (i = 0; i < l; ++i) {
50- t = b[i] - c;
51- if (t < 0) {
52- t += max;
53- c = 1;
54- } else
55- c = 0;
56- b[i] = t;
57- }
58- while (b[b.length - 1] === 0)
59- b.pop();
60-};
61-62-Int10.prototype.toString = function (base) {
63- if ((base || 10) != 10)
64- throw 'only base 10 is supported';
65- var b = this.buf,
66- s = b[b.length - 1].toString();
67- for (var i = b.length - 2; i >= 0; --i)
68- s += (max + b[i]).toString().substring(1);
69- return s;
70-};
71-72-Int10.prototype.valueOf = function () {
73- var b = this.buf,
74- v = 0;
75- for (var i = b.length - 1; i >= 0; --i)
76- v = v * max + b[i];
77- return v;
78-};
79-80-Int10.prototype.simplify = function () {
81- var b = this.buf;
82- return (b.length == 1) ? b[0] : this;
83-};
84-85-// export globals
86-if (typeof module !== 'undefined') { module.exports = Int10; } else { window.Int10 = Int10; }
87-})();
···1+#/bin/sh
2+URL='https://www.cs.auckland.ac.nz/~pgut001/dumpasn1.cfg'
3+if [ -x /usr/bin/fetch ]; then
4+ /usr/bin/fetch -m --no-verify-peer $URL
5+elif [ -x /usr/bin/wget ]; then
6+ /usr/bin/wget -N --no-check-certificate $URL
7+elif [ ! -r dumpasn1.cfg ]; then
8+ echo Please download $URL in this directory.
9+ exit 1
10+fi
11+cat dumpasn1.cfg | \
12+tr -d '\r' | \
13+awk -v apos="'" -v q='"' -v url="$URL" '
14+ function clean() {
15+ oid = "";
16+ comment = "";
17+ description = "";
18+ warning = "";
19+ }
20+ BEGIN {
21+ FS = "= *";
22+ clean();
23+ print "// Converted from: " url;
24+ print "// which is made by Peter Gutmann and whose license states:";
25+ print "// You can use this code in whatever way you want,";
26+ print "// as long as you don" apos "t try to claim you wrote it.";
27+ print "export const oids = {";
28+ }
29+ /^OID/ { oid = $2; }
30+ /^Comment/ { comment = $2; }
31+ /^Description/ { description = $2; }
32+ /^Warning/ { warning = ", \"w\": true"; }
33+ /^$/ {
34+ if (length(oid) > 0) {
35+ gsub(" ", ".", oid);
36+ gsub("\"", "\\\"", description);
37+ gsub("\"", "\\\"", comment);
38+ if (++seen[oid] > 1)
39+ print "Duplicate OID in line " NR ": " oid > "/dev/stderr";
40+ else
41+ printf "\"%s\": { \"d\": \"%s\", \"c\": \"%s\"%s },\n", oid, description, comment, warning;
42+ clean();
43+ }
44+ }
45+ END {
46+ print "};"
47+ }
48+' >oids.js
49+echo Conversion completed.
+32
updateRFC.sh
···00000000000000000000000000000000
···1+#/bin/sh
2+RFCs="5280 5208 3369 3161 2986 4211 4210 8017 4511"
3+downloadRFC() {
4+ URL="https://www.ietf.org/rfc/rfc$1.txt"
5+ if [ -x /usr/bin/fetch ]; then
6+ /usr/bin/fetch -m --no-verify-peer $URL
7+ elif [ -x /usr/bin/wget ]; then
8+ /usr/bin/wget -N --no-check-certificate $URL
9+ elif [ ! -r dumpasn1.cfg ]; then
10+ echo Please download $URL in this directory.
11+ exit 1
12+ fi
13+}
14+echo '{}' > rfcdef.json # start from scratch
15+mkdir -p rfc
16+cd rfc
17+for n in $RFCs; do
18+ downloadRFC $n
19+ ../parseRFC.js rfc$n.txt ../rfcdef.json
20+done
21+cd ..
22+{
23+ echo "// content parsed from ASN.1 definitions as found in the following RFCs: $RFCs"
24+ echo "// Copyright (C) The IETF Trust (2008)"
25+ echo "// as far as I can tell this file is allowed under the following clause:"
26+ echo "// It is acceptable under the current IETF rules (RFC 5378) to modify extracted code if necessary."
27+ echo "// https://trustee.ietf.org/about/faq/#reproducing-rfcs"
28+ echo -n "export const rfcdef = "
29+ cat rfcdef.json
30+ echo ";"
31+} > rfcdef.js
32+echo Conversion completed.