···11+# ChangeLog
22+33+## 2.1.0 - 2025-08-03
44+55+### Changed
66+77+- when fields are CHOICEs now both the field name and the choice name are shown (fixes GitHub #102)
88+- 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
99+1010+### Added
1111+1212+- add tests to check expected decoding
1313+1414+## 2.0.6 - 2025-07-29
1515+1616+### Added
1717+1818+- add proper support for standard Base64 (we previously only supported Base64url) (fixes GitHub #99)
1919+- improve test harness
2020+2121+## 2.0.5 - 2025-04-12
2222+2323+### Added
2424+2525+- add `index-local.html` for local `file://` usage without needing a web server
2626+- add definitions support for `LDAPMessage`
2727+- #TODO continue producing old ChangeLog entries
+3-2
LICENSE
···11-ASN.1 JavaScript decoder Copyright (c) 2008-2014 Lapo Luchini <lapo@lapo.it>
11+ISC License
22+33+Copyright (c) 2008-2025 Lapo Luchini <lapo@lapo.it>
2435Permission to use, copy, modify, and/or distribute this software for any
46purpose with or without fee is hereby granted, provided that the above
···1113WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1214ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1315OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1414-
+84-6
README.md
···3344asn1js is a JavaScript generic ASN.1 parser/decoder that can decode any valid ASN.1 DER or BER structures.
5566-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](http://lapo.it/asn1js/) or [offline (ZIP file)](http://lapo.it/asn1js/asn1js.zip).
66+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`.
77+88+Usage with `nodejs`
99+-------------------
1010+1111+This package can be installed with either npm or yarn via the following commands:
1212+1313+```sh
1414+npm install @lapo/asn1js
1515+# or other tools
1616+pnpm install @lapo/asn1js
1717+yarn add @lapo/asn1js
1818+```
1919+2020+You can import the classes like this:
2121+2222+```js
2323+import { ASN1 } from '@lapo/asn1js';
2424+```
2525+2626+A submodule of this package can also be imported:
2727+2828+```js
2929+import { Hex } from '@lapo/asn1js/hex.js';
3030+```
3131+3232+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`):
3333+3434+```js
3535+const
3636+ { ASN1 } = require('@lapo/asn1js'),
3737+ { Hex } = require('@lapo/asn1js/hex.js');
3838+console.log(ASN1.decode(Hex.decode('06032B6570')).content());
3939+```
4040+4141+On older NodeJS you instead need to use async `import`:
4242+4343+```js
4444+async function main() {
4545+ const
4646+ { ASN1 } = await import('@lapo/asn1js'),
4747+ { Hex } = await import('@lapo/asn1js/hex.js');
4848+ console.log(ASN1.decode(Hex.decode('06032B6570')).content());
4949+}
5050+main();
5151+```
5252+5353+Usage on the web
5454+--------------------
5555+5656+Can be [tested on JSFiddle](https://jsfiddle.net/lapo/y6t2wo7q/).
5757+5858+```html
5959+<script>
6060+import { ASN1 } from 'https://unpkg.com/@lapo/asn1js@2.0.0/asn1.js';
6161+import { Hex } from 'https://unpkg.com/@lapo/asn1js@2.0.0/hex.js';
6262+6363+document.body.innerText = ASN1.decode(Hex.decode('06032B6570')).content();
6464+</script>
6565+```
6666+6767+Local usage
6868+--------------------
6969+7070+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.
7171+7272+Usage from CLI
7373+--------------------
7474+7575+You can dump an ASN.1 structure from the command line using the following command (no need to even install it):
7676+7777+```sh
7878+npx @lapo/asn1js ed25519.cer
7979+```
780881ISC license
982-----------
10831111-ASN.1 JavaScript decoder Copyright (c) 2008-2014 Lapo Luchini <lapo@lapo.it>
8484+ASN.1 JavaScript decoder Copyright (c) 2008-2025 Lapo Luchini <lapo@lapo.it>
12851386Permission 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.
1487···1790credits
1891-------
19922020-- OBJECT IDENTIFIER values are recognized using data taken from Peter Gutmann's [dumpasn1](http://www.cs.auckland.ac.nz/~pgut001/#standards) program.
2121-- BMPString support added by [Felice Gasper](https://github.com/FGasper)
9393+- OBJECT IDENTIFIER values are recognized using data taken from Peter Gutmann's [dumpasn1](https://www.cs.auckland.ac.nz/~pgut001/#standards) program.
9494+- BMPString support added by [Felipe Gasper](https://github.com/FGasper)
2295- extended tag support added by [Pรฉter Budai](https://www.peterbudai.eu/)
2396- patches by [Gergely Nagy](https://github.com/ngg)
9797+- Relative OID support added by [Mistial Developer](https://github.com/mistial-dev)
9898+- dark mode and other UI improvements by [Oliver Burgmaier](https://github.com/olibu/)
9999+- patches by [Nicolai Sรธborg](https://github.com/NicolaiSoeborg)
2410025101links
26102-----
271032828-- [official website](http://lapo.it/asn1js/)
104104+- [official website](https://lapo.it/asn1js/)
105105+- [dedicated domain](https://asn1js.eu/)
29106- [InDefero tracker](http://idf.lapo.it/p/asn1js/)
30107- [GitHub mirror](https://github.com/lapo-luchini/asn1js)
3131-- [Ohloh code stats](https://www.ohloh.net/p/asn1js)
108108+- [ChangeLog on GitHub](https://github.com/lapo-luchini/asn1js/blob/trunk/CHANGELOG.md)
109109+- [Ohloh code stats](https://www.openhub.net/p/asn1js)
+604-387
asn1.js
···11// ASN.1 JavaScript decoder
22-// Copyright (c) 2008-2014 Lapo Luchini <lapo@lapo.it>
22+// Copyright (c) 2008 Lapo Luchini <lapo@lapo.it>
3344// Permission to use, copy, modify, and/or distribute this software for any
55// purpose with or without fee is hereby granted, provided that the above
66// copyright notice and this permission notice appear in all copies.
77-//
77+//
88// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
99// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1010// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
···1313// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1414// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15151616-/*jshint browser: true, strict: true, immed: true, latedef: true, undef: true, regexdash: false */
1717-/*global oids */
1818-(function (undefined) {
1919-"use strict";
1616+import { Int10 } from './int10.js';
1717+import { oids } from './oids.js';
20182121-var Int10 = (typeof module !== 'undefined') ? require('./int10.js') : window.Int10,
2222- ellipsis = "\u2026",
2323- reTime = /^((?:1[89]|2\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)?)?$/;
1919+const
2020+ ellipsis = '\u2026',
2121+ 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)?)?$/,
2222+ 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)?)?$/,
2323+ hexDigits = '0123456789ABCDEF',
2424+ b64Std = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
2525+ b64URL = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
2626+ tableT61 = [
2727+ ['', ''],
2828+ ['AEIOUaeiou', 'รรรรรร รจรฌรฒรน'], // Grave
2929+ ['ACEILNORSUYZacegilnorsuyz', 'รฤรรฤนลรลลรรลนรกฤรฉฤฃรญฤบลรณลลรบรฝลบ'], // Acute
3030+ ['ACEGHIJOSUWYaceghijosuwy', 'รฤรฤฤครฤดรลรลดลถรขฤรชฤฤฅรฎฤตรดลรปลตลท'], // Circumflex
3131+ ['AINOUainou', 'รฤจรรลจรฃฤฉรฑรตลฉ'], // Tilde
3232+ ['AEIOUaeiou', 'ฤฤฤชลลชฤฤฤซลลซ'], // Macron
3333+ ['AGUagu', 'ฤฤลฌฤฤลญ'], // Breve
3434+ ['CEGIZcegz', 'ฤฤฤ ฤฐลปฤฤฤกลผ'], // Dot
3535+ ['AEIOUYaeiouy', 'รรรรรลธรครซรฏรถรผรฟ'], // Umlaut or diรฆresis
3636+ ['', ''],
3737+ ['AUau', 'ร ลฎรฅลฏ'], // Ring
3838+ ['CGKLNRSTcklnrst', 'รฤขฤถฤปล ลลลขรงฤทฤผลลลลฃ'], // Cedilla
3939+ ['', ''],
4040+ ['OUou', 'ลลฐลลฑ'], // Double Acute
4141+ ['AEIUaeiu', 'ฤฤฤฎลฒฤ ฤฤฏลณ'], // Ogonek
4242+ ['CDELNRSTZcdelnrstz', 'ฤฤฤฤฝลลล ลคลฝฤฤฤฤพลลลกลฅลพ'], // Caron
4343+ ];
24442545function stringCut(str, len) {
2646 if (str.length > len)
···2848 return str;
2949}
30503131-function Stream(enc, pos) {
3232- if (enc instanceof Stream) {
3333- this.enc = enc.enc;
3434- this.pos = enc.pos;
3535- } else {
3636- this.enc = enc;
3737- this.pos = pos;
5151+function checkPrintable(s) {
5252+ let i, v;
5353+ for (i = 0; i < s.length; ++i) {
5454+ v = s.charCodeAt(i);
5555+ if (v < 32 && v != 9 && v != 10 && v != 13) // [\t\r\n] are (kinda) printable
5656+ throw new Error('Unprintable character at index ' + i + ' (code ' + s.str.charCodeAt(i) + ')');
3857 }
3958}
4040-Stream.prototype.get = function (pos) {
4141- if (pos === undefined)
4242- pos = this.pos++;
4343- if (pos >= this.enc.length)
4444- throw 'Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length;
4545- return this.enc[pos];
4646-};
4747-Stream.prototype.hexDigits = "0123456789ABCDEF";
4848-Stream.prototype.hexByte = function (b) {
4949- return this.hexDigits.charAt((b >> 4) & 0xF) + this.hexDigits.charAt(b & 0xF);
5050-};
5151-Stream.prototype.hexDump = function (start, end, raw) {
5252- var s = "";
5353- for (var i = start; i < end; ++i) {
5454- s += this.hexByte(this.get(i));
5555- if (raw !== true)
5656- switch (i & 0xF) {
5757- case 0x7: s += " "; break;
5858- case 0xF: s += "\n"; break;
5959- default: s += " ";
6060- }
5959+6060+/** Class to manage a stream of bytes, with a zero-copy approach.
6161+ * It uses an existing array or binary string and advances a position index. */
6262+export class Stream {
6363+6464+ /**
6565+ * @param {Stream|array|string} enc data (will not be copied)
6666+ * @param {?number} pos starting position (mandatory when `end` is not a Stream)
6767+ */
6868+ constructor(enc, pos) {
6969+ if (enc instanceof Stream) {
7070+ this.enc = enc.enc;
7171+ this.pos = enc.pos;
7272+ } else {
7373+ this.enc = enc;
7474+ this.pos = pos;
7575+ }
7676+ if (typeof this.pos != 'number')
7777+ throw new Error('"pos" must be a numeric value');
7878+ if (typeof this.enc == 'string')
7979+ this.getRaw = pos => this.enc.charCodeAt(pos);
8080+ else if (typeof this.enc[0] == 'number')
8181+ this.getRaw = pos => this.enc[pos];
8282+ else
8383+ throw new Error('"enc" must be a numeric array or a string');
6184 }
6262- return s;
6363-};
6464-Stream.prototype.isASCII = function (start, end) {
6565- for (var i = start; i < end; ++i) {
6666- var c = this.get(i);
6767- if (c < 32 || c > 176)
6868- return false;
8585+ /** Get the byte at current position (and increment it) or at a specified position (and avoid moving current position).
8686+ * @param {?number} pos read position if specified, else current position (and increment it) */
8787+ get(pos) {
8888+ if (pos === undefined)
8989+ pos = this.pos++;
9090+ if (pos >= this.enc.length)
9191+ throw new Error('Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length);
9292+ return this.getRaw(pos);
6993 }
7070- return true;
7171-};
7272-Stream.prototype.parseStringISO = function (start, end) {
7373- var s = "";
7474- for (var i = start; i < end; ++i)
7575- s += String.fromCharCode(this.get(i));
7676- return s;
7777-};
7878-Stream.prototype.parseStringUTF = function (start, end) {
7979- var s = "";
8080- for (var i = start; i < end; ) {
8181- var c = this.get(i++);
8282- if (c < 128)
8383- s += String.fromCharCode(c);
8484- else if ((c > 191) && (c < 224))
8585- s += String.fromCharCode(((c & 0x1F) << 6) | (this.get(i++) & 0x3F));
8686- else
8787- s += String.fromCharCode(((c & 0x0F) << 12) | ((this.get(i++) & 0x3F) << 6) | (this.get(i++) & 0x3F));
9494+ /** Convert a single byte to an hexadcimal string (of length 2).
9595+ * @param {number} b */
9696+ static hexByte(b) {
9797+ return hexDigits.charAt((b >> 4) & 0xF) + hexDigits.charAt(b & 0xF);
8898 }
8989- return s;
9090-};
9191-Stream.prototype.parseStringBMP = function (start, end) {
9292- var str = "", hi, lo;
9393- for (var i = start; i < end; ) {
9494- hi = this.get(i++);
9595- lo = this.get(i++);
9696- str += String.fromCharCode((hi << 8) | lo);
9999+ /** Hexadecimal dump of a specified region of the stream.
100100+ * @param {number} start starting position (included)
101101+ * @param {number} end ending position (excluded)
102102+ * @param {string} type 'raw', 'byte' or 'dump' (default) */
103103+ hexDump(start, end, type = 'dump') {
104104+ let s = '';
105105+ for (let i = start; i < end; ++i) {
106106+ if (type == 'byte' && i > start)
107107+ s += ' ';
108108+ s += Stream.hexByte(this.get(i));
109109+ if (type == 'dump')
110110+ switch (i & 0xF) {
111111+ case 0x7: s += ' '; break;
112112+ case 0xF: s += '\n'; break;
113113+ default: s += ' ';
114114+ }
115115+ }
116116+ return s;
97117 }
9898- return str;
9999-};
100100-Stream.prototype.parseTime = function (start, end, shortYear) {
101101- var s = this.parseStringISO(start, end),
102102- m = reTime.exec(s);
103103- if (!m)
104104- return "Unrecognized time: " + s;
105105- if (shortYear) {
106106- // to avoid querying the timer, use the fixed range [1970, 2069]
107107- // it will conform with ITU X.400 [-10, +40] sliding window until 2030
108108- m[1] = +m[1];
109109- m[1] += (m[1] < 70) ? 2000 : 1900;
118118+ /** Base64url dump of a specified region of the stream (according to RFC 4648 section 5).
119119+ * @param {number} start starting position (included)
120120+ * @param {number} end ending position (excluded)
121121+ * @param {string} type 'url' (default, section 5 without padding) or 'std' (section 4 with padding) */
122122+ b64Dump(start, end, type = 'url') {
123123+ const b64 = type === 'url' ? b64URL : b64Std;
124124+ let extra = (end - start) % 3,
125125+ s = '',
126126+ i, c;
127127+ for (i = start; i + 2 < end; i += 3) {
128128+ c = this.get(i) << 16 | this.get(i + 1) << 8 | this.get(i + 2);
129129+ s += b64.charAt(c >> 18 & 0x3F);
130130+ s += b64.charAt(c >> 12 & 0x3F);
131131+ s += b64.charAt(c >> 6 & 0x3F);
132132+ s += b64.charAt(c & 0x3F);
133133+ }
134134+ if (extra > 0) {
135135+ c = this.get(i) << 16;
136136+ if (extra > 1) c |= this.get(i + 1) << 8;
137137+ s += b64.charAt(c >> 18 & 0x3F);
138138+ s += b64.charAt(c >> 12 & 0x3F);
139139+ if (extra == 2) s += b64.charAt(c >> 6 & 0x3F);
140140+ if (b64 === b64Std) s += '==='.slice(0, 3 - extra);
141141+ }
142142+ return s;
110143 }
111111- s = m[1] + "-" + m[2] + "-" + m[3] + " " + m[4];
112112- if (m[5]) {
113113- s += ":" + m[5];
114114- if (m[6]) {
115115- s += ":" + m[6];
116116- if (m[7])
117117- s += "." + m[7];
144144+ isASCII(start, end) {
145145+ for (let i = start; i < end; ++i) {
146146+ let c = this.get(i);
147147+ if (c < 32 || c > 176)
148148+ return false;
118149 }
150150+ return true;
119151 }
120120- if (m[8]) {
121121- s += " UTC";
122122- if (m[8] != 'Z') {
123123- s += m[8];
124124- if (m[9])
125125- s += ":" + m[9];
152152+ parseStringISO(start, end, maxLength) {
153153+ let s = '';
154154+ for (let i = start; i < end; ++i)
155155+ s += String.fromCharCode(this.get(i));
156156+ return { size: s.length, str: stringCut(s, maxLength) };
157157+ }
158158+ parseStringT61(start, end, maxLength) {
159159+ // warning: this code is not very well tested so far
160160+ function merge(c, d) {
161161+ let t = tableT61[c - 0xC0];
162162+ let i = t[0].indexOf(String.fromCharCode(d));
163163+ return (i < 0) ? '\0' : t[1].charAt(i);
126164 }
165165+ let s = '', c;
166166+ for (let i = start; i < end; ++i) {
167167+ c = this.get(i);
168168+ if (c >= 0xA4 && c <= 0xBF)
169169+ s += '$ยฅ#ยงยค\0\0ยซ\0\0\0\0ยฐยฑยฒยณรยตยถยทรท\0\0ยปยผยฝยพยฟ'.charAt(c - 0xA4);
170170+ else if (c >= 0xE0 && c <= 0xFF)
171171+ s += 'โฆรรยชฤฆ\0ฤฒฤฟลรลยบรลฆลลฤธรฆฤรฐฤงฤฑฤณลลรธลรรพลงล\0'.charAt(c - 0xE0);
172172+ else if (c >= 0xC0 && c <= 0xCF)
173173+ s += merge(c, this.get(++i));
174174+ else // using ISO 8859-1 for characters undefined (or equal) in T.61
175175+ s += String.fromCharCode(c);
176176+ }
177177+ return { size: s.length, str: stringCut(s, maxLength) };
127178 }
128128- return s;
129129-};
130130-Stream.prototype.parseInteger = function (start, end) {
131131- var v = this.get(start),
132132- neg = (v > 127),
133133- pad = neg ? 255 : 0,
134134- len,
135135- s = '';
136136- // skip unuseful bits (not allowed in DER)
137137- while (v == pad && start < end)
138138- v = this.get(++start);
139139- len = end - start;
140140- if (len === 0)
141141- return neg ? -1 : 0;
142142- // show bit length of huge integers
143143- if (len > 4) {
144144- s = v;
145145- len <<= 3;
146146- while (((s ^ pad) & 0x80) == 0) {
147147- s <<= 1;
148148- --len;
179179+ parseStringUTF(start, end, maxLength) {
180180+ function ex(c) { // must be 10xxxxxx
181181+ if ((c < 0x80) || (c >= 0xC0))
182182+ throw new Error('Invalid UTF-8 continuation byte: ' + c);
183183+ return (c & 0x3F);
184184+ }
185185+ function surrogate(cp) {
186186+ if (cp < 0x10000)
187187+ throw new Error('UTF-8 overlong encoding, codepoint encoded in 4 bytes: ' + cp);
188188+ // we could use String.fromCodePoint(cp) but let's be nice to older browsers and use surrogate pairs
189189+ cp -= 0x10000;
190190+ return String.fromCharCode((cp >> 10) + 0xD800, (cp & 0x3FF) + 0xDC00);
149191 }
150150- s = "(" + len + " bit)\n";
192192+ let s = '';
193193+ for (let i = start; i < end; ) {
194194+ let c = this.get(i++);
195195+ if (c < 0x80) // 0xxxxxxx (7 bit)
196196+ s += String.fromCharCode(c);
197197+ else if (c < 0xC0)
198198+ throw new Error('Invalid UTF-8 starting byte: ' + c);
199199+ else if (c < 0xE0) // 110xxxxx 10xxxxxx (11 bit)
200200+ s += String.fromCharCode(((c & 0x1F) << 6) | ex(this.get(i++)));
201201+ else if (c < 0xF0) // 1110xxxx 10xxxxxx 10xxxxxx (16 bit)
202202+ s += String.fromCharCode(((c & 0x0F) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++)));
203203+ else if (c < 0xF8) // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (21 bit)
204204+ s += surrogate(((c & 0x07) << 18) | (ex(this.get(i++)) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++)));
205205+ else
206206+ throw new Error('Invalid UTF-8 starting byte (since 2003 it is restricted to 4 bytes): ' + c);
207207+ }
208208+ return { size: s.length, str: stringCut(s, maxLength) };
151209 }
152152- // decode the integer
153153- if (neg) v = v - 256;
154154- var n = new Int10(v);
155155- for (var i = start + 1; i < end; ++i)
156156- n.mulAdd(256, this.get(i));
157157- return s + n.toString();
158158-};
159159-Stream.prototype.parseBitString = function (start, end, maxLength) {
160160- var unusedBit = this.get(start),
161161- lenBit = ((end - start - 1) << 3) - unusedBit,
162162- intro = "(" + lenBit + " bit)\n",
163163- s = "",
164164- skip = unusedBit;
165165- for (var i = end - 1; i > start; --i) {
166166- var b = this.get(i);
167167- for (var j = skip; j < 8; ++j)
168168- s += (b >> j) & 1 ? "1" : "0";
169169- skip = 0;
170170- if (s.length > maxLength)
171171- return intro + stringCut(s, maxLength);
210210+ parseStringBMP(start, end, maxLength) {
211211+ let s = '', hi, lo;
212212+ for (let i = start; i < end; ) {
213213+ hi = this.get(i++);
214214+ lo = this.get(i++);
215215+ s += String.fromCharCode((hi << 8) | lo);
216216+ }
217217+ return { size: s.length, str: stringCut(s, maxLength) };
172218 }
173173- return intro + s;
174174-};
175175-Stream.prototype.parseOctetString = function (start, end, maxLength) {
176176- if (this.isASCII(start, end))
177177- return stringCut(this.parseStringISO(start, end), maxLength);
178178- var len = end - start,
179179- s = "(" + len + " byte)\n";
180180- maxLength /= 2; // we work in bytes
181181- if (len > maxLength)
182182- end = start + maxLength;
183183- for (var i = start; i < end; ++i)
184184- s += this.hexByte(this.get(i));
185185- if (len > maxLength)
186186- s += ellipsis;
187187- return s;
188188-};
189189-Stream.prototype.parseOID = function (start, end, maxLength) {
190190- var s = '',
191191- n = new Int10(),
192192- bits = 0;
193193- for (var i = start; i < end; ++i) {
194194- var v = this.get(i);
195195- n.mulAdd(128, v & 0x7F);
196196- bits += 7;
197197- if (!(v & 0x80)) { // finished
198198- if (s === '') {
199199- n = n.simplify();
200200- var m = n < 80 ? n < 40 ? 0 : 1 : 2;
201201- s = m + "." + (n - m * 40);
202202- } else
203203- s += "." + n.toString();
219219+ parseTime(start, end, shortYear) {
220220+ let s = this.parseStringISO(start, end).str,
221221+ m = (shortYear ? reTimeS : reTimeL).exec(s);
222222+ if (!m)
223223+ throw new Error('Unrecognized time: ' + s);
224224+ if (shortYear) {
225225+ // to avoid querying the timer, use the fixed range [1970, 2069]
226226+ // it will conform with ITU X.400 [-10, +40] sliding window until 2030
227227+ m[1] = +m[1];
228228+ m[1] += (m[1] < 70) ? 2000 : 1900;
229229+ }
230230+ s = m[1] + '-' + m[2] + '-' + m[3] + ' ' + m[4];
231231+ if (m[5]) {
232232+ s += ':' + m[5];
233233+ if (m[6]) {
234234+ s += ':' + m[6];
235235+ if (m[7])
236236+ s += '.' + m[7];
237237+ }
238238+ }
239239+ if (m[8]) {
240240+ s += ' UTC';
241241+ if (m[9])
242242+ s += m[9] + ':' + (m[10] || '00');
243243+ }
244244+ return s;
245245+ }
246246+ parseInteger(start, end) {
247247+ let v = this.get(start),
248248+ neg = (v > 127),
249249+ pad = neg ? 255 : 0,
250250+ len,
251251+ s = '';
252252+ // skip unuseful bits (not allowed in DER)
253253+ while (v == pad && ++start < end)
254254+ v = this.get(start);
255255+ len = end - start;
256256+ if (len === 0)
257257+ return neg ? '-1' : '0';
258258+ // show bit length of huge integers
259259+ if (len > 4) {
260260+ s = v;
261261+ len <<= 3;
262262+ while (((s ^ pad) & 0x80) == 0) {
263263+ s <<= 1;
264264+ --len;
265265+ }
266266+ s = '(' + len + ' bit)\n';
267267+ }
268268+ // decode the integer
269269+ if (neg) v = v - 256;
270270+ let n = new Int10(v);
271271+ for (let i = start + 1; i < end; ++i)
272272+ n.mulAdd(256, this.get(i));
273273+ return s + n.toString();
274274+ }
275275+ parseBitString(start, end, maxLength) {
276276+ let unusedBits = this.get(start);
277277+ if (unusedBits > 7)
278278+ throw new Error('Invalid BitString with unusedBits=' + unusedBits);
279279+ let lenBit = ((end - start - 1) << 3) - unusedBits,
280280+ s = '';
281281+ for (let i = start + 1; i < end; ++i) {
282282+ let b = this.get(i),
283283+ skip = (i == end - 1) ? unusedBits : 0;
284284+ for (let j = 7; j >= skip; --j)
285285+ s += (b >> j) & 1 ? '1' : '0';
204286 if (s.length > maxLength)
205205- return stringCut(s, maxLength);
206206- n = new Int10();
287287+ s = stringCut(s, maxLength);
288288+ }
289289+ return { size: lenBit, str: s };
290290+ }
291291+ parseOctetString(start, end, maxLength) {
292292+ let len = end - start,
293293+ s;
294294+ try {
295295+ s = this.parseStringUTF(start, end, maxLength);
296296+ checkPrintable(s.str);
297297+ return { size: end - start, str: s.str };
298298+ } catch (ignore) {
299299+ // ignore
300300+ }
301301+ maxLength /= 2; // we work in bytes
302302+ if (len > maxLength)
303303+ end = start + maxLength;
304304+ s = '';
305305+ for (let i = start; i < end; ++i)
306306+ s += Stream.hexByte(this.get(i));
307307+ if (len > maxLength)
308308+ s += ellipsis;
309309+ return { size: len, str: s };
310310+ }
311311+ parseOID(start, end, maxLength, isRelative) {
312312+ let s = '',
313313+ n = new Int10(),
207314 bits = 0;
315315+ for (let i = start; i < end; ++i) {
316316+ let v = this.get(i);
317317+ n.mulAdd(128, v & 0x7F);
318318+ bits += 7;
319319+ if (!(v & 0x80)) { // finished
320320+ if (s === '') {
321321+ n = n.simplify();
322322+ if (isRelative) {
323323+ s = (n instanceof Int10) ? n.toString() : '' + n;
324324+ } else if (n instanceof Int10) {
325325+ n.sub(80);
326326+ s = '2.' + n.toString();
327327+ } else {
328328+ let m = n < 80 ? n < 40 ? 0 : 1 : 2;
329329+ s = m + '.' + (n - m * 40);
330330+ }
331331+ } else
332332+ s += '.' + n.toString();
333333+ if (s.length > maxLength)
334334+ return stringCut(s, maxLength);
335335+ n = new Int10();
336336+ bits = 0;
337337+ }
208338 }
339339+ if (bits > 0)
340340+ s += '.incomplete';
341341+ if (typeof oids === 'object' && !isRelative) {
342342+ let oid = oids[s];
343343+ if (oid) {
344344+ if (oid.d) s += '\n' + oid.d;
345345+ if (oid.c) s += '\n' + oid.c;
346346+ if (oid.w) s += '\n(warning!)';
347347+ }
348348+ }
349349+ return s;
209350 }
210210- if (bits > 0)
211211- s += ".incomplete";
212212- return s;
213213-};
351351+ parseRelativeOID(start, end, maxLength) {
352352+ return this.parseOID(start, end, maxLength, true);
353353+ }
354354+}
355355+356356+function recurse(el, parser, maxLength) {
357357+ let avoidRecurse = true;
358358+ if (el.tag.tagConstructed && el.sub) {
359359+ avoidRecurse = false;
360360+ el.sub.forEach(function (e1) {
361361+ if (e1.tag.tagClass != el.tag.tagClass || e1.tag.tagNumber != el.tag.tagNumber)
362362+ avoidRecurse = true;
363363+ });
364364+ }
365365+ if (avoidRecurse)
366366+ return el.stream[parser](el.posContent(), el.posContent() + Math.abs(el.length), maxLength);
367367+ let d = { size: 0, str: '' };
368368+ el.sub.forEach(function (el) {
369369+ let d1 = recurse(el, parser, maxLength - d.str.length);
370370+ d.size += d1.size;
371371+ d.str += d1.str;
372372+ });
373373+ return d;
374374+}
214375215215-function ASN1(stream, header, length, tag, sub) {
216216- if (!(tag instanceof ASN1Tag)) throw 'Invalid tag value.';
217217- this.stream = stream;
218218- this.header = header;
219219- this.length = length;
220220- this.tag = tag;
221221- this.sub = sub;
376376+class ASN1Tag {
377377+ constructor(stream) {
378378+ let buf = stream.get();
379379+ this.tagClass = buf >> 6;
380380+ this.tagConstructed = ((buf & 0x20) !== 0);
381381+ this.tagNumber = buf & 0x1F;
382382+ if (this.tagNumber == 0x1F) { // long tag
383383+ let n = new Int10();
384384+ do {
385385+ buf = stream.get();
386386+ n.mulAdd(128, buf & 0x7F);
387387+ } while (buf & 0x80);
388388+ this.tagNumber = n.simplify();
389389+ }
390390+ }
391391+ isUniversal() {
392392+ return this.tagClass === 0x00;
393393+ }
394394+ isEOC() {
395395+ return this.tagClass === 0x00 && this.tagNumber === 0x00;
396396+ }
222397}
223223-ASN1.prototype.typeName = function () {
224224- switch (this.tag.tagClass) {
225225- case 0: // universal
226226- switch (this.tag.tagNumber) {
227227- case 0x00: return "EOC";
228228- case 0x01: return "BOOLEAN";
229229- case 0x02: return "INTEGER";
230230- case 0x03: return "BIT_STRING";
231231- case 0x04: return "OCTET_STRING";
232232- case 0x05: return "NULL";
233233- case 0x06: return "OBJECT_IDENTIFIER";
234234- case 0x07: return "ObjectDescriptor";
235235- case 0x08: return "EXTERNAL";
236236- case 0x09: return "REAL";
237237- case 0x0A: return "ENUMERATED";
238238- case 0x0B: return "EMBEDDED_PDV";
239239- case 0x0C: return "UTF8String";
240240- case 0x10: return "SEQUENCE";
241241- case 0x11: return "SET";
242242- case 0x12: return "NumericString";
243243- case 0x13: return "PrintableString"; // ASCII subset
244244- case 0x14: return "TeletexString"; // aka T61String
245245- case 0x15: return "VideotexString";
246246- case 0x16: return "IA5String"; // ASCII
247247- case 0x17: return "UTCTime";
248248- case 0x18: return "GeneralizedTime";
249249- case 0x19: return "GraphicString";
250250- case 0x1A: return "VisibleString"; // ASCII subset
251251- case 0x1B: return "GeneralString";
252252- case 0x1C: return "UniversalString";
253253- case 0x1E: return "BMPString";
398398+399399+export class ASN1 {
400400+ constructor(stream, header, length, tag, tagLen, sub) {
401401+ if (!(tag instanceof ASN1Tag)) throw new Error('Invalid tag value.');
402402+ this.stream = stream;
403403+ this.header = header;
404404+ this.length = length;
405405+ this.tag = tag;
406406+ this.tagLen = tagLen;
407407+ this.sub = sub;
408408+ }
409409+ typeName() {
410410+ switch (this.tag.tagClass) {
411411+ case 0: // universal
412412+ switch (this.tag.tagNumber) {
413413+ case 0x00: return 'EOC';
414414+ case 0x01: return 'BOOLEAN';
415415+ case 0x02: return 'INTEGER';
416416+ case 0x03: return 'BIT_STRING';
417417+ case 0x04: return 'OCTET_STRING';
418418+ case 0x05: return 'NULL';
419419+ case 0x06: return 'OBJECT_IDENTIFIER';
420420+ case 0x07: return 'ObjectDescriptor';
421421+ case 0x08: return 'EXTERNAL';
422422+ case 0x09: return 'REAL';
423423+ case 0x0A: return 'ENUMERATED';
424424+ case 0x0B: return 'EMBEDDED_PDV';
425425+ case 0x0C: return 'UTF8String';
426426+ case 0x0D: return 'RELATIVE_OID';
427427+ case 0x10: return 'SEQUENCE';
428428+ case 0x11: return 'SET';
429429+ case 0x12: return 'NumericString';
430430+ case 0x13: return 'PrintableString'; // ASCII subset
431431+ case 0x14: return 'TeletexString'; // aka T61String
432432+ case 0x15: return 'VideotexString';
433433+ case 0x16: return 'IA5String'; // ASCII
434434+ case 0x17: return 'UTCTime';
435435+ case 0x18: return 'GeneralizedTime';
436436+ case 0x19: return 'GraphicString';
437437+ case 0x1A: return 'VisibleString'; // ASCII subset
438438+ case 0x1B: return 'GeneralString';
439439+ case 0x1C: return 'UniversalString';
440440+ case 0x1E: return 'BMPString';
441441+ }
442442+ return 'Universal_' + this.tag.tagNumber.toString();
443443+ case 1: return 'Application_' + this.tag.tagNumber.toString();
444444+ case 2: return '[' + this.tag.tagNumber.toString() + ']'; // Context
445445+ case 3: return 'Private_' + this.tag.tagNumber.toString();
254446 }
255255- return "Universal_" + this.tag.tagNumber.toString();
256256- case 1: return "Application_" + this.tag.tagNumber.toString();
257257- case 2: return "[" + this.tag.tagNumber.toString() + "]"; // Context
258258- case 3: return "Private_" + this.tag.tagNumber.toString();
259447 }
260260-};
261261-ASN1.prototype.content = function (maxLength) { // a preview of the content (intended for humans)
262262- if (this.tag === undefined)
448448+ /** A string preview of the content (intended for humans). */
449449+ content(maxLength) {
450450+ if (this.tag === undefined)
451451+ return null;
452452+ if (maxLength === undefined)
453453+ maxLength = Infinity;
454454+ let content = this.posContent(),
455455+ len = Math.abs(this.length);
456456+ if (!this.tag.isUniversal()) {
457457+ if (this.sub !== null)
458458+ return '(' + this.sub.length + ' elem)';
459459+ let d1 = this.stream.parseOctetString(content, content + len, maxLength);
460460+ return '(' + d1.size + ' byte)\n' + d1.str;
461461+ }
462462+ switch (this.tag.tagNumber) {
463463+ case 0x01: // BOOLEAN
464464+ if (len === 0) return 'invalid length 0';
465465+ return (this.stream.get(content) === 0) ? 'false' : 'true';
466466+ case 0x02: // INTEGER
467467+ if (len === 0) return 'invalid length 0';
468468+ return this.stream.parseInteger(content, content + len);
469469+ case 0x03: { // BIT_STRING
470470+ let d = recurse(this, 'parseBitString', maxLength);
471471+ return '(' + d.size + ' bit)\n' + d.str;
472472+ }
473473+ case 0x04: { // OCTET_STRING
474474+ if (len === 0) return 'invalid length 0';
475475+ let d = recurse(this, 'parseOctetString', maxLength);
476476+ return '(' + d.size + ' byte)\n' + d.str;
477477+ }
478478+ //case 0x05: // NULL
479479+ case 0x06: // OBJECT_IDENTIFIER
480480+ return this.stream.parseOID(content, content + len, maxLength);
481481+ //case 0x07: // ObjectDescriptor
482482+ //case 0x08: // EXTERNAL
483483+ //case 0x09: // REAL
484484+ case 0x0A: // ENUMERATED
485485+ return this.stream.parseInteger(content, content + len);
486486+ //case 0x0B: // EMBEDDED_PDV
487487+ case 0x0D: // RELATIVE-OID
488488+ return this.stream.parseRelativeOID(content, content + len, maxLength);
489489+ case 0x10: // SEQUENCE
490490+ case 0x11: // SET
491491+ if (this.sub !== null)
492492+ return '(' + this.sub.length + ' elem)';
493493+ else
494494+ return '(no elem)';
495495+ case 0x0C: // UTF8String
496496+ return recurse(this, 'parseStringUTF', maxLength).str;
497497+ case 0x14: // TeletexString
498498+ return recurse(this, 'parseStringT61', maxLength).str;
499499+ case 0x12: // NumericString
500500+ case 0x13: // PrintableString
501501+ case 0x15: // VideotexString
502502+ case 0x16: // IA5String
503503+ case 0x1A: // VisibleString
504504+ case 0x1B: // GeneralString
505505+ //case 0x19: // GraphicString
506506+ //case 0x1C: // UniversalString
507507+ return recurse(this, 'parseStringISO', maxLength).str;
508508+ case 0x1E: // BMPString
509509+ return recurse(this, 'parseStringBMP', maxLength).str;
510510+ case 0x17: // UTCTime
511511+ case 0x18: // GeneralizedTime
512512+ return this.stream.parseTime(content, content + len, (this.tag.tagNumber == 0x17));
513513+ }
263514 return null;
264264- if (maxLength === undefined)
265265- maxLength = Infinity;
266266- var content = this.posContent(),
267267- len = Math.abs(this.length);
268268- if (!this.tag.isUniversal()) {
269269- if (this.sub !== null)
270270- return "(" + this.sub.length + " elem)";
271271- return this.stream.parseOctetString(content, content + len, maxLength);
515515+ }
516516+ toString() {
517517+ return this.typeName() + '@' + this.stream.pos + '[header:' + this.header + ',length:' + this.length + ',sub:' + ((this.sub === null) ? 'null' : this.sub.length) + ']';
272518 }
273273- switch (this.tag.tagNumber) {
274274- case 0x01: // BOOLEAN
275275- return (this.stream.get(content) === 0) ? "false" : "true";
276276- case 0x02: // INTEGER
277277- return this.stream.parseInteger(content, content + len);
278278- case 0x03: // BIT_STRING
279279- return this.sub ? "(" + this.sub.length + " elem)" :
280280- this.stream.parseBitString(content, content + len, maxLength);
281281- case 0x04: // OCTET_STRING
282282- return this.sub ? "(" + this.sub.length + " elem)" :
283283- this.stream.parseOctetString(content, content + len, maxLength);
284284- //case 0x05: // NULL
285285- case 0x06: // OBJECT_IDENTIFIER
286286- return this.stream.parseOID(content, content + len, maxLength);
287287- //case 0x07: // ObjectDescriptor
288288- //case 0x08: // EXTERNAL
289289- //case 0x09: // REAL
290290- //case 0x0A: // ENUMERATED
291291- //case 0x0B: // EMBEDDED_PDV
292292- case 0x10: // SEQUENCE
293293- case 0x11: // SET
294294- return "(" + this.sub.length + " elem)";
295295- case 0x0C: // UTF8String
296296- return stringCut(this.stream.parseStringUTF(content, content + len), maxLength);
297297- case 0x12: // NumericString
298298- case 0x13: // PrintableString
299299- case 0x14: // TeletexString
300300- case 0x15: // VideotexString
301301- case 0x16: // IA5String
302302- //case 0x19: // GraphicString
303303- case 0x1A: // VisibleString
304304- //case 0x1B: // GeneralString
305305- //case 0x1C: // UniversalString
306306- return stringCut(this.stream.parseStringISO(content, content + len), maxLength);
307307- case 0x1E: // BMPString
308308- return stringCut(this.stream.parseStringBMP(content, content + len), maxLength);
309309- case 0x17: // UTCTime
310310- case 0x18: // GeneralizedTime
311311- return this.stream.parseTime(content, content + len, (this.tag.tagNumber == 0x17));
519519+ toPrettyString(indent) {
520520+ if (indent === undefined) indent = '';
521521+ let s = indent;
522522+ if (this.def) {
523523+ if (this.def.id)
524524+ s += this.def.id + ' ';
525525+ if (this.def.name && this.def.name != this.typeName().replace(/_/g, ' '))
526526+ s+= this.def.name + ' ';
527527+ if (this.def.mismatch)
528528+ s += '[?] ';
529529+ }
530530+ s += this.typeName() + ' @' + this.stream.pos;
531531+ if (this.length >= 0)
532532+ s += '+';
533533+ s += this.length;
534534+ if (this.tag.tagConstructed)
535535+ s += ' (constructed)';
536536+ else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null))
537537+ s += ' (encapsulates)';
538538+ let content = this.content();
539539+ if (content)
540540+ s += ': ' + content.replace(/\n/g, '|');
541541+ s += '\n';
542542+ if (this.sub !== null) {
543543+ indent += ' ';
544544+ for (let i = 0, max = this.sub.length; i < max; ++i)
545545+ s += this.sub[i].toPrettyString(indent);
546546+ }
547547+ return s;
548548+ }
549549+ posStart() {
550550+ return this.stream.pos;
551551+ }
552552+ posContent() {
553553+ return this.stream.pos + this.header;
554554+ }
555555+ posEnd() {
556556+ return this.stream.pos + this.header + Math.abs(this.length);
557557+ }
558558+ /** Position of the length. */
559559+ posLen() {
560560+ return this.stream.pos + this.tagLen;
561561+ }
562562+ /** Hexadecimal dump of the node.
563563+ * @param type 'raw', 'byte' or 'dump' */
564564+ toHexString(type = 'raw') {
565565+ return this.stream.hexDump(this.posStart(), this.posEnd(), type);
312566 }
313313- return null;
314314-};
315315-ASN1.prototype.toString = function () {
316316- return this.typeName() + "@" + this.stream.pos + "[header:" + this.header + ",length:" + this.length + ",sub:" + ((this.sub === null) ? 'null' : this.sub.length) + "]";
317317-};
318318-ASN1.prototype.toPrettyString = function (indent) {
319319- if (indent === undefined) indent = '';
320320- var s = indent + this.typeName() + " @" + this.stream.pos;
321321- if (this.length >= 0)
322322- s += "+";
323323- s += this.length;
324324- if (this.tag.tagConstructed)
325325- s += " (constructed)";
326326- else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null))
327327- s += " (encapsulates)";
328328- s += "\n";
329329- if (this.sub !== null) {
330330- indent += ' ';
331331- for (var i = 0, max = this.sub.length; i < max; ++i)
332332- s += this.sub[i].toPrettyString(indent);
567567+ /** Base64url dump of the node (according to RFC 4648 section 5).
568568+ * @param {string} type 'url' (default, section 5 without padding) or 'std' (section 4 with padding)
569569+ */
570570+ toB64String(type = 'url') {
571571+ return this.stream.b64Dump(this.posStart(), this.posEnd(), type);
333572 }
334334- return s;
335335-};
336336-ASN1.prototype.posStart = function () {
337337- return this.stream.pos;
338338-};
339339-ASN1.prototype.posContent = function () {
340340- return this.stream.pos + this.header;
341341-};
342342-ASN1.prototype.posEnd = function () {
343343- return this.stream.pos + this.header + Math.abs(this.length);
344344-};
345345-ASN1.prototype.toHexString = function (root) {
346346- return this.stream.hexDump(this.posStart(), this.posEnd(), true);
347347-};
348348-ASN1.decodeLength = function (stream) {
349349- var buf = stream.get(),
350350- len = buf & 0x7F;
351351- if (len == buf)
352352- return len;
353353- if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways
354354- throw "Length over 48 bits not supported at position " + (stream.pos - 1);
355355- if (len === 0)
356356- return null; // undefined
357357- buf = 0;
358358- for (var i = 0; i < len; ++i)
359359- buf = (buf * 256) + stream.get();
360360- return buf;
361361-};
362362-function ASN1Tag(stream) {
363363- var buf = stream.get();
364364- this.tagClass = buf >> 6;
365365- this.tagConstructed = ((buf & 0x20) !== 0);
366366- this.tagNumber = buf & 0x1F;
367367- if (this.tagNumber == 0x1F) { // long tag
368368- var n = new Int10();
369369- do {
370370- buf = stream.get();
371371- n.mulAdd(128, buf & 0x7F);
372372- } while (buf & 0x80);
373373- this.tagNumber = n.simplify();
573573+ static decodeLength(stream) {
574574+ let buf = stream.get(),
575575+ len = buf & 0x7F;
576576+ if (len == buf) // first bit was 0, short form
577577+ return len;
578578+ if (len === 0) // long form with length 0 is a special case
579579+ return null; // undefined length
580580+ if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways
581581+ throw new Error('Length over 48 bits not supported at position ' + (stream.pos - 1));
582582+ buf = 0;
583583+ for (let i = 0; i < len; ++i)
584584+ buf = (buf * 256) + stream.get();
585585+ return buf;
374586 }
375375-}
376376-ASN1Tag.prototype.isUniversal = function () {
377377- return this.tagClass === 0x00;
378378-};
379379-ASN1Tag.prototype.isEOC = function () {
380380- return this.tagClass === 0x00 && this.tagNumber === 0x00;
381381-};
382382-ASN1.decode = function (stream) {
383383- if (!(stream instanceof Stream))
384384- stream = new Stream(stream, 0);
385385- var streamStart = new Stream(stream),
386386- tag = new ASN1Tag(stream),
387387- len = ASN1.decodeLength(stream),
388388- start = stream.pos,
389389- header = start - streamStart.pos,
390390- sub = null,
391391- getSub = function () {
392392- sub = [];
393393- if (len !== null) {
394394- // definite length
395395- var end = start + len;
396396- while (stream.pos < end)
397397- sub[sub.length] = ASN1.decode(stream);
398398- if (stream.pos != end)
399399- throw "Content size is not correct for container starting at offset " + start;
400400- } else {
401401- // undefined length
402402- try {
403403- for (;;) {
404404- var s = ASN1.decode(stream);
405405- if (s.tag.isEOC())
406406- break;
407407- sub[sub.length] = s;
587587+ static decode(stream, offset, type = ASN1) {
588588+ if (!(type == ASN1 || type.prototype instanceof ASN1))
589589+ throw new Error('Must pass a class that extends ASN1');
590590+ if (!(stream instanceof Stream))
591591+ stream = new Stream(stream, offset || 0);
592592+ let streamStart = new Stream(stream),
593593+ tag = new ASN1Tag(stream),
594594+ tagLen = stream.pos - streamStart.pos,
595595+ len = ASN1.decodeLength(stream),
596596+ start = stream.pos,
597597+ header = start - streamStart.pos,
598598+ sub = null,
599599+ getSub = function () {
600600+ sub = [];
601601+ if (len !== null) {
602602+ // definite length
603603+ let end = start + len;
604604+ if (end > stream.enc.length)
605605+ throw new Error('Container at offset ' + start + ' has a length of ' + len + ', which is past the end of the stream');
606606+ while (stream.pos < end)
607607+ sub[sub.length] = type.decode(stream);
608608+ if (stream.pos != end)
609609+ throw new Error('Content size is not correct for container at offset ' + start);
610610+ } else {
611611+ // undefined length
612612+ try {
613613+ for (;;) {
614614+ let s = type.decode(stream);
615615+ if (s.tag.isEOC())
616616+ break;
617617+ sub[sub.length] = s;
618618+ }
619619+ len = start - stream.pos; // undefined lengths are represented as negative values
620620+ } catch (e) {
621621+ throw new Error('Exception while decoding undefined length content at offset ' + start + ': ' + e);
622622+ }
623623+ }
624624+ };
625625+ if (tag.tagConstructed) {
626626+ // must have valid content
627627+ getSub();
628628+ } else if (tag.isUniversal() && ((tag.tagNumber == 0x03) || (tag.tagNumber == 0x04))) {
629629+ // sometimes BitString and OctetString are used to encapsulate ASN.1
630630+ try {
631631+ if (tag.tagNumber == 0x03)
632632+ if (stream.get() != 0)
633633+ throw new Error('BIT STRINGs with unused bits cannot encapsulate.');
634634+ getSub();
635635+ for (let s of sub) {
636636+ if (s.tag.isEOC())
637637+ throw new Error('EOC is not supposed to be actual content.');
638638+ try {
639639+ s.content();
640640+ } catch (e) {
641641+ throw new Error('Unable to parse content: ' + e);
408642 }
409409- len = start - stream.pos; // undefined lengths are represented as negative values
410410- } catch (e) {
411411- throw "Exception while decoding undefined length content: " + e;
412643 }
644644+ } catch (ignore) {
645645+ // but silently ignore when they don't
646646+ sub = null;
647647+ //DEBUG console.log('Could not decode structure at ' + start + ':', e);
413648 }
414414- };
415415- if (tag.tagConstructed) {
416416- // must have valid content
417417- getSub();
418418- } else if (tag.isUniversal() && ((tag.tagNumber == 0x03) || (tag.tagNumber == 0x04))) {
419419- if (tag.tagNumber == 0x03) stream.get(); // skip BitString unused bits, must be in [0, 7]
420420- // sometimes BitString and OctetString do contain ASN.1
421421- try {
422422- getSub();
423423- for (var i = 0; i < sub.length; ++i)
424424- if (sub[i].tag.isEOC())
425425- throw 'EOC is not supposed to be actual content.';
426426- } catch (e) {
427427- // but silently ignore when they don't
428428- sub = null;
649649+ }
650650+ if (sub === null) {
651651+ if (len === null)
652652+ throw new Error("We can't skip over an invalid tag with undefined length at offset " + start);
653653+ stream.pos = start + Math.abs(len);
429654 }
655655+ return new type(streamStart, header, len, tag, tagLen, sub);
430656 }
431431- if (sub === null) {
432432- if (len === null)
433433- throw "We can't skip over an invalid tag with undefined length at offset " + start;
434434- stream.pos = start + Math.abs(len);
435435- }
436436- return new ASN1(streamStart, header, len, tag, sub);
437437-};
438657439439-// export globals
440440-if (typeof module !== 'undefined') { module.exports = ASN1; } else { window.ASN1 = ASN1; }
441441-})();
658658+}
+82-64
base64.js
···11// Base64 JavaScript decoder
22-// Copyright (c) 2008-2014 Lapo Luchini <lapo@lapo.it>
22+// Copyright (c) 2008 Lapo Luchini <lapo@lapo.it>
3344// Permission to use, copy, modify, and/or distribute this software for any
55// purpose with or without fee is hereby granted, provided that the above
66// copyright notice and this permission notice appear in all copies.
77-//
77+//
88// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
99// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1010// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
···1313// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1414// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15151616-/*jshint browser: true, strict: true, immed: true, latedef: true, undef: true, regexdash: false */
1717-(function (undefined) {
1818-"use strict";
1616+const
1717+ haveU8 = (typeof Uint8Array == 'function');
1818+1919+let decoder; // populated on first usage
19202020-var Base64 = {},
2121- decoder;
2121+export class Base64 {
22222323-Base64.decode = function (a) {
2424- var i;
2525- if (decoder === undefined) {
2626- var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
2727- ignore = "= \f\n\r\t\u00A0\u2028\u2029";
2828- decoder = [];
2929- for (i = 0; i < 64; ++i)
3030- decoder[b64.charAt(i)] = i;
3131- for (i = 0; i < ignore.length; ++i)
3232- decoder[ignore.charAt(i)] = -1;
3333- }
3434- var out = [];
3535- var bits = 0, char_count = 0;
3636- for (i = 0; i < a.length; ++i) {
3737- var c = a.charAt(i);
3838- if (c == '=')
2323+ static decode(a) {
2424+ let isString = (typeof a == 'string');
2525+ let i;
2626+ if (decoder === undefined) {
2727+ let b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
2828+ ignore = '= \f\n\r\t\u00A0\u2028\u2029';
2929+ decoder = [];
3030+ for (i = 0; i < 64; ++i)
3131+ decoder[b64.charCodeAt(i)] = i;
3232+ for (i = 0; i < ignore.length; ++i)
3333+ decoder[ignore.charCodeAt(i)] = -1;
3434+ // also support decoding Base64url (RFC 4648 section 5)
3535+ decoder['-'.charCodeAt(0)] = decoder['+'.charCodeAt(0)];
3636+ decoder['_'.charCodeAt(0)] = decoder['/'.charCodeAt(0)];
3737+ }
3838+ let out = haveU8 ? new Uint8Array(a.length * 3 >> 2) : [];
3939+ let bits = 0, char_count = 0, len = 0;
4040+ for (i = 0; i < a.length; ++i) {
4141+ let c = isString ? a.charCodeAt(i) : a[i];
4242+ if (c == 61) // '='.charCodeAt(0)
4343+ break;
4444+ c = decoder[c];
4545+ if (c == -1)
4646+ continue;
4747+ if (c === undefined)
4848+ throw 'Illegal character at offset ' + i;
4949+ bits |= c;
5050+ if (++char_count >= 4) {
5151+ out[len++] = (bits >> 16);
5252+ out[len++] = (bits >> 8) & 0xFF;
5353+ out[len++] = bits & 0xFF;
5454+ bits = 0;
5555+ char_count = 0;
5656+ } else {
5757+ bits <<= 6;
5858+ }
5959+ }
6060+ switch (char_count) {
6161+ case 1:
6262+ throw 'Base64 encoding incomplete: at least 2 bits missing';
6363+ case 2:
6464+ out[len++] = (bits >> 10);
3965 break;
4040- c = decoder[c];
4141- if (c == -1)
4242- continue;
4343- if (c === undefined)
4444- throw 'Illegal character at offset ' + i;
4545- bits |= c;
4646- if (++char_count >= 4) {
4747- out[out.length] = (bits >> 16);
4848- out[out.length] = (bits >> 8) & 0xFF;
4949- out[out.length] = bits & 0xFF;
5050- bits = 0;
5151- char_count = 0;
5252- } else {
5353- bits <<= 6;
6666+ case 3:
6767+ out[len++] = (bits >> 16);
6868+ out[len++] = (bits >> 8) & 0xFF;
6969+ break;
5470 }
7171+ if (haveU8 && out.length > len) // in case it was originally longer because of ignored characters
7272+ out = out.subarray(0, len);
7373+ return out;
5574 }
5656- switch (char_count) {
5757- case 1:
5858- throw "Base64 encoding incomplete: at least 2 bits missing";
5959- case 2:
6060- out[out.length] = (bits >> 10);
6161- break;
6262- case 3:
6363- out[out.length] = (bits >> 16);
6464- out[out.length] = (bits >> 8) & 0xFF;
6565- break;
7575+7676+ static pretty(str) {
7777+ // fix padding
7878+ let pad = 4 - str.length % 4;
7979+ if (pad < 4)
8080+ str += '==='.slice(0, pad);
8181+ // convert Base64url (RFC 4648 section 5) to standard Base64 (RFC 4648 section 4)
8282+ str = str.replace(/-/g, '+').replace(/_/g, '/');
8383+ // 80 column width
8484+ return str.replace(/.{80}/g, '$&\n');
6685 }
6767- return out;
6868-};
69867070-Base64.re = /-----BEGIN [^-]+-----([A-Za-z0-9+\/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+\/=\s]+)====/;
7171-Base64.unarmor = function (a) {
7272- var m = Base64.re.exec(a);
7373- if (m) {
7474- if (m[1])
7575- a = m[1];
7676- else if (m[2])
7777- a = m[2];
7878- else
7979- throw "RegExp out of sync";
8787+ static unarmor(a) {
8888+ let m = Base64.re.exec(a);
8989+ if (m) {
9090+ if (m[1])
9191+ a = m[1];
9292+ else if (m[2])
9393+ a = m[2];
9494+ else if (m[3])
9595+ a = m[3];
9696+ else
9797+ throw 'RegExp out of sync';
9898+ }
9999+ return Base64.decode(a);
80100 }
8181- return Base64.decode(a);
8282-};
101101+102102+}
831038484-// export globals
8585-if (typeof module !== 'undefined') { module.exports = Base64; } else { window.Base64 = Base64; }
8686-})();
104104+Base64.re = /-----BEGIN [^-]+-----([A-Za-z0-9+/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+/=\s]+)====|^([A-Za-z0-9+/=\s]+)$/;
···11+CMPv2 example as found on Wireshark page.
22+33+Original link:
44+https://wiki.wireshark.org/CMP
55+66+Attachment found moved here:
77+https://wiki.wireshark.org/uploads/__moin_import__/attachments/SampleCaptures/cmp_IR_sequence_OpenSSL-Cryptlib.pcap
88+99+begin-base64 644 cmpv2.der
1010+MIICPjCB1QIBAqQCMACkRjBEMQswCQYDVQQGEwJERTEMMAoGA1UEChMDTlNOMREwDwYDVQQLEwhQ
1111+RyBSREUgMzEUMBIGA1UEAxMLTWFydGluJ3MgQ0GgERgPMjAxMDA3MDUwNzM1MzhaoTwwOgYJKoZI
1212+hvZ9B0INMC0EEJ5EpSD3zKjvmzHEK5+aoAAwCQYFKw4DAhoFAAICAfQwCgYIKwYBBQUIAQKiCwQJ
1313+b/KGO0ILNJqApBIEEJGOKFG/9crkwU+z/I5ICa6lEgQQnnbd7EB2QjRCwOHt9QWdBKCCAUkwggFF
1414+MIIBQTCBqAIBADCBoqaBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqVTOtjEEYELkomc3sMOy
1515+Too5a9YeC91IMn52cVx7doY4AeO6J9e8p+CtWNbzVF8aRgHUhh31m+/X3MkQOaY5i8nF33uxAxDL
1616+MDXttHjsqrF/tsgYuuHSs/Znz4PA1kLkdhKE9DLiGlCFaJH5QY5Hzl6bcS3ApuWCny0RRzIA1/cC
1717+AwEAAaGBkzANBgkqhkiG9w0BAQUFAAOBgQArOldjg75fDx7BaFp0oAknLDREvB1KyE+BV96R+lB+
1818+tRRhwv3dyc/GTvRw4GtaeDjWCjNPaDCl9ZvvVljaR2aMZvhaQV+DUmCMjFSP3DPiGuszBA6R2azX
1919+NKtnpJ3SGx2vk0+Iv05tXLhdnqQJZs5a3S3R30kn4Vw+4WQm3kb0fKAXAxUA9K8u+7hv5Rg6GDn6
2020+aoPxbUo6fpU=
2121+====
+9
examples/cms-password.p7m
···11+This is a PKCS#7/CMS encrypted with passwod.
22+$ echo content | openssl cms -encrypt -pwri_password test -aes256 -outform pem -out examples/cms-password.p7m
33+-----BEGIN CMS-----
44+MIHYBgkqhkiG9w0BBwOggcowgccCAQMxgYOjgYACAQCgGwYJKoZIhvcNAQUMMA4E
55+CED/DSxXMtH6AgIIADAsBgsqhkiG9w0BCRADCTAdBglghkgBZQMEASoEEDIQbJMC
66+Sfb3LpwHduj/meQEMKwrwq5M4V0stztm6OUTAsFY2zKDY20SApwSEeEcAh9TM42E
77+1palnHeqHTBpC8pIpjA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBByt+scPrdM
88+giR7WUOJyB3hgBDcD3UDMtZSep8X/3yy1/Yq
99+-----END CMS-----
+12
examples/crl-rfc5280.b64
···11+CRL example from RFC5280 as found here:
22+https://csrc.nist.gov/projects/pki-testing/sample-certificates-and-crls
33+44+begin-base64 644 crl-rfc5280.der
55+MIIBYDCBygIBATANBgkqhkiG9w0BAQUFADBDMRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZIm
66+iZPyLGQBGRYHZXhhbXBsZTETMBEGA1UEAxMKRXhhbXBsZSBDQRcNMDUwMjA1MTIwMDAwWhcNMDUw
77+MjA2MTIwMDAwWjAiMCACARIXDTA0MTExOTE1NTcwM1owDDAKBgNVHRUEAwoBAaAvMC0wHwYDVR0j
88+BBgwFoAUCGivhTPIOUp6+IKTjnBqSiCELDIwCgYDVR0UBAMCAQwwDQYJKoZIhvcNAQEFBQADgYEA
99+ItwYffcIzsx10NBqm60Q9HYjtIFutW2+DvsVFGzIF20f7pAXom9g5L2qjFXejoRvkvifEBInr0rU
1010+L4XiNkR9qqNMJTgV/wD9Pn7uPSYS69jnK2LiK8NGgO94gtEVxtCccmrLznrtZ5mLbnCBfUNCdMGm
1111+r8FVF6IzTNYGmCuk/C4=
1212+====
+45
examples/crl-rfc5280.b64.dump
···11+CertificateList SEQUENCE @0+352 (constructed): (3 elem)
22+ tbsCertList TBSCertList SEQUENCE @4+202 (constructed): (7 elem)
33+ version Version INTEGER @7+1: 1
44+ signature AlgorithmIdentifier SEQUENCE @10+13 (constructed): (2 elem)
55+ algorithm OBJECT_IDENTIFIER @12+9: 1.2.840.113549.1.1.5|sha1WithRSAEncryption|PKCS #1
66+ parameters ANY NULL @23+0
77+ issuer rdnSequence Name SEQUENCE @25+67 (constructed): (3 elem)
88+ RelativeDistinguishedName SET @27+19 (constructed): (1 elem)
99+ AttributeTypeAndValue SEQUENCE @29+17 (constructed): (2 elem)
1010+ type AttributeType OBJECT_IDENTIFIER @31+10: 0.9.2342.19200300.100.1.25|domainComponent|Men are from Mars, this OID is from Pluto
1111+ value AttributeValue [?] IA5String @43+3: com
1212+ RelativeDistinguishedName SET @48+23 (constructed): (1 elem)
1313+ AttributeTypeAndValue SEQUENCE @50+21 (constructed): (2 elem)
1414+ type AttributeType OBJECT_IDENTIFIER @52+10: 0.9.2342.19200300.100.1.25|domainComponent|Men are from Mars, this OID is from Pluto
1515+ value AttributeValue [?] IA5String @64+7: example
1616+ RelativeDistinguishedName SET @73+19 (constructed): (1 elem)
1717+ AttributeTypeAndValue SEQUENCE @75+17 (constructed): (2 elem)
1818+ type AttributeType OBJECT_IDENTIFIER @77+3: 2.5.4.3|commonName|X.520 DN component
1919+ value AttributeValue [?] PrintableString @82+10: Example CA
2020+ thisUpdate utcTime Time UTCTime @94+13: 2005-02-05 12:00:00 UTC
2121+ nextUpdate utcTime Time UTCTime @109+13: 2005-02-06 12:00:00 UTC
2222+ revokedCertificates SEQUENCE @124+34 (constructed): (1 elem)
2323+ SEQUENCE @126+32 (constructed): (3 elem)
2424+ userCertificate CertificateSerialNumber INTEGER @128+1: 18
2525+ revocationDate utcTime Time UTCTime @131+13: 2004-11-19 15:57:03 UTC
2626+ crlEntryExtensions Extensions SEQUENCE @146+12 (constructed): (1 elem)
2727+ Extension SEQUENCE @148+10 (constructed): (2 elem)
2828+ extnID OBJECT_IDENTIFIER @150+3: 2.5.29.21|cRLReason|X.509 extension
2929+ extnValue OCTET_STRING @155+3 (encapsulates): (3 byte)|0A0101
3030+ ENUMERATED @157+1: 1
3131+ crlExtensions [0] @160+47 (constructed): (1 elem)
3232+ Extensions SEQUENCE @162+45 (constructed): (2 elem)
3333+ Extension SEQUENCE @164+31 (constructed): (2 elem)
3434+ extnID OBJECT_IDENTIFIER @166+3: 2.5.29.35|authorityKeyIdentifier|X.509 extension
3535+ extnValue OCTET_STRING @171+24 (encapsulates): (24 byte)|301680140868AF8533C8394A7AF882938E706A4A20842C32
3636+ SEQUENCE @173+22 (constructed): (1 elem)
3737+ [0] @175+20: (20 byte)|0868AF8533C8394A7AF882938E706A4A20842C32
3838+ Extension SEQUENCE @197+10 (constructed): (2 elem)
3939+ extnID OBJECT_IDENTIFIER @199+3: 2.5.29.20|cRLNumber|X.509 extension
4040+ extnValue OCTET_STRING @204+3 (encapsulates): (3 byte)|02010C
4141+ INTEGER @206+1: 12
4242+ signatureAlgorithm AlgorithmIdentifier SEQUENCE @209+13 (constructed): (2 elem)
4343+ algorithm OBJECT_IDENTIFIER @211+9: 1.2.840.113549.1.1.5|sha1WithRSAEncryption|PKCS #1
4444+ parameters ANY NULL @222+0
4545+ signature BIT_STRING @224+129: (1024 bit)|0010001011011100000110000111110111110111000010001100111011001100011101011101000011010000011010101001101110101101000100001111010001110110001000111011010010000001011011101011010101101101101111100000111011111011000101010001010001101100110010000001011101101101000111111110111010010000000101111010001001101111011000001110010010111101101010101000110001010101110111101000111010000100011011111001001011111000100111110001000000010010001001111010111101001010110101000010111110000101111000100011011001000100011111011010101010100011010011000010010100111000000101011111111100000000111111010011111001111110111011100011110100100110000100101110101111011000111001110010101101100010111000100010101111000011010001101000000011101111011110001000001011010001000101011100011011010000100111000111001001101010110010111100111001111010111011010110011110011001100010110110111001110000100000010111110101000011010000100111010011000001101001101010111111000001010101010001011110100010001100110100110011010110000001101001100000101011101001001111110000101110
+13
examples/ed25519.cer
···11+X.509 certificate based on Daniel J. Bernsteinโs Curve25519 (as per RFC 8410).
22+$ openssl req -x509 -newkey ed25519 -keyout test.key -out test.cer -days 3652 -subj '/C=IT/L=Milano/CN=Test ed25519'
33+-----BEGIN CERTIFICATE-----
44+MIIBfzCCATGgAwIBAgIUfI5kSdcO2S0+LkpdL3b2VUJG10YwBQYDK2VwMDUxCzAJ
55+BgNVBAYTAklUMQ8wDQYDVQQHDAZNaWxhbm8xFTATBgNVBAMMDFRlc3QgZWQyNTUx
66+OTAeFw0yMDA5MDIxMzI1MjZaFw0zMDA5MDIxMzI1MjZaMDUxCzAJBgNVBAYTAklU
77+MQ8wDQYDVQQHDAZNaWxhbm8xFTATBgNVBAMMDFRlc3QgZWQyNTUxOTAqMAUGAytl
88+cAMhADupL/3LF2beQKKS95PeMPgKI6gxIV3QB9hjJC7/aCGFo1MwUTAdBgNVHQ4E
99+FgQUa6W9z536I1l4EmQXrh5y2JqASugwHwYDVR0jBBgwFoAUa6W9z536I1l4EmQX
1010+rh5y2JqASugwDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQBvc3e+KJZaMzbX5TT9
1111+kPP9QH8fAvkAV/IWDxZrBL9lhLaY0tDSv0zWbw624uidBKPgmVD5wm3ec60dNVeF
1212+ZYYG
1313+-----END CERTIFICATE-----
···11+LDAPMessage example as found on ldap.com.
22+33+Original link:
44+https://ldap.com/ldapv3-wire-protocol-reference-ldap-message/
55+66+begin-base64 644 ldapmessage.der
77+MDUCAQVKEWRjPWV4YW1wbGUsZGM9Y29toB0wGwQWMS4yLjg0MC4xMTM1NTYuMS40LjgwNQEB/w==
88+====
···11+This is a PKCS#7/CMS detached digital signature.
22+It is an old example generated in 2008 and as such uses obsolete cryptography: RSA1024 with SHA1.
33+-----BEGIN PKCS7-----
44+MIIDUAYJKoZIhvcNAQcCoIIDQTCCAz0CAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3
55+DQEHAaCCAfMwggHvMIIBWKADAgECAhAvoXazbunwSfREtACZZhlFMA0GCSqGSIb3
66+DQEBBQUAMAwxCjAIBgNVBAMMAWEwHhcNMDgxMDE1MTUwMzQxWhcNMDkxMDE1MTUw
77+MzQxWjAMMQowCAYDVQQDDAFhMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJ
88+Uwlwhu5hR8X01f+vG0mKPRHsVRjpZNxSEmsmFPdDiD9kylE3ertTDf0gRkpIvWfN
99+J+eymuxoXF0Qgl5gXAVuSrjupGD6J+VapixJiwLXJHokmDihLs3zfGARz08O3qnO
1010+5ofBy0pRxq5isu/bAAcjoByZ1sI/g0iAuotC1UFObwIDAQABo1IwUDAOBgNVHQ8B
1111+Af8EBAMCBPAwHQYDVR0OBBYEFEIGXQB4h+04Z3y/n7Nv94+CqPitMB8GA1UdIwQY
1212+MBaAFEIGXQB4h+04Z3y/n7Nv94+CqPitMA0GCSqGSIb3DQEBBQUAA4GBAE0G7tAi
1313+aacJxvP3fhEj+yP9VDxL0omrRRAEaMXwWaBf/Ggk1T/u+8/CDAdjuGNCiF6ctooK
1414+c8u8KpnZJsGqnpGQ4n6L2KjTtRUDh+hija0eJRBFdirPQe2HAebQGFnmOk6Mn7Ki
1515+QfBIsOzXim/bFqaBSbf06bLTQNwFouSO+jwOMYIBJTCCASECAQEwIDAMMQowCAYD
1616+VQQDDAFhAhAvoXazbunwSfREtACZZhlFMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0B
1717+CQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0wODEwMTUxNTAzNDNaMCMG
1818+CSqGSIb3DQEJBDEWBBQAAAAAAAAAAAAAAAAAAAAAAAAAADANBgkqhkiG9w0BAQEF
1919+AASBgHQe0ocjBn+ZVXWbb8CpZ2CxzFKiVrgZxZO2kMBwJoNyZx+jnICHdhfsX4Or
2020+cF5vIYVAZIRz5RxqFmuZELTfZ/K89zaK873DP9V7/ftBGpezWEp9h29AtAzI9lzS
2121+GB9gugiyB5JstXoM1L87KJmT05MeZxg1pvvFhwo1m/QOpcqz
2222+-----END PKCS7-----
···11-๏ปฟ/*jshint browser: true, strict: true, globalstrict: true, indent: 4, immed: true, latedef: true, undef: true, regexdash: false */
22-/*global Hex, Base64, ASN1 */
33-"use strict";
11+import './theme.js';
22+import { ASN1DOM } from './dom.js';
33+import { Base64 } from './base64.js';
44+import { Hex } from './hex.js';
55+import { Defs } from './defs.js';
66+import { tags } from './tags.js';
4755-var reHex = /^\s*(?:[0-9A-Fa-f][0-9A-Fa-f]\s*)+$/,
66- hash = null;
88+const
99+ maxLength = 10240,
1010+ reHex = /^\s*(?:[0-9A-Fa-f][0-9A-Fa-f]\s*)+$/,
1111+ tree = id('tree'),
1212+ dump = id('dump'),
1313+ wantHex = checkbox('wantHex'),
1414+ trimHex = checkbox('trimHex'),
1515+ wantDef = checkbox('wantDef'),
1616+ area = id('area'),
1717+ file = id('file'),
1818+ examples = id('examples'),
1919+ selectDefs = id('definitions'),
2020+ selectTag = id('tags');
2121+2222+let hash = null;
2323+2424+if (!window.console || !window.console.log) // IE8 with closed developer tools
2525+ window.console = { log: function () {} };
726function id(elem) {
827 return document.getElementById(elem);
928}
1010-function toHTML(obj) {
1111- return String(obj).replace(/</g, "<");
2929+function text(el, string) {
3030+ if ('textContent' in el) el.textContent = string;
3131+ else el.innerText = string;
1232}
1313-function decode(der) {
1414- var tree = id('tree');
1515- var dump = id('dump');
3333+function checkbox(name) {
3434+ const el = id(name);
3535+ const cfg = localStorage.getItem(name);
3636+ if (cfg === 'false')
3737+ el.checked = false;
3838+ el.onchange = () => localStorage.setItem(name, el.checked);
3939+ return el;
4040+}
4141+function show(asn1) {
1642 tree.innerHTML = '';
1743 dump.innerHTML = '';
4444+ let ul = document.createElement('ul');
4545+ ul.className = 'treecollapse';
4646+ tree.appendChild(ul);
4747+ ul.appendChild(asn1.toDOM());
4848+ if (wantHex.checked) dump.appendChild(asn1.toHexDOM(undefined, trimHex.checked));
4949+}
5050+export function decode(der, offset) {
5151+ offset = offset || 0;
1852 try {
1919- var asn1 = ASN1.decode(der),
2020- hex = asn1.toHexString();
2121- tree.appendChild(asn1.toDOM());
2222- if (id('wantHex').checked)
2323- dump.appendChild(asn1.toHexDOM());
2424- if (id('pem').value === '')
2525- id('pem').value = hex;
2626- // update URL hash (does this have length limits we should avoid?)
2727- hash = '#' + hex;
2828- window.location.hash = hash;
5353+ const asn1 = ASN1DOM.decode(der, offset);
5454+ if (wantDef.checked) {
5555+ selectDefs.innerHTML = '';
5656+ const types = Defs.commonTypes
5757+ .map(type => {
5858+ const stats = Defs.match(asn1, type);
5959+ return { type, match: stats.recognized / stats.total };
6060+ })
6161+ .sort((a, b) => b.match - a.match);
6262+ for (const t of types) {
6363+ t.element = document.createElement('option');
6464+ t.element.innerText = (t.match * 100).toFixed(1) + '% ' + t.type.description;
6565+ selectDefs.appendChild(t.element);
6666+ }
6767+ let not = document.createElement('option');
6868+ not.innerText = 'no definition';
6969+ selectDefs.appendChild(not);
7070+ Defs.match(asn1, types[0].type);
7171+ selectDefs.onchange = () => {
7272+ for (const t of types) {
7373+ if (t.element == selectDefs.selectedOptions[0]) {
7474+ Defs.match(asn1, t.type);
7575+ show(asn1);
7676+ return;
7777+ }
7878+ }
7979+ Defs.match(asn1, null);
8080+ show(asn1);
8181+ };
8282+ } else
8383+ selectDefs.innerHTML = '<option>no definition</option>';
8484+ show(asn1);
8585+ let b64 = der.length < maxLength ? asn1.toB64String() : '';
8686+ if (area.value === '') area.value = Base64.pretty(b64);
8787+ try {
8888+ window.location.hash = hash = '#' + b64;
8989+ } catch (ignore) {
9090+ // fails with "Access Denied" on IE with URLs longer than ~2048 chars
9191+ window.location.hash = hash = '#';
9292+ }
9393+ let endOffset = asn1.posEnd();
9494+ if (endOffset < der.length) {
9595+ let p = document.createElement('p');
9696+ p.innerText = 'Input contains ' + (der.length - endOffset) + ' more bytes to decode.';
9797+ let button = document.createElement('button');
9898+ button.innerText = 'try to decode';
9999+ button.onclick = function () {
100100+ decode(der, endOffset);
101101+ };
102102+ p.appendChild(button);
103103+ tree.insertBefore(p, tree.firstChild);
104104+ }
29105 } catch (e) {
3030- tree.innerHTML = toHTML(e);
106106+ text(tree, e);
31107 }
3232- return false;
33108}
3434-function decodeArea() {
109109+export function decodeText(val) {
35110 try {
3636- var pem = id('pem').value;
3737- var der = reHex.test(pem) ? Hex.decode(pem) : Base64.unarmor(pem);
111111+ let der = reHex.test(val) ? Hex.decode(val) : Base64.unarmor(val);
38112 decode(der);
39113 } catch (e) {
4040- id('tree').innerHTML = toHTML(e);
4141- id('dump').innerHTML = '';
114114+ text(tree, e);
115115+ dump.innerHTML = '';
42116 }
4343- return false;
44117}
4545-function decodeBinaryString(str) {
4646- var der;
118118+export function decodeBinaryString(str) {
119119+ let der;
47120 try {
4848- if (reHex.test(str))
4949- der = Hex.decode(str);
5050- else if (Base64.re.test(str))
5151- der = Base64.unarmor(str);
5252- else {
5353- der = [];
5454- for (var i = 0; i < str.length; ++i)
5555- der[der.length] = str.charCodeAt(i);
5656- }
121121+ if (reHex.test(str)) der = Hex.decode(str);
122122+ else if (Base64.re.test(str)) der = Base64.unarmor(str);
123123+ else der = str;
57124 decode(der);
5858- } catch (e) {
5959- id('tree').innerHTML = 'Cannot decode file.';
6060- id('dump').innerHTML = '';
125125+ } catch (ignore) {
126126+ text(tree, 'Cannot decode file.');
127127+ dump.innerHTML = '';
61128 }
6262- return false;
63129}
6464-function clearAll() {
6565- id('pem').value = '';
6666- id('tree').innerHTML = '';
6767- id('dump').innerHTML = '';
6868- hash = window.location.hash = '';
6969- return false;
130130+// set up buttons
131131+const butClickHandlers = {
132132+ butDecode: () => {
133133+ decodeText(area.value);
134134+ },
135135+ butClear: () => {
136136+ area.value = '';
137137+ file.value = '';
138138+ tree.innerHTML = '';
139139+ dump.innerHTML = '';
140140+ selectDefs.innerHTML = '';
141141+ hash = window.location.hash = '';
142142+ },
143143+ butExample: () => {
144144+ console.log('Loading example:', examples.value);
145145+ let request = new XMLHttpRequest();
146146+ request.open('GET', 'examples/' + examples.value, true);
147147+ request.onreadystatechange = function () {
148148+ if (this.readyState !== 4) return;
149149+ if (this.status >= 200 && this.status < 400) {
150150+ area.value = this.responseText;
151151+ decodeText(this.responseText);
152152+ } else {
153153+ console.log('Error loading example.');
154154+ }
155155+ };
156156+ request.send();
157157+ },
158158+};
159159+for (const [name, onClick] of Object.entries(butClickHandlers)) {
160160+ let elem = id(name);
161161+ if (elem)
162162+ elem.onclick = onClick;
70163}
71164// this is only used if window.FileReader
72165function read(f) {
7373- id('pem').value = ''; // clear text area, will get hex content
7474- var r = new FileReader();
166166+ area.value = ''; // clear text area, will get b64 content
167167+ let r = new FileReader();
75168 r.onloadend = function () {
7676- if (r.error) {
7777- alert("Your browser couldn't read the specified file (error code " + r.error.code + ").");
7878- } else
7979- decodeBinaryString(r.result);
169169+ if (r.error) alert("Your browser couldn't read the specified file (error code " + r.error.code + ').');
170170+ else decodeBinaryString(r.result);
80171 };
81172 r.readAsBinaryString(f);
82173}
83174function load() {
8484- var file = id('file');
8585- if (file.files.length === 0) {
8686- alert("Select a file to load first.");
8787- return false;
8888- }
8989- read(file.files[0]);
9090- return false;
175175+ if (file.files.length === 0) alert('Select a file to load first.');
176176+ else read(file.files[0]);
91177}
92178function loadFromHash() {
93179 if (window.location.hash && window.location.hash != hash) {
94180 hash = window.location.hash;
9595- id('pem').value = hash.substring(1);
9696- decodeArea();
181181+ // Firefox is not consistent with other browsers and returns an
182182+ // already-decoded hash string so we risk double-decoding here,
183183+ // but since % is not allowed in base64 nor hexadecimal, it's ok
184184+ let val = decodeURIComponent(hash.substr(1));
185185+ if (val.length) decodeText(val);
97186 }
98187}
99188function stop(e) {
···102191}
103192function dragAccept(e) {
104193 stop(e);
105105- if (e.dataTransfer.files.length > 0)
106106- read(e.dataTransfer.files[0]);
194194+ if (e.dataTransfer.files.length > 0) read(e.dataTransfer.files[0]);
107195}
108108-window.onload = function () {
109109- if ('onhashchange' in window)
110110- window.onhashchange = loadFromHash;
111111- loadFromHash();
112112- document.ondragover = stop;
113113- document.ondragleave = stop;
114114- if ('FileReader' in window) {
115115- id('file').style.display = 'block';
116116- id('file').onchange = load;
117117- document.ondrop = dragAccept;
118118- }
196196+// main
197197+if ('onhashchange' in window) window.onhashchange = loadFromHash;
198198+loadFromHash();
199199+document.ondragover = stop;
200200+document.ondragleave = stop;
201201+if ('FileReader' in window && 'readAsBinaryString' in new FileReader()) {
202202+ file.style.display = 'block';
203203+ file.onchange = load;
204204+ document.ondrop = dragAccept;
205205+}
206206+for (let tag in tags) {
207207+ let date = tags[tag];
208208+ let el = document.createElement('option');
209209+ el.value = tag;
210210+ el.innerText = date + ' ' + tag;
211211+ selectTag.appendChild(el);
212212+}
213213+selectTag.onchange = function (ev) {
214214+ let tag = ev.target.selectedOptions[0].value;
215215+ window.location.href = 'https://rawcdn.githack.com/lapo-luchini/asn1js/' + tag + '/index.html';
119216};
+90-48
int10.js
···11// Big integer base-10 printing library
22-// Copyright (c) 2014 Lapo Luchini <lapo@lapo.it>
22+// Copyright (c) 2008 Lapo Luchini <lapo@lapo.it>
3344// Permission to use, copy, modify, and/or distribute this software for any
55// purpose with or without fee is hereby granted, provided that the above
66// copyright notice and this permission notice appear in all copies.
77-//
77+//
88// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
99// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1010// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
···1313// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1414// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15151616-/*jshint browser: true, strict: true, immed: true, latedef: true, undef: true, regexdash: false */
1717-(function () {
1818-"use strict";
1616+/** Biggest 10^n integer that can still fit 2^53 when multiplied by 256. */
1717+const max = 10000000000000;
19182020-var max = 10000000000000; // biggest integer that can still fit 2^53 when multiplied by 256
1919+export class Int10 {
2020+ /**
2121+ * Arbitrary length base-10 value.
2222+ * @param {number} value - Optional initial value (will be 0 otherwise).
2323+ */
2424+ constructor(value) {
2525+ this.buf = [+value || 0];
2626+ }
21272222-function Int10(value) {
2323- this.buf = [+value || 0];
2424-}
2828+ /**
2929+ * Multiply value by m and add c.
3030+ * @param {number} m - multiplier, must be 0<m<=256
3131+ * @param {number} c - value to add, must be c>=0
3232+ */
3333+ mulAdd(m, c) {
3434+ // assert(m > 0)
3535+ // assert(m <= 256)
3636+ // assert(c >= 0)
3737+ let b = this.buf,
3838+ l = b.length,
3939+ i, t;
4040+ for (i = 0; i < l; ++i) {
4141+ t = b[i] * m + c;
4242+ if (t < max)
4343+ c = 0;
4444+ else {
4545+ c = 0|(t / max);
4646+ t -= c * max;
4747+ }
4848+ b[i] = t;
4949+ }
5050+ if (c > 0)
5151+ b[i] = c;
5252+ }
25532626-Int10.prototype.mulAdd = function (m, c) {
2727- // assert(m <= 256)
2828- var b = this.buf,
2929- l = b.length,
3030- i, t;
3131- for (i = 0; i < l; ++i) {
3232- t = b[i] * m + c;
3333- if (t < max)
3434- c = 0;
3535- else {
3636- c = 0|(t / max);
3737- t -= c * max;
5454+ /**
5555+ * Subtract value.
5656+ * @param {number} c - value to subtract
5757+ */
5858+ sub(c) {
5959+ let b = this.buf,
6060+ l = b.length,
6161+ i, t;
6262+ for (i = 0; i < l; ++i) {
6363+ t = b[i] - c;
6464+ if (t < 0) {
6565+ t += max;
6666+ c = 1;
6767+ } else
6868+ c = 0;
6969+ b[i] = t;
3870 }
3939- b[i] = t;
7171+ while (b[b.length - 1] === 0)
7272+ b.pop();
4073 }
4141- if (c > 0)
4242- b[i] = c;
4343-};
44744545-Int10.prototype.toString = function (base) {
4646- if ((base || 10) != 10)
4747- throw 'only base 10 is supported';
4848- var b = this.buf,
4949- s = b[b.length - 1].toString();
5050- for (var i = b.length - 2; i >= 0; --i)
5151- s += (max + b[i]).toString().substring(1);
5252- return s;
5353-};
7575+ /**
7676+ * Convert to decimal string representation.
7777+ * @param {number} [base=10] - optional value, only value accepted is 10
7878+ * @returns {string} The decimal string representation.
7979+ */
8080+ toString(base = 10) {
8181+ if (base != 10)
8282+ throw new Error('only base 10 is supported');
8383+ let b = this.buf,
8484+ s = b[b.length - 1].toString();
8585+ for (let i = b.length - 2; i >= 0; --i)
8686+ s += (max + b[i]).toString().substring(1);
8787+ return s;
8888+ }
54895555-Int10.prototype.valueOf = function () {
5656- var b = this.buf,
5757- v = 0;
5858- for (var i = b.length - 1; i >= 0; --i)
5959- v = v * max + b[i];
6060- return v;
6161-};
9090+ /**
9191+ * Convert to Number value representation.
9292+ * Will probably overflow 2^53 and thus become approximate.
9393+ * @returns {number} The numeric value.
9494+ */
9595+ valueOf() {
9696+ let b = this.buf,
9797+ v = 0;
9898+ for (let i = b.length - 1; i >= 0; --i)
9999+ v = v * max + b[i];
100100+ return v;
101101+ }
621026363-Int10.prototype.simplify = function () {
6464- var b = this.buf;
6565- return (b.length == 1) ? b[0] : this;
6666-};
103103+ /**
104104+ * Return value as a simple Number (if it is <= 10000000000000), or return this.
105105+ * @returns {number | Int10} The simplified value.
106106+ */
107107+ simplify() {
108108+ let b = this.buf;
109109+ return (b.length == 1) ? b[0] : this;
110110+ }
671116868-// export globals
6969-if (typeof module !== 'undefined') { module.exports = Int10; } else { window.Int10 = Int10; }
7070-})();
112112+}
···11-#/bin/sh
22-URL='https://www.cs.auckland.ac.nz/~pgut001/dumpasn1.cfg'
33-if [ -x /usr/bin/fetch ]; then
44- /usr/bin/fetch -m --no-verify-peer $URL
55-elif [ -x /usr/bin/wget ]; then
66- /usr/bin/wget -N --no-check-certificate $URL
77-elif [ ! -r dumpasn1.cfg ]; then
88- echo Please download $URL in this directory.
99- exit 1
1010-fi
1111-cat dumpasn1.cfg | \
1212-tr -d '\r' | \
1313-awk -v url="$URL" '
1414- function clean() {
1515- oid = "";
1616- comment = "";
1717- description = "";
1818- warning = "false";
1919- }
2020- BEGIN {
2121- FS = "= *";
2222- apos = sprintf("%c", 39);
2323- clean();
2424- print "// Converted from: " url;
2525- print "// which is made by Peter Gutmann and whose license states:";
2626- print "// You can use this code in whatever way you want,";
2727- print "// as long as you don" apos "t try to claim you wrote it.";
2828- print "oids = {";
2929- }
3030- /^OID/ { oid = $2; }
3131- /^Comment/ { comment = $2; }
3232- /^Description/ { description = $2; }
3333- /^Warning/ { warning = "true"; }
3434- /^$/ {
3535- if (length(oid) > 0) {
3636- gsub(" ", ".", oid);
3737- gsub("\"", "\\\"", description);
3838- gsub("\"", "\\\"", comment);
3939- if (++seen[oid] > 1)
4040- print "Duplicate OID in line " NR ": " oid > "/dev/stderr";
4141- else
4242- printf "\"%s\": { \"d\": \"%s\", \"c\": \"%s\", \"w\": %s },\n", oid, description, comment, warning;
4343- clean();
4444- }
4545- }
4646- END {
4747- print "\"END\": \"\""
4848- print "};"
4949- }
5050-' >oids.js
5151-echo Conversion completed.
+49
updateOID.sh
···11+#/bin/sh
22+URL='https://www.cs.auckland.ac.nz/~pgut001/dumpasn1.cfg'
33+if [ -x /usr/bin/fetch ]; then
44+ /usr/bin/fetch -m --no-verify-peer $URL
55+elif [ -x /usr/bin/wget ]; then
66+ /usr/bin/wget -N --no-check-certificate $URL
77+elif [ ! -r dumpasn1.cfg ]; then
88+ echo Please download $URL in this directory.
99+ exit 1
1010+fi
1111+cat dumpasn1.cfg | \
1212+tr -d '\r' | \
1313+awk -v apos="'" -v q='"' -v url="$URL" '
1414+ function clean() {
1515+ oid = "";
1616+ comment = "";
1717+ description = "";
1818+ warning = "";
1919+ }
2020+ BEGIN {
2121+ FS = "= *";
2222+ clean();
2323+ print "// Converted from: " url;
2424+ print "// which is made by Peter Gutmann and whose license states:";
2525+ print "// You can use this code in whatever way you want,";
2626+ print "// as long as you don" apos "t try to claim you wrote it.";
2727+ print "export const oids = {";
2828+ }
2929+ /^OID/ { oid = $2; }
3030+ /^Comment/ { comment = $2; }
3131+ /^Description/ { description = $2; }
3232+ /^Warning/ { warning = ", \"w\": true"; }
3333+ /^$/ {
3434+ if (length(oid) > 0) {
3535+ gsub(" ", ".", oid);
3636+ gsub("\"", "\\\"", description);
3737+ gsub("\"", "\\\"", comment);
3838+ if (++seen[oid] > 1)
3939+ print "Duplicate OID in line " NR ": " oid > "/dev/stderr";
4040+ else
4141+ printf "\"%s\": { \"d\": \"%s\", \"c\": \"%s\"%s },\n", oid, description, comment, warning;
4242+ clean();
4343+ }
4444+ }
4545+ END {
4646+ print "};"
4747+ }
4848+' >oids.js
4949+echo Conversion completed.
+32
updateRFC.sh
···11+#/bin/sh
22+RFCs="5280 5208 3369 3161 2986 4211 4210 8017 4511"
33+downloadRFC() {
44+ URL="https://www.ietf.org/rfc/rfc$1.txt"
55+ if [ -x /usr/bin/fetch ]; then
66+ /usr/bin/fetch -m --no-verify-peer $URL
77+ elif [ -x /usr/bin/wget ]; then
88+ /usr/bin/wget -N --no-check-certificate $URL
99+ elif [ ! -r dumpasn1.cfg ]; then
1010+ echo Please download $URL in this directory.
1111+ exit 1
1212+ fi
1313+}
1414+echo '{}' > rfcdef.json # start from scratch
1515+mkdir -p rfc
1616+cd rfc
1717+for n in $RFCs; do
1818+ downloadRFC $n
1919+ ../parseRFC.js rfc$n.txt ../rfcdef.json
2020+done
2121+cd ..
2222+{
2323+ echo "// content parsed from ASN.1 definitions as found in the following RFCs: $RFCs"
2424+ echo "// Copyright (C) The IETF Trust (2008)"
2525+ echo "// as far as I can tell this file is allowed under the following clause:"
2626+ echo "// It is acceptable under the current IETF rules (RFC 5378) to modify extracted code if necessary."
2727+ echo "// https://trustee.ietf.org/about/faq/#reproducing-rfcs"
2828+ echo -n "export const rfcdef = "
2929+ cat rfcdef.json
3030+ echo ";"
3131+} > rfcdef.js
3232+echo Conversion completed.