···11ISC License
2233-Copyright (c) 2008-2022 Lapo Luchini <lapo@lapo.it>
33+Copyright (c) 2008-2025 Lapo Luchini <lapo@lapo.it>
4455Permission to use, copy, modify, and/or distribute this software for any
66purpose with or without fee is hereby granted, provided that the above
+53-21
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](https://lapo.it/asn1js/) or [offline (ZIP file)](https://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`.
7788-Usage with `npm` / `yarn`
99--------------------------
88+Usage with `nodejs`
99+-------------------
10101111This package can be installed with either npm or yarn via the following commands:
12121313```sh
1414npm install @lapo/asn1js
1515-# or with yarn
1515+# or other tools
1616+pnpm install @lapo/asn1js
1617yarn add @lapo/asn1js
1718```
18191919-Assuming a standard javascript bundler is setup you can import it like so:
2020+You can import the classes like this:
20212122```js
2222-const ASN1 = require('@lapo/asn1js');
2323-// or with ES modules
2424-import ASN1 from '@lapo/asn1js';
2323+import { ASN1 } from '@lapo/asn1js';
2524```
26252726A submodule of this package can also be imported:
28272928```js
3030-const Hex = require('@lapo/asn1js/hex');
3131-// or with ES modules
3232-import Hex from '@lapo/asn1js/hex';
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());
3339```
34403535-Usage with RequireJS
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
3654--------------------
37553838-Can be [tested on JSFiddle](https://jsfiddle.net/lapo/tmdq35ug/).
5656+Can be [tested on JSFiddle](https://jsfiddle.net/lapo/y6t2wo7q/).
39574058```html
4141-<script type="text/javascript" src="https://unpkg.com/requirejs/require.js"></script>
4259<script>
4343-require([
4444- 'https://unpkg.com/@lapo/asn1js/asn1.js',
4545- 'https://unpkg.com/@lapo/asn1js/hex.js'
4646-], function(ASN1, Hex) {
4747- document.body.innerText = ASN1.decode(Hex.decode('06032B6570')).content();
4848-});
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();
4964</script>
5065```
51666767+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+```
8080+5281ISC license
5382-----------
54835555-ASN.1 JavaScript decoder Copyright (c) 2008-2022 Lapo Luchini <lapo@lapo.it>
8484+ASN.1 JavaScript decoder Copyright (c) 2008-2025 Lapo Luchini <lapo@lapo.it>
56855786Permission 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.
5887···6695- extended tag support added by [Pรฉter Budai](https://www.peterbudai.eu/)
6796- patches by [Gergely Nagy](https://github.com/ngg)
6897- 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)
6910070101links
71102-----
7210373104- [official website](https://lapo.it/asn1js/)
105105+- [dedicated domain](https://asn1js.eu/)
74106- [InDefero tracker](http://idf.lapo.it/p/asn1js/)
75107- [GitHub mirror](https://github.com/lapo-luchini/asn1js)
76108- [Ohloh code stats](https://www.openhub.net/p/asn1js)
+583-526
asn1.js
···11// ASN.1 JavaScript decoder
22-// Copyright (c) 2008-2022 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-(typeof define != 'undefined' ? define : function (factory) { 'use strict';
1717- if (typeof module == 'object') module.exports = factory(function (name) { return require(name); });
1818- else window.asn1 = factory(function (name) { return window[name.substring(2)]; });
1919-})(function (require) {
2020-"use strict";
1616+import { Int10 } from './int10.js';
1717+import { oids } from './oids.js';
21182222-var Int10 = require('./int10'),
2323- oids = require('./oids'),
2424- ellipsis = "\u2026",
1919+const
2020+ ellipsis = '\u2026',
2521 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)?)?$/,
2626- 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)?)?$/;
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+ ];
27442845function stringCut(str, len) {
2946 if (str.length > len)
···3148 return str;
3249}
33503434-function Stream(enc, pos) {
3535- if (enc instanceof Stream) {
3636- this.enc = enc.enc;
3737- this.pos = enc.pos;
3838- } else {
3939- // enc should be an array or a binary string
4040- this.enc = enc;
4141- 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) + ')');
4257 }
4358}
4444-Stream.prototype.get = function (pos) {
4545- if (pos === undefined)
4646- pos = this.pos++;
4747- if (pos >= this.enc.length)
4848- throw 'Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length;
4949- return (typeof this.enc == "string") ? this.enc.charCodeAt(pos) : this.enc[pos];
5050-};
5151-Stream.prototype.hexDigits = "0123456789ABCDEF";
5252-Stream.prototype.hexByte = function (b) {
5353- return this.hexDigits.charAt((b >> 4) & 0xF) + this.hexDigits.charAt(b & 0xF);
5454-};
5555-Stream.prototype.hexDump = function (start, end, raw) {
5656- var s = "";
5757- for (var i = start; i < end; ++i) {
5858- s += this.hexByte(this.get(i));
5959- if (raw !== true)
6060- switch (i & 0xF) {
6161- case 0x7: s += " "; break;
6262- case 0xF: s += "\n"; break;
6363- default: s += " ";
6464- }
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');
6584 }
6666- return s;
6767-};
6868-var b64Safe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
6969-Stream.prototype.b64Dump = function (start, end) {
7070- var extra = (end - start) % 3,
7171- s = '',
7272- i, c;
7373- for (i = start; i + 2 < end; i += 3) {
7474- c = this.get(i) << 16 | this.get(i + 1) << 8 | this.get(i + 2);
7575- s += b64Safe.charAt(c >> 18 & 0x3F);
7676- s += b64Safe.charAt(c >> 12 & 0x3F);
7777- s += b64Safe.charAt(c >> 6 & 0x3F);
7878- s += b64Safe.charAt(c & 0x3F);
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);
7993 }
8080- if (extra > 0) {
8181- c = this.get(i) << 16;
8282- if (extra > 1) c |= this.get(i + 1) << 8;
8383- s += b64Safe.charAt(c >> 18 & 0x3F);
8484- s += b64Safe.charAt(c >> 12 & 0x3F);
8585- if (extra == 2) s += b64Safe.charAt(c >> 6 & 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);
8698 }
8787- return s;
8888-};
8989-Stream.prototype.isASCII = function (start, end) {
9090- for (var i = start; i < end; ++i) {
9191- var c = this.get(i);
9292- if (c < 32 || c > 176)
9393- return false;
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;
94117 }
9595- return true;
9696-};
9797-Stream.prototype.parseStringISO = function (start, end, maxLength) {
9898- var s = "";
9999- for (var i = start; i < end; ++i)
100100- s += String.fromCharCode(this.get(i));
101101- return { size: s.length, str: stringCut(s, maxLength) };
102102-};
103103-var tableT61 = [
104104- ['', ''],
105105- ['AEIOUaeiou', 'รรรรรร รจรฌรฒรน'], // Grave
106106- ['ACEILNORSUYZacegilnorsuyz', 'รฤรรฤนลรลลรรลนรกฤรฉฤฃรญฤบลรณลลรบรฝลบ'], // Acute
107107- ['ACEGHIJOSUWYaceghijosuwy', 'รฤรฤฤครฤดรลรลดลถรขฤรชฤฤฅรฎฤตรดลรปลตลท'], // Circumflex
108108- ['AINOUainou', 'รฤจรรลจรฃฤฉรฑรตลฉ'], // Tilde
109109- ['AEIOUaeiou', 'ฤฤฤชลลชฤฤฤซลลซ'], // Macron
110110- ['AGUagu', 'ฤฤลฌฤฤลญ'], // Breve
111111- ['CEGIZcegz', 'ฤฤฤ ฤฐลปฤฤฤกลผ'], // Dot
112112- ['AEIOUYaeiouy', 'รรรรรลธรครซรฏรถรผรฟ'], // Umlaut or diรฆresis
113113- ['', ''],
114114- ['AUau', 'ร ลฎรฅลฏ'], // Ring
115115- ['CGKLNRSTcklnrst', 'รฤขฤถฤปล ลลลขรงฤทฤผลลลลฃ'], // Cedilla
116116- ['', ''],
117117- ['OUou', 'ลลฐลลฑ'], // Double Acute
118118- ['AEIUaeiu', 'ฤฤฤฎลฒฤ ฤฤฏลณ'], // Ogonek
119119- ['CDELNRSTZcdelnrstz', 'ฤฤฤฤฝลลล ลคลฝฤฤฤฤพลลลกลฅลพ'] // Caron
120120-];
121121-Stream.prototype.parseStringT61 = function (start, end, maxLength) {
122122- // warning: this code is not very well tested so far
123123- function merge(c, d) {
124124- var t = tableT61[c - 0xC0];
125125- var i = t[0].indexOf(String.fromCharCode(d));
126126- return (i < 0) ? '\0' : t[1].charAt(i);
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;
127143 }
128128- var s = "", c;
129129- for (var i = start; i < end; ++i) {
130130- c = this.get(i);
131131- if (c >= 0xA4 && c <= 0xBF)
132132- s += '$ยฅ#ยงยค\0\0ยซ\0\0\0\0ยฐยฑยฒยณรยตยถยทรท\0\0ยปยผยฝยพยฟ'.charAt(c - 0xA4);
133133- else if (c >= 0xE0 && c <= 0xFF)
134134- s += 'โฆรรยชฤฆ\0ฤฒฤฟลรลยบรลฆลลฤธรฆฤรฐฤงฤฑฤณลลรธลรรพลงล\0'.charAt(c - 0xE0);
135135- else if (c >= 0xC0 && c <= 0xCF)
136136- s += merge(c, this.get(++i));
137137- else // using ISO 8859-1 for characters undefined (or equal) in T.61
138138- s += String.fromCharCode(c);
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;
149149+ }
150150+ return true;
139151 }
140140- return { size: s.length, str: stringCut(s, maxLength) };
141141-};
142142-Stream.prototype.parseStringUTF = function (start, end, maxLength) {
143143- function ex(c) { // must be 10xxxxxx
144144- if ((c < 0x80) || (c >= 0xC0))
145145- throw new Error('Invalid UTF-8 continuation byte: ' + c);
146146- return (c & 0x3F);
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) };
147157 }
148148- function surrogate(cp) {
149149- if (cp < 0x10000)
150150- throw new Error('UTF-8 overlong encoding, codepoint encoded in 4 bytes: ' + cp);
151151- // we could use String.fromCodePoint(cp) but let's be nice to older browsers and use surrogate pairs
152152- cp -= 0x10000;
153153- return String.fromCharCode((cp >> 10) + 0xD800, (cp & 0x3FF) + 0xDC00);
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);
164164+ }
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) };
154178 }
155155- var s = "";
156156- for (var i = start; i < end; ) {
157157- var c = this.get(i++);
158158- if (c < 0x80) // 0xxxxxxx (7 bit)
159159- s += String.fromCharCode(c);
160160- else if (c < 0xC0)
161161- throw new Error('Invalid UTF-8 starting byte: ' + c);
162162- else if (c < 0xE0) // 110xxxxx 10xxxxxx (11 bit)
163163- s += String.fromCharCode(((c & 0x1F) << 6) | ex(this.get(i++)));
164164- else if (c < 0xF0) // 1110xxxx 10xxxxxx 10xxxxxx (16 bit)
165165- s += String.fromCharCode(((c & 0x0F) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++)));
166166- else if (c < 0xF8) // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (21 bit)
167167- s += surrogate(((c & 0x07) << 18) | (ex(this.get(i++)) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++)));
168168- else
169169- throw new Error('Invalid UTF-8 starting byte (since 2003 it is restricted to 4 bytes): ' + c);
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);
191191+ }
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) };
170209 }
171171- return { size: s.length, str: stringCut(s, maxLength) };
172172-};
173173-Stream.prototype.parseStringBMP = function (start, end, maxLength) {
174174- var s = "", hi, lo;
175175- for (var i = start; i < end; ) {
176176- hi = this.get(i++);
177177- lo = this.get(i++);
178178- s += String.fromCharCode((hi << 8) | lo);
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) };
179218 }
180180- return { size: s.length, str: stringCut(s, maxLength) };
181181-};
182182-Stream.prototype.parseTime = function (start, end, shortYear) {
183183- var s = this.parseStringISO(start, end).str,
184184- m = (shortYear ? reTimeS : reTimeL).exec(s);
185185- if (!m)
186186- return "Unrecognized time: " + s;
187187- if (shortYear) {
188188- // to avoid querying the timer, use the fixed range [1970, 2069]
189189- // it will conform with ITU X.400 [-10, +40] sliding window until 2030
190190- m[1] = +m[1];
191191- m[1] += (m[1] < 70) ? 2000 : 1900;
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;
192245 }
193193- s = m[1] + "-" + m[2] + "-" + m[3] + " " + m[4];
194194- if (m[5]) {
195195- s += ":" + m[5];
196196- if (m[6]) {
197197- s += ":" + m[6];
198198- if (m[7])
199199- s += "." + m[7];
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';
200267 }
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();
201274 }
202202- if (m[8]) {
203203- s += " UTC";
204204- if (m[9])
205205- s += m[9] + ":" + (m[10] || "00");
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';
286286+ if (s.length > maxLength)
287287+ s = stringCut(s, maxLength);
288288+ }
289289+ return { size: lenBit, str: s };
206290 }
207207- return s;
208208-};
209209-Stream.prototype.parseInteger = function (start, end) {
210210- var v = this.get(start),
211211- neg = (v > 127),
212212- pad = neg ? 255 : 0,
213213- len,
214214- s = '';
215215- // skip unuseful bits (not allowed in DER)
216216- while (v == pad && ++start < end)
217217- v = this.get(start);
218218- len = end - start;
219219- if (len === 0)
220220- return neg ? '-1' : '0';
221221- // show bit length of huge integers
222222- if (len > 4) {
223223- s = v;
224224- len <<= 3;
225225- while (((s ^ pad) & 0x80) == 0) {
226226- s <<= 1;
227227- --len;
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 (e) {
299299+ // ignore
228300 }
229229- s = "(" + len + " bit)\n";
230230- }
231231- // decode the integer
232232- if (neg) v = v - 256;
233233- var n = new Int10(v);
234234- for (var i = start + 1; i < end; ++i)
235235- n.mulAdd(256, this.get(i));
236236- return s + n.toString();
237237-};
238238-Stream.prototype.parseBitString = function (start, end, maxLength) {
239239- var unusedBits = this.get(start);
240240- if (unusedBits > 7)
241241- throw 'Invalid BitString with unusedBits=' + unusedBits;
242242- var lenBit = ((end - start - 1) << 3) - unusedBits,
243243- s = "";
244244- for (var i = start + 1; i < end; ++i) {
245245- var b = this.get(i),
246246- skip = (i == end - 1) ? unusedBits : 0;
247247- for (var j = 7; j >= skip; --j)
248248- s += (b >> j) & 1 ? "1" : "0";
249249- if (s.length > maxLength)
250250- s = stringCut(s, maxLength);
251251- }
252252- return { size: lenBit, str: s };
253253-};
254254-function checkPrintable(s) {
255255- var i, v;
256256- for (i = 0; i < s.length; ++i) {
257257- v = s.charCodeAt(i);
258258- if (v < 32 && v != 9 && v != 10 && v != 13) // [\t\r\n] are (kinda) printable
259259- throw new Error('Unprintable character at index ' + i + ' (code ' + s.str.charCodeAt(i) + ")");
260260- }
261261-}
262262-Stream.prototype.parseOctetString = function (start, end, maxLength) {
263263- var len = end - start,
264264- s;
265265- try {
266266- s = this.parseStringUTF(start, end, maxLength);
267267- checkPrintable(s.str);
268268- return { size: end - start, str: s.str };
269269- } catch (e) {
270270- // ignore
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 };
271310 }
272272- maxLength /= 2; // we work in bytes
273273- if (len > maxLength)
274274- end = start + maxLength;
275275- s = '';
276276- for (var i = start; i < end; ++i)
277277- s += this.hexByte(this.get(i));
278278- if (len > maxLength)
279279- s += ellipsis;
280280- return { size: len, str: s };
281281-};
282282-Stream.prototype.parseOID = function (start, end, maxLength, isRelative) {
283283- var s = '',
284284- n = new Int10(),
285285- bits = 0;
286286- for (var i = start; i < end; ++i) {
287287- var v = this.get(i);
288288- n.mulAdd(128, v & 0x7F);
289289- bits += 7;
290290- if (!(v & 0x80)) { // finished
291291- if (s === '') {
292292- n = n.simplify();
293293- if (isRelative) {
294294- s = (n instanceof Int10) ? n.toString() : "" + n;
295295- } else if (n instanceof Int10) {
296296- n.sub(80);
297297- s = "2." + n.toString();
298298- } else {
299299- var m = n < 80 ? n < 40 ? 0 : 1 : 2;
300300- s = m + "." + (n - m * 40);
301301- }
302302- } else
303303- s += "." + n.toString();
304304- if (s.length > maxLength)
305305- return stringCut(s, maxLength);
306306- n = new Int10();
311311+ parseOID(start, end, maxLength, isRelative) {
312312+ let s = '',
313313+ n = new Int10(),
307314 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+ }
308338 }
309309- }
310310- if (bits > 0)
311311- s += ".incomplete";
312312- if (typeof oids === 'object' && !isRelative) {
313313- var oid = oids[s];
314314- if (oid) {
315315- if (oid.d) s += "\n" + oid.d;
316316- if (oid.c) s += "\n" + oid.c;
317317- if (oid.w) s += "\n(warning!)";
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+ }
318348 }
349349+ return s;
319350 }
320320- return s;
321321-};
322322-Stream.prototype.parseRelativeOID = function (start, end, maxLength) {
323323- return this.parseOID(start, end, maxLength, true);
324324-};
325325-326326-function ASN1(stream, header, length, tag, tagLen, sub) {
327327- if (!(tag instanceof ASN1Tag)) throw 'Invalid tag value.';
328328- this.stream = stream;
329329- this.header = header;
330330- this.length = length;
331331- this.tag = tag;
332332- this.tagLen = tagLen;
333333- this.sub = sub;
351351+ parseRelativeOID(start, end, maxLength) {
352352+ return this.parseOID(start, end, maxLength, true);
353353+ }
334354}
335335-ASN1.prototype.typeName = function () {
336336- switch (this.tag.tagClass) {
337337- case 0: // universal
338338- switch (this.tag.tagNumber) {
339339- case 0x00: return "EOC";
340340- case 0x01: return "BOOLEAN";
341341- case 0x02: return "INTEGER";
342342- case 0x03: return "BIT_STRING";
343343- case 0x04: return "OCTET_STRING";
344344- case 0x05: return "NULL";
345345- case 0x06: return "OBJECT_IDENTIFIER";
346346- case 0x07: return "ObjectDescriptor";
347347- case 0x08: return "EXTERNAL";
348348- case 0x09: return "REAL";
349349- case 0x0A: return "ENUMERATED";
350350- case 0x0B: return "EMBEDDED_PDV";
351351- case 0x0C: return "UTF8String";
352352- case 0x0D: return "RELATIVE_OID";
353353- case 0x10: return "SEQUENCE";
354354- case 0x11: return "SET";
355355- case 0x12: return "NumericString";
356356- case 0x13: return "PrintableString"; // ASCII subset
357357- case 0x14: return "TeletexString"; // aka T61String
358358- case 0x15: return "VideotexString";
359359- case 0x16: return "IA5String"; // ASCII
360360- case 0x17: return "UTCTime";
361361- case 0x18: return "GeneralizedTime";
362362- case 0x19: return "GraphicString";
363363- case 0x1A: return "VisibleString"; // ASCII subset
364364- case 0x1B: return "GeneralString";
365365- case 0x1C: return "UniversalString";
366366- case 0x1E: return "BMPString";
367367- }
368368- return "Universal_" + this.tag.tagNumber.toString();
369369- case 1: return "Application_" + this.tag.tagNumber.toString();
370370- case 2: return "[" + this.tag.tagNumber.toString() + "]"; // Context
371371- case 3: return "Private_" + this.tag.tagNumber.toString();
372372- }
373373-};
355355+374356function recurse(el, parser, maxLength) {
375375- var avoidRecurse = true;
357357+ let avoidRecurse = true;
376358 if (el.tag.tagConstructed && el.sub) {
377359 avoidRecurse = false;
378360 el.sub.forEach(function (e1) {
···382364 }
383365 if (avoidRecurse)
384366 return el.stream[parser](el.posContent(), el.posContent() + Math.abs(el.length), maxLength);
385385- var d = { size: 0, str: '' };
367367+ let d = { size: 0, str: '' };
386368 el.sub.forEach(function (el) {
387387- var d1 = recurse(el, parser, maxLength - d.str.length);
369369+ let d1 = recurse(el, parser, maxLength - d.str.length);
388370 d.size += d1.size;
389371 d.str += d1.str;
390372 });
391373 return d;
392374}
393393-/** A string preview of the content (intended for humans). */
394394-ASN1.prototype.content = function (maxLength) {
395395- if (this.tag === undefined)
375375+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+ }
397397+}
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();
446446+ }
447447+ }
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+ return (this.stream.get(content) === 0) ? 'false' : 'true';
465465+ case 0x02: // INTEGER
466466+ return this.stream.parseInteger(content, content + len);
467467+ case 0x03: { // BIT_STRING
468468+ let d = recurse(this, 'parseBitString', maxLength);
469469+ return '(' + d.size + ' bit)\n' + d.str;
470470+ }
471471+ case 0x04: { // OCTET_STRING
472472+ let d = recurse(this, 'parseOctetString', maxLength);
473473+ return '(' + d.size + ' byte)\n' + d.str;
474474+ }
475475+ //case 0x05: // NULL
476476+ case 0x06: // OBJECT_IDENTIFIER
477477+ return this.stream.parseOID(content, content + len, maxLength);
478478+ //case 0x07: // ObjectDescriptor
479479+ //case 0x08: // EXTERNAL
480480+ //case 0x09: // REAL
481481+ case 0x0A: // ENUMERATED
482482+ return this.stream.parseInteger(content, content + len);
483483+ //case 0x0B: // EMBEDDED_PDV
484484+ case 0x0D: // RELATIVE-OID
485485+ return this.stream.parseRelativeOID(content, content + len, maxLength);
486486+ case 0x10: // SEQUENCE
487487+ case 0x11: // SET
488488+ if (this.sub !== null)
489489+ return '(' + this.sub.length + ' elem)';
490490+ else
491491+ return '(no elem)';
492492+ case 0x0C: // UTF8String
493493+ return recurse(this, 'parseStringUTF', maxLength).str;
494494+ case 0x14: // TeletexString
495495+ return recurse(this, 'parseStringT61', maxLength).str;
496496+ case 0x12: // NumericString
497497+ case 0x13: // PrintableString
498498+ case 0x15: // VideotexString
499499+ case 0x16: // IA5String
500500+ case 0x1A: // VisibleString
501501+ case 0x1B: // GeneralString
502502+ //case 0x19: // GraphicString
503503+ //case 0x1C: // UniversalString
504504+ return recurse(this, 'parseStringISO', maxLength).str;
505505+ case 0x1E: // BMPString
506506+ return recurse(this, 'parseStringBMP', maxLength).str;
507507+ case 0x17: // UTCTime
508508+ case 0x18: // GeneralizedTime
509509+ return this.stream.parseTime(content, content + len, (this.tag.tagNumber == 0x17));
510510+ }
396511 return null;
397397- if (maxLength === undefined)
398398- maxLength = Infinity;
399399- var content = this.posContent(),
400400- len = Math.abs(this.length);
401401- if (!this.tag.isUniversal()) {
402402- if (this.sub !== null)
403403- return "(" + this.sub.length + " elem)";
404404- var d1 = this.stream.parseOctetString(content, content + len, maxLength);
405405- return "(" + d1.size + " byte)\n" + d1.str;
406512 }
407407- switch (this.tag.tagNumber) {
408408- case 0x01: // BOOLEAN
409409- return (this.stream.get(content) === 0) ? "false" : "true";
410410- case 0x02: // INTEGER
411411- return this.stream.parseInteger(content, content + len);
412412- case 0x03: // BIT_STRING
413413- var d = recurse(this, 'parseBitString', maxLength);
414414- return "(" + d.size + " bit)\n" + d.str;
415415- case 0x04: // OCTET_STRING
416416- d = recurse(this, 'parseOctetString', maxLength);
417417- return "(" + d.size + " byte)\n" + d.str;
418418- //case 0x05: // NULL
419419- case 0x06: // OBJECT_IDENTIFIER
420420- return this.stream.parseOID(content, content + len, maxLength);
421421- //case 0x07: // ObjectDescriptor
422422- //case 0x08: // EXTERNAL
423423- //case 0x09: // REAL
424424- case 0x0A: // ENUMERATED
425425- return this.stream.parseInteger(content, content + len);
426426- //case 0x0B: // EMBEDDED_PDV
427427- case 0x0D: // RELATIVE-OID
428428- return this.stream.parseRelativeOID(content, content + len, maxLength);
429429- case 0x10: // SEQUENCE
430430- case 0x11: // SET
431431- if (this.sub !== null)
432432- return "(" + this.sub.length + " elem)";
433433- else
434434- return "(no elem)";
435435- case 0x0C: // UTF8String
436436- return recurse(this, 'parseStringUTF', maxLength).str;
437437- case 0x14: // TeletexString
438438- return recurse(this, 'parseStringT61', maxLength).str;
439439- case 0x12: // NumericString
440440- case 0x13: // PrintableString
441441- case 0x15: // VideotexString
442442- case 0x16: // IA5String
443443- case 0x1A: // VisibleString
444444- case 0x1B: // GeneralString
445445- //case 0x19: // GraphicString
446446- //case 0x1C: // UniversalString
447447- return recurse(this, 'parseStringISO', maxLength).str;
448448- case 0x1E: // BMPString
449449- return recurse(this, 'parseStringBMP', maxLength).str;
450450- case 0x17: // UTCTime
451451- case 0x18: // GeneralizedTime
452452- return this.stream.parseTime(content, content + len, (this.tag.tagNumber == 0x17));
513513+ toString() {
514514+ return this.typeName() + '@' + this.stream.pos + '[header:' + this.header + ',length:' + this.length + ',sub:' + ((this.sub === null) ? 'null' : this.sub.length) + ']';
453515 }
454454- return null;
455455-};
456456-ASN1.prototype.toString = function () {
457457- return this.typeName() + "@" + this.stream.pos + "[header:" + this.header + ",length:" + this.length + ",sub:" + ((this.sub === null) ? 'null' : this.sub.length) + "]";
458458-};
459459-ASN1.prototype.toPrettyString = function (indent) {
460460- if (indent === undefined) indent = '';
461461- var s = indent + this.typeName() + " @" + this.stream.pos;
462462- if (this.length >= 0)
463463- s += "+";
464464- s += this.length;
465465- if (this.tag.tagConstructed)
466466- s += " (constructed)";
467467- else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null))
468468- s += " (encapsulates)";
469469- var content = this.content();
470470- if (content)
471471- s += ": " + content.replace(/\n/g, '|');
472472- s += "\n";
473473- if (this.sub !== null) {
474474- indent += ' ';
475475- for (var i = 0, max = this.sub.length; i < max; ++i)
476476- s += this.sub[i].toPrettyString(indent);
516516+ toPrettyString(indent) {
517517+ if (indent === undefined) indent = '';
518518+ let s = indent;
519519+ if (this.def) {
520520+ if (this.def.id)
521521+ s += this.def.id + ' ';
522522+ if (this.def.name && this.def.name != this.typeName().replace(/_/g, ' '))
523523+ s+= this.def.name + ' ';
524524+ if (this.def.mismatch)
525525+ s += '[?] ';
526526+ }
527527+ s += this.typeName() + ' @' + this.stream.pos;
528528+ if (this.length >= 0)
529529+ s += '+';
530530+ s += this.length;
531531+ if (this.tag.tagConstructed)
532532+ s += ' (constructed)';
533533+ else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null))
534534+ s += ' (encapsulates)';
535535+ let content = this.content();
536536+ if (content)
537537+ s += ': ' + content.replace(/\n/g, '|');
538538+ s += '\n';
539539+ if (this.sub !== null) {
540540+ indent += ' ';
541541+ for (let i = 0, max = this.sub.length; i < max; ++i)
542542+ s += this.sub[i].toPrettyString(indent);
543543+ }
544544+ return s;
545545+ }
546546+ posStart() {
547547+ return this.stream.pos;
548548+ }
549549+ posContent() {
550550+ return this.stream.pos + this.header;
551551+ }
552552+ posEnd() {
553553+ return this.stream.pos + this.header + Math.abs(this.length);
554554+ }
555555+ /** Position of the length. */
556556+ posLen() {
557557+ return this.stream.pos + this.tagLen;
558558+ }
559559+ /** Hexadecimal dump of the node.
560560+ * @param type 'raw', 'byte' or 'dump' */
561561+ toHexString(type = 'raw') {
562562+ return this.stream.hexDump(this.posStart(), this.posEnd(), type);
563563+ }
564564+ /** Base64url dump of the node (according to RFC 4648 section 5).
565565+ * @param {string} type 'url' (default, section 5 without padding) or 'std' (section 4 with padding)
566566+ */
567567+ toB64String(type = 'url') {
568568+ return this.stream.b64Dump(this.posStart(), this.posEnd(), type);
477569 }
478478- return s;
479479-};
480480-ASN1.prototype.posStart = function () {
481481- return this.stream.pos;
482482-};
483483-ASN1.prototype.posContent = function () {
484484- return this.stream.pos + this.header;
485485-};
486486-ASN1.prototype.posEnd = function () {
487487- return this.stream.pos + this.header + Math.abs(this.length);
488488-};
489489-/** Position of the length. */
490490-ASN1.prototype.posLen = function() {
491491- return this.stream.pos + this.tagLen;
492492-};
493493-ASN1.prototype.toHexString = function () {
494494- return this.stream.hexDump(this.posStart(), this.posEnd(), true);
495495-};
496496-ASN1.prototype.toB64String = function () {
497497- return this.stream.b64Dump(this.posStart(), this.posEnd());
498498-};
499499-ASN1.decodeLength = function (stream) {
500500- var buf = stream.get(),
501501- len = buf & 0x7F;
502502- if (len == buf) // first bit was 0, short form
503503- return len;
504504- if (len === 0) // long form with length 0 is a special case
505505- return null; // undefined length
506506- if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways
507507- throw "Length over 48 bits not supported at position " + (stream.pos - 1);
508508- buf = 0;
509509- for (var i = 0; i < len; ++i)
510510- buf = (buf * 256) + stream.get();
511511- return buf;
512512-};
513513-function ASN1Tag(stream) {
514514- var buf = stream.get();
515515- this.tagClass = buf >> 6;
516516- this.tagConstructed = ((buf & 0x20) !== 0);
517517- this.tagNumber = buf & 0x1F;
518518- if (this.tagNumber == 0x1F) { // long tag
519519- var n = new Int10();
520520- do {
521521- buf = stream.get();
522522- n.mulAdd(128, buf & 0x7F);
523523- } while (buf & 0x80);
524524- this.tagNumber = n.simplify();
570570+ static decodeLength(stream) {
571571+ let buf = stream.get(),
572572+ len = buf & 0x7F;
573573+ if (len == buf) // first bit was 0, short form
574574+ return len;
575575+ if (len === 0) // long form with length 0 is a special case
576576+ return null; // undefined length
577577+ if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways
578578+ throw new Error('Length over 48 bits not supported at position ' + (stream.pos - 1));
579579+ buf = 0;
580580+ for (let i = 0; i < len; ++i)
581581+ buf = (buf * 256) + stream.get();
582582+ return buf;
525583 }
526526-}
527527-ASN1Tag.prototype.isUniversal = function () {
528528- return this.tagClass === 0x00;
529529-};
530530-ASN1Tag.prototype.isEOC = function () {
531531- return this.tagClass === 0x00 && this.tagNumber === 0x00;
532532-};
533533-ASN1.decode = function (stream, offset) {
534534- if (!(stream instanceof Stream))
535535- stream = new Stream(stream, offset || 0);
536536- var streamStart = new Stream(stream),
537537- tag = new ASN1Tag(stream),
538538- tagLen = stream.pos - streamStart.pos,
539539- len = ASN1.decodeLength(stream),
540540- start = stream.pos,
541541- header = start - streamStart.pos,
542542- sub = null,
543543- getSub = function () {
544544- sub = [];
545545- if (len !== null) {
546546- // definite length
547547- var end = start + len;
548548- if (end > stream.enc.length)
549549- throw 'Container at offset ' + start + ' has a length of ' + len + ', which is past the end of the stream';
550550- while (stream.pos < end)
551551- sub[sub.length] = ASN1.decode(stream);
552552- if (stream.pos != end)
553553- throw 'Content size is not correct for container at offset ' + start;
554554- } else {
555555- // undefined length
556556- try {
557557- for (;;) {
558558- var s = ASN1.decode(stream);
559559- if (s.tag.isEOC())
560560- break;
561561- sub[sub.length] = s;
584584+ static decode(stream, offset, type = ASN1) {
585585+ if (!(type == ASN1 || type.prototype instanceof ASN1))
586586+ throw new Error('Must pass a class that extends ASN1');
587587+ if (!(stream instanceof Stream))
588588+ stream = new Stream(stream, offset || 0);
589589+ let streamStart = new Stream(stream),
590590+ tag = new ASN1Tag(stream),
591591+ tagLen = stream.pos - streamStart.pos,
592592+ len = ASN1.decodeLength(stream),
593593+ start = stream.pos,
594594+ header = start - streamStart.pos,
595595+ sub = null,
596596+ getSub = function () {
597597+ sub = [];
598598+ if (len !== null) {
599599+ // definite length
600600+ let end = start + len;
601601+ if (end > stream.enc.length)
602602+ throw new Error('Container at offset ' + start + ' has a length of ' + len + ', which is past the end of the stream');
603603+ while (stream.pos < end)
604604+ sub[sub.length] = type.decode(stream);
605605+ if (stream.pos != end)
606606+ throw new Error('Content size is not correct for container at offset ' + start);
607607+ } else {
608608+ // undefined length
609609+ try {
610610+ for (;;) {
611611+ let s = type.decode(stream);
612612+ if (s.tag.isEOC())
613613+ break;
614614+ sub[sub.length] = s;
615615+ }
616616+ len = start - stream.pos; // undefined lengths are represented as negative values
617617+ } catch (e) {
618618+ throw new Error('Exception while decoding undefined length content at offset ' + start + ': ' + e);
562619 }
563563- len = start - stream.pos; // undefined lengths are represented as negative values
564564- } catch (e) {
565565- throw 'Exception while decoding undefined length content at offset ' + start + ': ' + e;
620620+ }
621621+ };
622622+ if (tag.tagConstructed) {
623623+ // must have valid content
624624+ getSub();
625625+ } else if (tag.isUniversal() && ((tag.tagNumber == 0x03) || (tag.tagNumber == 0x04))) {
626626+ // sometimes BitString and OctetString are used to encapsulate ASN.1
627627+ try {
628628+ if (tag.tagNumber == 0x03)
629629+ if (stream.get() != 0)
630630+ throw new Error('BIT STRINGs with unused bits cannot encapsulate.');
631631+ getSub();
632632+ for (let s of sub) {
633633+ if (s.tag.isEOC())
634634+ throw new Error('EOC is not supposed to be actual content.');
635635+ try {
636636+ s.content();
637637+ } catch (e) {
638638+ throw new Error('Unable to parse content: ' + e);
639639+ }
566640 }
641641+ } catch (e) {
642642+ // but silently ignore when they don't
643643+ sub = null;
644644+ //DEBUG console.log('Could not decode structure at ' + start + ':', e);
567645 }
568568- };
569569- if (tag.tagConstructed) {
570570- // must have valid content
571571- getSub();
572572- } else if (tag.isUniversal() && ((tag.tagNumber == 0x03) || (tag.tagNumber == 0x04))) {
573573- // sometimes BitString and OctetString are used to encapsulate ASN.1
574574- try {
575575- if (tag.tagNumber == 0x03)
576576- if (stream.get() != 0)
577577- throw "BIT STRINGs with unused bits cannot encapsulate.";
578578- getSub();
579579- for (var i = 0; i < sub.length; ++i)
580580- if (sub[i].tag.isEOC())
581581- throw 'EOC is not supposed to be actual content.';
582582- } catch (e) {
583583- // but silently ignore when they don't
584584- sub = null;
585585- //DEBUG console.log('Could not decode structure at ' + start + ':', e);
646646+ }
647647+ if (sub === null) {
648648+ if (len === null)
649649+ throw new Error("We can't skip over an invalid tag with undefined length at offset " + start);
650650+ stream.pos = start + Math.abs(len);
586651 }
652652+ return new type(streamStart, header, len, tag, tagLen, sub);
587653 }
588588- if (sub === null) {
589589- if (len === null)
590590- throw "We can't skip over an invalid tag with undefined length at offset " + start;
591591- stream.pos = start + Math.abs(len);
592592- }
593593- return new ASN1(streamStart, header, len, tag, tagLen, sub);
594594-};
595654596596-return ASN1;
597597-598598-});
655655+}
+79-82
base64.js
···11// Base64 JavaScript decoder
22-// Copyright (c) 2008-2022 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-(typeof define != 'undefined' ? define : function (factory) { 'use strict';
1717- if (typeof module == 'object') module.exports = factory();
1818- else window.base64 = factory();
1919-})(function () {
2020-"use strict";
1616+const
1717+ haveU8 = (typeof Uint8Array == 'function');
1818+1919+let decoder; // populated on first usage
21202222-var Base64 = {},
2323- decoder, // populated on first usage
2424- haveU8 = (typeof Uint8Array == 'function');
2121+export class Base64 {
25222626-Base64.decode = function (a) {
2727- var isString = (typeof a == 'string');
2828- var i;
2929- if (decoder === undefined) {
3030- var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
3131- ignore = "= \f\n\r\t\u00A0\u2028\u2029";
3232- decoder = [];
3333- for (i = 0; i < 64; ++i)
3434- decoder[b64.charCodeAt(i)] = i;
3535- for (i = 0; i < ignore.length; ++i)
3636- decoder[ignore.charCodeAt(i)] = -1;
3737- // RFC 3548 URL & file safe encoding
3838- decoder['-'.charCodeAt(0)] = decoder['+'.charCodeAt(0)];
3939- decoder['_'.charCodeAt(0)] = decoder['/'.charCodeAt(0)];
4040- }
4141- var out = haveU8 ? new Uint8Array(a.length * 3 >> 2) : [];
4242- var bits = 0, char_count = 0, len = 0;
4343- for (i = 0; i < a.length; ++i) {
4444- var c = isString ? a.charCodeAt(i) : a[i];
4545- if (c == 61) // '='.charCodeAt(0)
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);
4665 break;
4747- c = decoder[c];
4848- if (c == -1)
4949- continue;
5050- if (c === undefined)
5151- throw 'Illegal character at offset ' + i;
5252- bits |= c;
5353- if (++char_count >= 4) {
6666+ case 3:
5467 out[len++] = (bits >> 16);
5568 out[len++] = (bits >> 8) & 0xFF;
5656- out[len++] = bits & 0xFF;
5757- bits = 0;
5858- char_count = 0;
5959- } else {
6060- bits <<= 6;
6969+ break;
6170 }
6262- }
6363- switch (char_count) {
6464- case 1:
6565- throw "Base64 encoding incomplete: at least 2 bits missing";
6666- case 2:
6767- out[len++] = (bits >> 10);
6868- break;
6969- case 3:
7070- out[len++] = (bits >> 16);
7171- out[len++] = (bits >> 8) & 0xFF;
7272- break;
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;
7374 }
7474- if (haveU8 && out.length > len) // in case it was originally longer because of ignored characters
7575- out = out.subarray(0, len);
7676- return out;
7777-};
78757979-Base64.pretty = function (str) {
8080- // fix padding
8181- if (str.length % 4 > 0)
8282- str = (str + '===').slice(0, str.length + str.length % 4);
8383- // convert RFC 3548 to standard Base64
8484- str = str.replace(/-/g, '+').replace(/_/g, '/');
8585- // 80 column width
8686- return str.replace(/(.{80})/g, '$1\n');
8787-};
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');
8585+ }
88868989-Base64.re = /-----BEGIN [^-]+-----([A-Za-z0-9+/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+/=\s]+)====|^([A-Za-z0-9+/=\s]+)$/;
9090-Base64.unarmor = function (a) {
9191- var m = Base64.re.exec(a);
9292- if (m) {
9393- if (m[1])
9494- a = m[1];
9595- else if (m[2])
9696- a = m[2];
9797- else if (m[3])
9898- a = m[3];
9999- else
100100- 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);
101100 }
102102- return Base64.decode(a);
103103-};
104101105105-return Base64;
102102+}
106103107107-});
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+const
22+ id = (elem) => document.getElementById(elem),
33+ contextMenu = id('contextmenu'),
44+ btnCopyHex = id('btnCopyHex'),
55+ btnCopyB64 = id('btnCopyB64'),
66+ btnCopyTree = id('btnCopyTree'),
77+ btnCopyValue = id('btnCopyValue');
88+99+export function bindContextMenu(node) {
1010+ const type = node.asn1.typeName();
1111+ const valueEnabled = type != 'SET' && type != 'SEQUENCE';
1212+ node.onclick = function (event) {
1313+ // do not show the menu in case of clicking the icon
1414+ if (event.srcElement.nodeName != 'SPAN') return;
1515+ contextMenu.style.left = event.pageX + 'px';
1616+ contextMenu.style.top = event.pageY + 'px';
1717+ contextMenu.style.visibility = 'visible';
1818+ contextMenu.node = this;
1919+ btnCopyValue.style.display = valueEnabled ? 'block' : 'none';
2020+ event.preventDefault();
2121+ event.stopPropagation();
2222+ };
2323+}
2424+2525+function close(event) {
2626+ contextMenu.style.visibility = 'hidden';
2727+ event.stopPropagation();
2828+}
2929+3030+contextMenu.onmouseleave = close;
3131+3232+btnCopyHex.onclick = function (event) {
3333+ navigator.clipboard.writeText(contextMenu.node.asn1.toHexString('byte'));
3434+ close(event);
3535+};
3636+3737+btnCopyB64.onclick = function (event) {
3838+ event.stopPropagation();
3939+ navigator.clipboard.writeText(contextMenu.node.asn1.toB64String());
4040+ close(event);
4141+};
4242+4343+btnCopyTree.onclick = function (event) {
4444+ event.stopPropagation();
4545+ navigator.clipboard.writeText(contextMenu.node.asn1.toPrettyString());
4646+ close(event);
4747+};
4848+4949+btnCopyValue.onclick = function (event) {
5050+ event.stopPropagation();
5151+ navigator.clipboard.writeText(contextMenu.node.asn1.content());
5252+ close(event);
5353+};
+141
defs.js
···11+// ASN.1 RFC definitions matcher
22+// Copyright (c) 2023 Lapo Luchini <lapo@lapo.it>
33+44+// 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+//
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
1111+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1212+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1313+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1414+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1515+1616+import { rfcdef } from './rfcdef.js';
1717+1818+function translate(def, tn, stats) {
1919+ if (def?.type == 'tag' && !def.explicit)
2020+ // def.type = def.content[0].type;
2121+ def = def.content[0].type;
2222+ if (def?.definedBy)
2323+ try {
2424+ // hope current OIDs contain the type name (will need to parse from RFC itself)
2525+ def = Defs.searchType(firstUpper(stats.defs[def.definedBy][1]));
2626+ } catch (e) { /*ignore*/ }
2727+ while (def?.type == 'defined' || def?.type?.type == 'defined') {
2828+ const name = def?.type?.type ? def.type.name : def.name;
2929+ def = Object.assign({}, def);
3030+ def.type = Defs.searchType(name).type;
3131+ }
3232+ if (def?.name == 'CHOICE' || def?.type?.name == 'CHOICE') {
3333+ for (let c of def.content ?? def.type.content) {
3434+ if (tn != c.type.name && tn != c.name)
3535+ c = translate(c);
3636+ if (tn == c.type.name || tn == c.name) {
3737+ def = Object.assign({}, def);
3838+ if (c.id) def.id = c.id;
3939+ def.type = c.type.name ? c.type : c;
4040+ break;
4141+ }
4242+ }
4343+ }
4444+ const id = def?.id;
4545+ if (id)
4646+ def = Object.assign({}, def, { id });
4747+ return def ?? { type: {} };
4848+}
4949+5050+function firstUpper(s) {
5151+ return s[0].toUpperCase() + s.slice(1);
5252+}
5353+5454+export class Defs {
5555+5656+ static moduleAndType(mod, name) {
5757+ return Object.assign({ module: { oid: mod.oid, name: mod.name, source: mod.source } }, mod.types[name]);
5858+ }
5959+6060+ static searchType(name) {
6161+ for (const mod of Object.values(rfcdef))
6262+ if (name in mod.types) {
6363+ // console.log(name + ' found in ' + r.name);
6464+ // return r.types[name];
6565+ return Defs.moduleAndType(mod, name);
6666+ }
6767+ throw new Error('Type not found: ' + name);
6868+ }
6969+7070+ static match(value, def, stats = { total: 0, recognized: 0, defs: {} }) {
7171+ value.def = {};
7272+ let tn = value.typeName().replaceAll('_', ' ');
7373+ def = translate(def, tn, stats);
7474+ ++stats.total;
7575+ if (def?.type) {
7676+ // if (def.id || def.name) ++stats.recognized;
7777+ if (tn == def.type.name || tn == def.name || def.name == 'ANY')
7878+ ++stats.recognized;
7979+ else if (def.name)
8080+ def = Object.assign({ mismatch: 1 }, def);
8181+ value.def = def;
8282+ }
8383+ if (value.sub !== null) {
8484+ if (def?.type?.type)
8585+ def = def.type;
8686+ let j = def?.content ? 0 : -1;
8787+ for (const subval of value.sub) {
8888+ let type;
8989+ if (j >= 0) {
9090+ if (def.typeOf)
9191+ type = def.content[0];
9292+ else {
9393+ let tn = subval.typeName().replaceAll('_', ' ');
9494+ for (;;) {
9595+ type = def.content[j++];
9696+ if (!type || typeof type != 'object') break;
9797+ if (type?.type?.type)
9898+ type = type.type;
9999+ if (type.type == 'defined') {
100100+ let t2 = translate(type, tn);
101101+ if (t2.type.name == tn) break; // exact match
102102+ if (t2.type.name == 'ANY') break; // good enough
103103+ }
104104+ if (type.name == tn) break; // exact match
105105+ if (type.name == 'ANY') break; // good enough
106106+ if (!('optional' in type || 'default' in type)) break;
107107+ }
108108+ if (type?.type == 'builtin' || type?.type == 'defined') {
109109+ let v = subval.content();
110110+ if (typeof v == 'string')
111111+ v = v.split(/\n/);
112112+ stats.defs[type.id] = v;
113113+ } else if (type?.definedBy && stats.defs?.[type.definedBy]?.[1]) { // hope current OIDs contain the type name (will need to parse from RFC itself)
114114+ try {
115115+ type = Defs.searchType(firstUpper(stats.defs[type.definedBy][1]));
116116+ } catch (e) { /*ignore*/ }
117117+ }
118118+ }
119119+ }
120120+ Defs.match(subval, type, stats);
121121+ }
122122+ }
123123+ return stats;
124124+ }
125125+126126+}
127127+128128+Defs.RFC = rfcdef;
129129+130130+Defs.commonTypes = [
131131+ [ 'X.509 certificate', '1.3.6.1.5.5.7.0.18', 'Certificate' ],
132132+ [ 'X.509 public key info', '1.3.6.1.5.5.7.0.18', 'SubjectPublicKeyInfo' ],
133133+ [ 'X.509 certificate revocation list', '1.3.6.1.5.5.7.0.18', 'CertificateList' ],
134134+ [ 'CMS / PKCS#7 envelope', '1.2.840.113549.1.9.16.0.14', 'ContentInfo' ],
135135+ [ 'PKCS#1 RSA private key', '1.2.840.113549.1.1.0.1', 'RSAPrivateKey' ],
136136+ [ 'PKCS#8 encrypted private key', '1.2.840.113549.1.8.1.1', 'EncryptedPrivateKeyInfo' ],
137137+ [ 'PKCS#8 private key', '1.2.840.113549.1.8.1.1', 'PrivateKeyInfo' ],
138138+ [ 'PKCS#10 certification request', '1.2.840.113549.1.10.1.1', 'CertificationRequest' ],
139139+ [ 'CMP PKI Message', '1.3.6.1.5.5.7.0.16', 'PKIMessage' ],
140140+ [ 'LDAP Message', '1.3.6.1.1.18', 'LDAPMessage' ],
141141+].map(arr => ({ description: arr[0], ...Defs.moduleAndType(rfcdef[arr[1]], arr[2]) }));
+204-176
dom.js
···11// ASN.1 JavaScript decoder
22-// Copyright (c) 2008-2022 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-(typeof define != 'undefined' ? define : function (factory) { 'use strict';
1717- if (typeof module == 'object') factory(function (name) { return require(name); });
1818- else factory(function (name) { return window[name.substring(2)]; });
1919-})(function (require) {
2020-"use strict";
1616+import { ASN1 } from './asn1.js';
1717+import { oids } from './oids.js';
1818+import { bindContextMenu } from './context.js';
21192222-var ASN1 = require('./asn1'),
2323- oids = require('./oids'),
2020+const
2421 lineLength = 80,
2522 contentLength = 8 * lineLength,
2623 DOM = {
2727- ellipsis: "\u2026",
2828- tag: function (tagName, className) {
2929- var t = document.createElement(tagName);
3030- t.className = className;
2424+ ellipsis: '\u2026',
2525+ tag: function (tagName, className, text) {
2626+ let t = document.createElement(tagName);
2727+ if (className) t.className = className;
2828+ if (text) t.innerText = text;
3129 return t;
3230 },
3331 text: function (str) {
3432 return document.createTextNode(str);
3533 },
3634 space: function () {
3737- var t = document.createElement('span');
3838- t.className = 'spaces';
3939- t.innerHTML = ' ';
4040- return t;
3535+ return DOM.tag('span', 'spaces', ' ');
4136 },
4237 breakLines: function (str, length) {
4343- var lines = str.split(/\r?\n/),
3838+ let lines = str.split(/\r?\n/),
4439 o = '';
4545- for (var i = 0; i < lines.length; ++i) {
4646- var line = lines[i];
4747- if (i > 0) o += "\n";
4040+ for (let i = 0; i < lines.length; ++i) {
4141+ let line = lines[i];
4242+ if (i > 0) o += '\n';
4843 while (line.length > length) {
4944 o += line.substring(0, length);
5050- o += "\n";
4545+ o += '\n';
5146 line = line.substring(length);
5247 }
5348 o += line;
5449 }
5550 return o;
5656- }
5151+ },
5752 };
58535959-ASN1.prototype.toDOM = function (spaces) {
6060- spaces = spaces || '';
6161- var isOID = (typeof oids === 'object') && (this.tag.isUniversal() && (this.tag.tagNumber == 0x06) || (this.tag.tagNumber == 0x0D));
6262- var node = DOM.tag("div", "node");
6363- node.asn1 = this;
6464- var head = DOM.tag("div", "head");
6565- head.innerHTML = "<span class='spaces'>" + spaces + "</span>" + this.typeName().replace(/_/g, " ");
6666- var content = this.content(contentLength);
6767- if (content !== null) {
6868- var preview = DOM.tag("span", "preview"),
6969- shortContent;
7070- if (isOID)
7171- content = content.split('\n', 1)[0];
7272- shortContent = (content.length > lineLength) ? content.substring(0, lineLength) + DOM.ellipsis : content;
7373- preview.appendChild(DOM.space());
7474- preview.appendChild(DOM.text(shortContent));
7575- if (isOID) {
7676- var oid = oids[content];
7777- if (oid) {
7878- if (oid.d) {
7979- preview.appendChild(DOM.space());
8080- var oidd = DOM.tag("span", "oid description");
8181- oidd.appendChild(DOM.text(oid.d));
8282- preview.appendChild(oidd);
8383- }
8484- if (oid.c) {
8585- preview.appendChild(DOM.space());
8686- var oidc = DOM.tag("span", "oid comment");
8787- oidc.appendChild(DOM.text("(" + oid.c + ")"));
8888- preview.appendChild(oidc);
8989- }
5454+export class ASN1DOM extends ASN1 {
5555+5656+ toDOM(spaces) {
5757+ spaces = spaces || '';
5858+ let isOID = (typeof oids === 'object') && (this.tag.isUniversal() && (this.tag.tagNumber == 0x06) || (this.tag.tagNumber == 0x0D));
5959+ let node = DOM.tag('li');
6060+ node.asn1 = this;
6161+ let head = DOM.tag('span', 'head');
6262+ const typeName = this.typeName().replace(/_/g, ' ');
6363+ if (this.def) {
6464+ if (this.def.id) {
6565+ head.appendChild(DOM.tag('span', 'name id', this.def.id));
6666+ head.appendChild(DOM.space());
6767+ }
6868+ if (this.def.name && this.def.name != typeName) {
6969+ head.appendChild(DOM.tag('span', 'name type', this.def.name));
7070+ head.appendChild(DOM.space());
7171+ }
7272+ if (this.def.mismatch) {
7373+ head.appendChild(DOM.tag('span', 'name type', '[?]'));
7474+ head.appendChild(DOM.space());
9075 }
9176 }
9292- head.appendChild(preview);
9393- content = DOM.breakLines(content, lineLength);
9494- content = content.replace(/</g, "<");
9595- content = content.replace(/\n/g, "<br>");
9696- }
9797- node.appendChild(head);
9898- this.node = node;
9999- this.head = head;
100100- var value = DOM.tag("div", "value");
101101- var s = "Offset: " + this.stream.pos + "<br>";
102102- s += "Length: " + this.header + "+";
103103- if (this.length >= 0)
104104- s += this.length;
105105- else
106106- s += (-this.length) + " (undefined)";
107107- if (this.tag.tagConstructed)
108108- s += "<br>(constructed)";
109109- else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null))
110110- s += "<br>(encapsulates)";
111111- //TODO if (this.tag.isUniversal() && this.tag.tagNumber == 0x03) s += "Unused bits: "
112112- if (content !== null) {
113113- s += "<br>Value:<br><b>" + content + "</b>";
114114- if (isOID && oid) {
115115- if (oid.d) s += "<br>" + oid.d;
116116- if (oid.c) s += "<br>" + oid.c;
117117- if (oid.w) s += "<br>(warning!)";
7777+ head.appendChild(DOM.text(typeName));
7878+ let content;
7979+ try {
8080+ content = this.content(contentLength);
8181+ } catch (e) {
8282+ content = 'Cannot decode: ' + e;
11883 }
119119- }
120120- value.innerHTML = s;
121121- node.appendChild(value);
122122- var sub = DOM.tag("div", "sub");
123123- if (this.sub !== null) {
124124- spaces += '\xA0 ';
125125- for (var i = 0, max = this.sub.length; i < max; ++i)
126126- sub.appendChild(this.sub[i].toDOM(spaces));
127127- }
128128- node.appendChild(sub);
129129- head.onclick = function () {
130130- node.className = (node.className == "node collapsed") ? "node" : "node collapsed";
131131- };
132132- return node;
133133-};
134134-ASN1.prototype.fakeHover = function (current) {
135135- this.node.className += " hover";
136136- if (current)
137137- this.head.className += " hover";
138138-};
139139-ASN1.prototype.fakeOut = function (current) {
140140- var re = / ?hover/;
141141- this.node.className = this.node.className.replace(re, "");
142142- if (current)
143143- this.head.className = this.head.className.replace(re, "");
144144-};
145145-ASN1.prototype.toHexDOM_sub = function (node, className, stream, start, end) {
146146- if (start >= end)
147147- return;
148148- var sub = DOM.tag("span", className);
149149- sub.appendChild(DOM.text(
150150- stream.hexDump(start, end)));
151151- node.appendChild(sub);
152152-};
153153-ASN1.prototype.toHexDOM = function (root) {
154154- var node = DOM.tag("span", "hex");
155155- if (root === undefined) root = node;
156156- this.head.hexNode = node;
157157- this.head.onmouseover = function () { this.hexNode.className = "hexCurrent"; };
158158- this.head.onmouseout = function () { this.hexNode.className = "hex"; };
159159- node.asn1 = this;
160160- node.onmouseover = function () {
161161- var current = !root.selected;
162162- if (current) {
163163- root.selected = this.asn1;
164164- this.className = "hexCurrent";
8484+ let oid;
8585+ if (content !== null) {
8686+ let preview = DOM.tag('span', 'preview'),
8787+ shortContent;
8888+ if (isOID)
8989+ content = content.split('\n', 1)[0];
9090+ shortContent = (content.length > lineLength) ? content.substring(0, lineLength) + DOM.ellipsis : content;
9191+ preview.appendChild(DOM.space());
9292+ preview.appendChild(DOM.text(shortContent));
9393+ if (isOID) {
9494+ oid = oids[content];
9595+ if (oid) {
9696+ if (oid.d) {
9797+ preview.appendChild(DOM.space());
9898+ let oidd = DOM.tag('span', 'oid description', oid.d);
9999+ preview.appendChild(oidd);
100100+ }
101101+ if (oid.c) {
102102+ preview.appendChild(DOM.space());
103103+ let oidc = DOM.tag('span', 'oid comment', '(' + oid.c + ')');
104104+ preview.appendChild(oidc);
105105+ }
106106+ }
107107+ }
108108+ head.appendChild(preview);
109109+ content = DOM.breakLines(content, lineLength);
110110+ content = content.replace(/</g, '<');
111111+ content = content.replace(/\n/g, '<br>');
165112 }
166166- this.asn1.fakeHover(current);
167167- };
168168- node.onmouseout = function () {
169169- var current = (root.selected == this.asn1);
170170- this.asn1.fakeOut(current);
171171- if (current) {
172172- root.selected = null;
173173- this.className = "hex";
113113+ // add the li and details section for this node
114114+ let contentNode;
115115+ let childNode;
116116+ if (this.sub !== null) {
117117+ let details = DOM.tag('details');
118118+ details.setAttribute('open', '');
119119+ node.appendChild(details);
120120+ let summary = DOM.tag('summary', 'node');
121121+ details.appendChild(summary);
122122+ summary.appendChild(head);
123123+ contentNode = summary;
124124+ childNode = details;
125125+ } else {
126126+ contentNode = node;
127127+ contentNode.classList.add('node');
128128+ contentNode.appendChild(head);
129129+ }
130130+ this.node = contentNode;
131131+ this.head = head;
132132+ let value = DOM.tag('div', 'value');
133133+ let s = 'Offset: ' + this.stream.pos + '<br>';
134134+ s += 'Length: ' + this.header + '+';
135135+ if (this.length >= 0)
136136+ s += this.length;
137137+ else
138138+ s += (-this.length) + ' (undefined)';
139139+ if (this.tag.tagConstructed)
140140+ s += '<br>(constructed)';
141141+ else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null))
142142+ s += '<br>(encapsulates)';
143143+ //TODO if (this.tag.isUniversal() && this.tag.tagNumber == 0x03) s += "Unused bits: "
144144+ if (content !== null) {
145145+ s += '<br>Value:<br><b>' + content + '</b>';
146146+ if (isOID && oid) {
147147+ if (oid.d) s += '<br>' + oid.d;
148148+ if (oid.c) s += '<br>' + oid.c;
149149+ if (oid.w) s += '<br>(warning!)';
150150+ }
174151 }
175175- };
176176- if (root == node) {
177177- var lineStart = this.posStart() & 0xF;
178178- if (lineStart != 0) {
179179- var skip = DOM.tag("span", "skip");
180180- var skipStr = '';
181181- for (var j = lineStart; j > 0; --j)
182182- skipStr += ' ';
183183- if (lineStart >= 8)
184184- skipStr += ' ';
185185- skip.innerText = skipStr;
186186- node.appendChild(skip);
152152+ value.innerHTML = s;
153153+ contentNode.appendChild(value);
154154+ if (this.sub !== null) {
155155+ let sub = DOM.tag('ul');
156156+ childNode.appendChild(sub);
157157+ spaces += '\xA0 ';
158158+ for (let i = 0, max = this.sub.length; i < max; ++i)
159159+ sub.appendChild(this.sub[i].toDOM(spaces));
187160 }
161161+ bindContextMenu(node);
162162+ return node;
188163 }
189189- this.toHexDOM_sub(node, "tag", this.stream, this.posStart(), this.posLen());
190190- this.toHexDOM_sub(node, (this.length >= 0) ? "dlen" : "ulen", this.stream, this.posLen(), this.posContent());
191191- if (this.sub === null) {
192192- var start = this.posContent();
193193- var end = this.posEnd();
194194- if (end - start < 10 * 16)
195195- node.appendChild(DOM.text(
196196- this.stream.hexDump(start, end)));
197197- else {
198198- var end1 = start + 5 * 16 - (start & 0xF);
199199- var start2 = end - 16 - (end & 0xF);
200200- node.appendChild(DOM.text(
201201- this.stream.hexDump(start, end1)));
202202- var sub = DOM.tag("span", "skip");
203203- sub.appendChild(DOM.text("\u2026 skipping " + (start2 - end1) + " bytes \u2026\n"));
204204- node.appendChild(sub);
205205- node.appendChild(DOM.text(
206206- this.stream.hexDump(start2, end)));
164164+ fakeHover(current) {
165165+ this.node.classList.add('hover');
166166+ if (current)
167167+ this.head.classList.add('hover');
168168+ }
169169+ fakeOut(current) {
170170+ this.node.classList.remove('hover');
171171+ if (current)
172172+ this.head.classList.remove('hover');
173173+ }
174174+ toHexDOM_sub(node, className, stream, start, end) {
175175+ if (start >= end)
176176+ return;
177177+ let sub = DOM.tag('span', className, stream.hexDump(start, end));
178178+ node.appendChild(sub);
179179+ }
180180+ toHexDOM(root, trim=true) {
181181+ let node = DOM.tag('span', 'hex');
182182+ if (root === undefined) root = node;
183183+ this.head.hexNode = node;
184184+ this.head.onmouseover = function () { this.hexNode.className = 'hexCurrent'; };
185185+ this.head.onmouseout = function () { this.hexNode.className = 'hex'; };
186186+ node.asn1 = this;
187187+ node.onmouseover = function (event) {
188188+ let current = !root.selected;
189189+ if (current) {
190190+ root.selected = this.asn1;
191191+ this.className = 'hexCurrent';
192192+ }
193193+ this.asn1.fakeHover(current);
194194+ event.stopPropagation();
195195+ };
196196+ node.onmouseout = function () {
197197+ let current = (root.selected == this.asn1);
198198+ this.asn1.fakeOut(current);
199199+ if (current) {
200200+ root.selected = null;
201201+ this.className = 'hex';
202202+ }
203203+ };
204204+ bindContextMenu(node);
205205+ if (root == node) {
206206+ let lineStart = this.posStart() & 0xF;
207207+ if (lineStart != 0) {
208208+ let skip = DOM.tag('span', 'skip');
209209+ let skipStr = '';
210210+ for (let j = lineStart; j > 0; --j)
211211+ skipStr += ' ';
212212+ if (lineStart >= 8)
213213+ skipStr += ' ';
214214+ skip.innerText = skipStr;
215215+ node.appendChild(skip);
216216+ }
207217 }
208208- } else if (this.sub.length > 0) {
209209- var first = this.sub[0];
210210- var last = this.sub[this.sub.length - 1];
211211- this.toHexDOM_sub(node, "intro", this.stream, this.posContent(), first.posStart());
212212- for (var i = 0, max = this.sub.length; i < max; ++i)
213213- node.appendChild(this.sub[i].toHexDOM(root));
214214- this.toHexDOM_sub(node, "outro", this.stream, last.posEnd(), this.posEnd());
215215- } else
216216- this.toHexDOM_sub(node, "outro", this.stream, this.posContent(), this.posEnd());
217217- return node;
218218-};
218218+ this.toHexDOM_sub(node, 'tag', this.stream, this.posStart(), this.posLen());
219219+ this.toHexDOM_sub(node, (this.length >= 0) ? 'dlen' : 'ulen', this.stream, this.posLen(), this.posContent());
220220+ if (this.sub === null) {
221221+ let start = this.posContent();
222222+ let end = this.posEnd();
223223+ if (!trim || end - start < 10 * 16)
224224+ node.appendChild(DOM.text(
225225+ this.stream.hexDump(start, end)));
226226+ else {
227227+ let end1 = start + 5 * 16 - (start & 0xF);
228228+ let start2 = end - 16 - (end & 0xF);
229229+ node.appendChild(DOM.text(this.stream.hexDump(start, end1)));
230230+ node.appendChild(DOM.tag('span', 'skip', '\u2026 skipping ' + (start2 - end1) + ' bytes \u2026\n'));
231231+ node.appendChild(DOM.text(this.stream.hexDump(start2, end)));
232232+ }
233233+ } else if (this.sub.length > 0) {
234234+ let first = this.sub[0];
235235+ let last = this.sub[this.sub.length - 1];
236236+ this.toHexDOM_sub(node, 'intro', this.stream, this.posContent(), first.posStart());
237237+ for (let i = 0, max = this.sub.length; i < max; ++i)
238238+ node.appendChild(this.sub[i].toHexDOM(root, trim));
239239+ this.toHexDOM_sub(node, 'outro', this.stream, last.posEnd(), this.posEnd());
240240+ } else
241241+ this.toHexDOM_sub(node, 'outro', this.stream, this.posContent(), this.posEnd());
242242+ return node;
243243+ }
244244+ static decode(stream, offset) {
245245+ return ASN1.decode(stream, offset, ASN1DOM);
246246+ }
219247220220-});
248248+}
+53-90
dumpASN1.js
···11#!/usr/bin/env node
22-'use strict';
3244-const
55- fs = require('fs'),
66- Base64 = require('./base64.js'),
77- ASN1 = require('./asn1.js'),
88- rfc = require('./rfcasn1.json'),
99- colYellow = "\x1b[33m",
1010- colBlue = "\x1b[34m",
1111- colReset = "\x1b[0m";
1212-1313-function searchType(name) {
1414- for (const r of Object.values(rfc))
1515- if (name in r.types) {
1616- // console.log(name + ' found in ' + r.name);
1717- return r.types[name];
1818- }
1919- throw 'Type not found: ' + name;
2020-}
33+// usage:
44+// ./dumpASN1.js filename
55+// ./dumpASN1.js data:base64,MDMCAQFjLgQACgEACgEAAgEAAgEAAQEAoA+jDQQFTnRWZXIEBAEAAAAwCgQITmV0bG9nb24===
2162222-function translate(def, tn) {
2323- const id = def?.id;
2424- if (def?.type == 'tag' && !def.explicit)
2525- // def.type = def.content[0].type;
2626- def = def.content[0].type;
2727- while (def?.type == 'defined' || def?.type?.type == 'defined') {
2828- const name = def?.type?.type ? def.type.name : def.name;
2929- def = Object.assign({}, def);
3030- def.type = searchType(name).type;
3131- }
3232- if (def?.type?.name == 'CHOICE') {
3333- for (let c of def.type.content) {
3434- c = translate(c);
3535- if (tn == c.type.name || tn == c.name) {
3636- def = Object.assign({}, def);
3737- def.type = c.type.name ? c.type : c;
3838- break;
3939- }
4040- }
4141- }
4242- if (id)
4343- def = Object.assign({}, def, { id });
4444- return def ?? { type: {} };
4545-}
77+import * as fs from 'node:fs';
88+import { Base64 } from './base64.js';
99+import { ASN1 } from './asn1.js';
1010+import { Defs } from './defs.js';
46114747-function firstUpper(s) {
4848- return s[0].toUpperCase() + s.slice(1);
4949-}
1212+const
1313+ colYellow = '\x1b[33m',
1414+ colBlue = '\x1b[34m',
1515+ colReset = '\x1b[0m',
1616+ reDataURI = /^data:(?:[a-z-]+[/][a-z.+-]+;)?base64,([A-Za-z0-9+/=\s]+)$/;
50175151-function print(value, def, stats, indent) {
1818+function print(value, indent) {
5219 if (indent === undefined) indent = '';
5353- stats ??= {};
5454- stats.total ??= 0;
5555- stats.recognized ??= 0;
5656- stats.defs ??= {};
5757- let tn = value.typeName();
5858- def = translate(def, tn);
5959- tn = tn.replaceAll('_', ' ');
6060- if (stats) ++stats.total;
2020+ const def = value.def;
6121 let name = '';
6222 if (def?.type) {
6323 if (def.id) name += colBlue + def.id + colReset;
6424 if (typeof def.type == 'object' && def.name) name = (name ? name + ' ' : '') + def.name;
6565- if (stats && name != '') ++stats.recognized;
2525+ if (def.mismatch) name = (name ? name + ' ' : '') + '[?]';
6626 if (name) name += ' ';
6727 }
6868- let s = indent + name + colYellow + value.typeName() + colReset + " @" + value.stream.pos;
2828+ let s = indent + name + colYellow + value.typeName() + colReset + ' @' + value.stream.pos;
6929 if (value.length >= 0)
7070- s += "+";
3030+ s += '+';
7131 s += value.length;
7232 if (value.tag.tagConstructed)
7373- s += " (constructed)";
3333+ s += ' (constructed)';
7434 else if ((value.tag.isUniversal() && ((value.tag.tagNumber == 0x03) || (value.tag.tagNumber == 0x04))) && (value.sub !== null))
7575- s += " (encapsulates)";
3535+ s += ' (encapsulates)';
7636 let content = value.content();
7737 if (content)
7878- s += ": " + content.replace(/\n/g, '|');
7979- s += "\n";
3838+ s += ': ' + content.replace(/\n/g, '|');
3939+ s += '\n';
8040 if (value.sub !== null) {
8141 indent += ' ';
8282- if (def?.type?.type)
8383- def = def.type;
8484- let j = def?.content ? 0 : -1;
8585- for (const subval of value.sub) {
8686- let type;
8787- if (j >= 0) {
8888- if (def.typeOf)
8989- type = def.content[0];
9090- else {
9191- let tn = subval.typeName(); //.replaceAll('_', ' ');
9292- do {
9393- type = def.content[j++];
9494- // type = translate(type, tn);
9595- if (type?.type?.type)
9696- type = type.type;
9797- } while (type && ('optional' in type || 'default' in type) && type.name != 'ANY' && type.name != tn);
9898- if (type?.type == 'defined')
9999- stats.defs[type.id] = subval.content().split(/\n/);
100100- else if (type?.definedBy && stats.defs?.[type.definedBy]?.[1]) // hope current OIDs contain the type name (will need to parse from RFC itself)
101101- type = searchType(firstUpper(stats.defs[type.definedBy][1]));
102102- }
103103- }
104104- s += print(subval, type, stats, indent);
105105- }
4242+ for (const subval of value.sub)
4343+ s += print(subval, indent);
10644 }
10745 return s;
10846}
10947110110-let content = fs.readFileSync(process.argv[3]);
4848+const filename = process.argv[2];
4949+const match = reDataURI.exec(filename);
5050+let content = match
5151+ ? Buffer.from(match[1])
5252+ : fs.readFileSync(filename);
11153try { // try PEM first
11254 content = Base64.unarmor(content);
11355} catch (e) { // try DER/BER then
11456}
11557let result = ASN1.decode(content);
11658content = null;
117117-let stats = {};
118118-console.log(print(result, searchType(process.argv[2]), stats));
119119-console.log('Stats:', (stats.recognized * 100 / stats.total).toFixed(2) + '%');
120120-// console.log('Defs:', stats.defs);
5959+const t0 = performance.now();
6060+if (process.argv.length == 5) {
6161+ Defs.match(result, Defs.moduleAndType(Defs.RFC[process.argv[3]], process.argv[4]));
6262+} else {
6363+ const types = Defs.commonTypes
6464+ .map(type => {
6565+ const stats = Defs.match(result, type);
6666+ return { type, match: stats.recognized / stats.total };
6767+ })
6868+ .sort((a, b) => b.match - a.match);
6969+ const t1 = performance.now();
7070+ console.log('Parsed in ' + (t1 - t0).toFixed(2) + ' ms; possible types:');
7171+ for (const t of types)
7272+ console.log((t.match * 100).toFixed(2).padStart(6) + '% ' + t.type.description);
7373+ Defs.match(result, types[0].type);
7474+ // const stats = Defs.match(result, types[0].type);
7575+ // console.log('Stats:', stats);
7676+ console.log('Parsed as:', result.def);
7777+ // const type = searchType(process.argv[2]);
7878+ // const stats = applyDef(result, type);
7979+}
8080+console.log(print(result));
8181+// console.log('Stats:', (stats.recognized * 100 / stats.total).toFixed(2) + '%');
8282+// // print(result, searchType(process.argv[2]), stats);
8383+// // console.log('Defs:', stats.defs);
+21
examples/cmpv2.b64
···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+====
+8
examples/ldapmessage.b64
···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// Hex JavaScript decoder
22-// Copyright (c) 2008-2022 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-(typeof define != 'undefined' ? define : function (factory) { 'use strict';
1717- if (typeof module == 'object') module.exports = factory();
1818- else window.hex = factory();
1919-})(function () {
2020-"use strict";
1616+const
1717+ haveU8 = (typeof Uint8Array == 'function');
1818+1919+let decoder; // populated on first usage
21202222-var Hex = {},
2323- decoder, // populated on first usage
2424- haveU8 = (typeof Uint8Array == 'function');
2121+export class Hex {
25222626-/**
2727- * Decodes an hexadecimal value.
2828- * @param {string|Array|Uint8Array} a - a string representing hexadecimal data, or an array representation of its charcodes
2929- */
3030-Hex.decode = function(a) {
3131- var isString = (typeof a == 'string');
3232- var i;
3333- if (decoder === undefined) {
3434- var hex = "0123456789ABCDEF",
3535- ignore = " \f\n\r\t\u00A0\u2028\u2029";
3636- decoder = [];
3737- for (i = 0; i < 16; ++i)
3838- decoder[hex.charCodeAt(i)] = i;
3939- hex = hex.toLowerCase();
4040- for (i = 10; i < 16; ++i)
4141- decoder[hex.charCodeAt(i)] = i;
4242- for (i = 0; i < ignore.length; ++i)
4343- decoder[ignore.charCodeAt(i)] = -1;
4444- }
4545- var out = haveU8 ? new Uint8Array(a.length >> 1) : [],
4646- bits = 0,
4747- char_count = 0,
4848- len = 0;
4949- for (i = 0; i < a.length; ++i) {
5050- var c = isString ? a.charCodeAt(i) : a[i];
5151- c = decoder[c];
5252- if (c == -1)
5353- continue;
5454- if (c === undefined)
5555- throw 'Illegal character at offset ' + i;
5656- bits |= c;
5757- if (++char_count >= 2) {
5858- out[len++] = bits;
5959- bits = 0;
6060- char_count = 0;
6161- } else {
6262- bits <<= 4;
2323+ /**
2424+ * Decodes an hexadecimal value.
2525+ * @param {string|Array|Uint8Array} a - a string representing hexadecimal data, or an array representation of its charcodes
2626+ */
2727+ static decode(a) {
2828+ let isString = (typeof a == 'string');
2929+ let i;
3030+ if (decoder === undefined) {
3131+ let hex = '0123456789ABCDEF',
3232+ ignore = ' \f\n\r\t\u00A0\u2028\u2029';
3333+ decoder = [];
3434+ for (i = 0; i < 16; ++i)
3535+ decoder[hex.charCodeAt(i)] = i;
3636+ hex = hex.toLowerCase();
3737+ for (i = 10; i < 16; ++i)
3838+ decoder[hex.charCodeAt(i)] = i;
3939+ for (i = 0; i < ignore.length; ++i)
4040+ decoder[ignore.charCodeAt(i)] = -1;
4141+ }
4242+ let out = haveU8 ? new Uint8Array(a.length >> 1) : [],
4343+ bits = 0,
4444+ char_count = 0,
4545+ len = 0;
4646+ for (i = 0; i < a.length; ++i) {
4747+ let c = isString ? a.charCodeAt(i) : a[i];
4848+ c = decoder[c];
4949+ if (c == -1)
5050+ continue;
5151+ if (c === undefined)
5252+ throw 'Illegal character at offset ' + i;
5353+ bits |= c;
5454+ if (++char_count >= 2) {
5555+ out[len++] = bits;
5656+ bits = 0;
5757+ char_count = 0;
5858+ } else {
5959+ bits <<= 4;
6060+ }
6361 }
6262+ if (char_count)
6363+ throw 'Hex encoding incomplete: 4 bits missing';
6464+ if (haveU8 && out.length > len) // in case it was originally longer because of ignored characters
6565+ out = out.subarray(0, len);
6666+ return out;
6467 }
6565- if (char_count)
6666- throw "Hex encoding incomplete: 4 bits missing";
6767- if (haveU8 && out.length > len) // in case it was originally longer because of ignored characters
6868- out = out.subarray(0, len);
6969- return out;
7070-};
71687272-return Hex;
7373-7474-});
6969+}
···11-(typeof define != 'undefined' ? define : function (factory) { 'use strict';
22- if (typeof module == 'object') factory(function (name) { return require(name); });
33- else factory(function (name) { return window[name.substring(2)]; });
44-})(function (require) {
55-"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';
6777-var ASN1 = require('./asn1'),
88- Base64 = require('./base64'),
99- Hex = require('./hex'),
88+const
109 maxLength = 10240,
1110 reHex = /^\s*(?:[0-9A-Fa-f][0-9A-Fa-f]\s*)+$/,
1211 tree = id('tree'),
1312 dump = id('dump'),
1414- wantHex = id('wantHex'),
1313+ wantHex = checkbox('wantHex'),
1414+ trimHex = checkbox('trimHex'),
1515+ wantDef = checkbox('wantDef'),
1516 area = id('area'),
1617 file = id('file'),
1718 examples = id('examples'),
1818- hash = null;
1919+ selectDefs = id('definitions'),
2020+ selectTag = id('tags');
19212020-require('./dom'); // side effect: augment ASN1
2222+let hash = null;
2323+2124if (!window.console || !window.console.log) // IE8 with closed developer tools
2225 window.console = { log: function () {} };
2326function id(elem) {
2427 return document.getElementById(elem);
2528}
2629function text(el, string) {
2727- if ('textContent' in el)
2828- el.textContent = string;
2929- else
3030- el.innerText = string;
3030+ if ('textContent' in el) el.textContent = string;
3131+ else el.innerText = string;
3132}
3232-function decode(der, offset) {
3333- offset = offset || 0;
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) {
3442 tree.innerHTML = '';
3543 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;
3652 try {
3737- var asn1 = ASN1.decode(der, offset);
3838- tree.appendChild(asn1.toDOM());
3939- if (wantHex.checked)
4040- dump.appendChild(asn1.toHexDOM());
4141- var b64 = (der.length < maxLength) ? asn1.toB64String() : '';
4242- if (area.value === '')
4343- area.value = Base64.pretty(b64);
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);
4487 try {
4588 window.location.hash = hash = '#' + b64;
4646- } catch (e) { // fails with "Access Denied" on IE with URLs longer than ~2048 chars
8989+ } catch (e) {
9090+ // fails with "Access Denied" on IE with URLs longer than ~2048 chars
4791 window.location.hash = hash = '#';
4892 }
4949- var endOffset = asn1.posEnd();
9393+ let endOffset = asn1.posEnd();
5094 if (endOffset < der.length) {
5151- var p = document.createElement('p');
9595+ let p = document.createElement('p');
5296 p.innerText = 'Input contains ' + (der.length - endOffset) + ' more bytes to decode.';
5353- var button = document.createElement('button');
9797+ let button = document.createElement('button');
5498 button.innerText = 'try to decode';
5599 button.onclick = function () {
56100 decode(der, endOffset);
···62106 text(tree, e);
63107 }
64108}
6565-function decodeText(val) {
109109+export function decodeText(val) {
66110 try {
6767- var der = reHex.test(val) ? Hex.decode(val) : Base64.unarmor(val);
111111+ let der = reHex.test(val) ? Hex.decode(val) : Base64.unarmor(val);
68112 decode(der);
69113 } catch (e) {
70114 text(tree, e);
71115 dump.innerHTML = '';
72116 }
73117}
7474-function decodeBinaryString(str) {
7575- var der;
118118+export function decodeBinaryString(str) {
119119+ let der;
76120 try {
7777- if (reHex.test(str))
7878- der = Hex.decode(str);
7979- else if (Base64.re.test(str))
8080- der = Base64.unarmor(str);
8181- else
8282- der = str;
121121+ if (reHex.test(str)) der = Hex.decode(str);
122122+ else if (Base64.re.test(str)) der = Base64.unarmor(str);
123123+ else der = str;
83124 decode(der);
84125 } catch (e) {
85126 text(tree, 'Cannot decode file.');
···87128 }
88129}
89130// set up buttons
9090-id('butDecode').onclick = function () { decodeText(area.value); };
9191-id('butClear').onclick = function () {
9292- area.value = '';
9393- file.value = '';
9494- tree.innerHTML = '';
9595- dump.innerHTML = '';
9696- hash = window.location.hash = '';
9797-};
9898-id('butExample').onclick = function () {
9999- console.log('Loading example:', examples.value);
100100- var request = new XMLHttpRequest();
101101- request.open('GET', 'examples/' + examples.value, true);
102102- request.onreadystatechange = function() {
103103- if (this.readyState !== 4)
104104- return;
105105- if (this.status >= 200 && this.status < 400) {
106106- area.value = this.responseText;
107107- decodeText(this.responseText);
108108- } else {
109109- console.log('Error loading example.');
110110- }
111111- };
112112- request.send();
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+ },
113158};
159159+for (const [name, onClick] of Object.entries(butClickHandlers)) {
160160+ let elem = id(name);
161161+ if (elem)
162162+ elem.onclick = onClick;
163163+}
114164// this is only used if window.FileReader
115165function read(f) {
116166 area.value = ''; // clear text area, will get b64 content
117117- var r = new FileReader();
167167+ let r = new FileReader();
118168 r.onloadend = function () {
119119- if (r.error)
120120- alert("Your browser couldn't read the specified file (error code " + r.error.code + ").");
121121- else
122122- 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);
123171 };
124172 r.readAsBinaryString(f);
125173}
126174function load() {
127127- if (file.files.length === 0)
128128- alert("Select a file to load first.");
129129- else
130130- read(file.files[0]);
175175+ if (file.files.length === 0) alert('Select a file to load first.');
176176+ else read(file.files[0]);
131177}
132178function loadFromHash() {
133179 if (window.location.hash && window.location.hash != hash) {
···135181 // Firefox is not consistent with other browsers and returns an
136182 // already-decoded hash string so we risk double-decoding here,
137183 // but since % is not allowed in base64 nor hexadecimal, it's ok
138138- var val = decodeURIComponent(hash.substr(1));
139139- if (val.length)
140140- decodeText(val);
184184+ let val = decodeURIComponent(hash.substr(1));
185185+ if (val.length) decodeText(val);
141186 }
142187}
143188function stop(e) {
···146191}
147192function dragAccept(e) {
148193 stop(e);
149149- if (e.dataTransfer.files.length > 0)
150150- read(e.dataTransfer.files[0]);
194194+ if (e.dataTransfer.files.length > 0) read(e.dataTransfer.files[0]);
151195}
152196// main
153153-if ('onhashchange' in window)
154154- window.onhashchange = loadFromHash;
197197+if ('onhashchange' in window) window.onhashchange = loadFromHash;
155198loadFromHash();
156199document.ondragover = stop;
157200document.ondragleave = stop;
158158-if ('FileReader' in window && 'readAsBinaryString' in (new FileReader())) {
201201+if ('FileReader' in window && 'readAsBinaryString' in new FileReader()) {
159202 file.style.display = 'block';
160203 file.onchange = load;
161204 document.ondrop = dragAccept;
162205}
163163-164164-});
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';
216216+};
+89-90
int10.js
···11// Big integer base-10 printing library
22-// Copyright (c) 2008-2022 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-(typeof define != 'undefined' ? define : function (factory) { 'use strict';
1717- if (typeof module == 'object') module.exports = factory();
1818- else window.int10 = factory();
1919-})(function () {
2020-"use strict";
1616+/** Biggest 10^n integer that can still fit 2^53 when multiplied by 256. */
1717+const max = 10000000000000;
21182222-var max = 10000000000000; // biggest 10^n 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+ }
23272424-/**
2525- * Arbitrary length base-10 value.
2626- * @param {number} value - Optional initial value (will be 0 otherwise).
2727- */
2828-function Int10(value) {
2929- this.buf = [+value || 0];
3030-}
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+ }
31533232-/**
3333- * Multiply value by m and add c.
3434- * @param {number} m - multiplier, must be < =256
3535- * @param {number} c - value to add
3636- */
3737-Int10.prototype.mulAdd = function (m, c) {
3838- // assert(m <= 256)
3939- var b = this.buf,
4040- l = b.length,
4141- i, t;
4242- for (i = 0; i < l; ++i) {
4343- t = b[i] * m + c;
4444- if (t < max)
4545- c = 0;
4646- else {
4747- c = 0|(t / max);
4848- 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;
4970 }
5050- b[i] = t;
7171+ while (b[b.length - 1] === 0)
7272+ b.pop();
5173 }
5252- if (c > 0)
5353- b[i] = c;
5454-};
55745656-/**
5757- * Subtract value.
5858- * @param {number} c - value to subtract
5959- */
6060-Int10.prototype.sub = function (c) {
6161- var b = this.buf,
6262- l = b.length,
6363- i, t;
6464- for (i = 0; i < l; ++i) {
6565- t = b[i] - c;
6666- if (t < 0) {
6767- t += max;
6868- c = 1;
6969- } else
7070- c = 0;
7171- b[i] = t;
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;
7288 }
7373- while (b[b.length - 1] === 0)
7474- b.pop();
7575-};
76897777-/**
7878- * Convert to decimal string representation.
7979- * @param {*} base - optional value, only value accepted is 10
8080- */
8181-Int10.prototype.toString = function (base) {
8282- if ((base || 10) != 10)
8383- throw 'only base 10 is supported';
8484- var b = this.buf,
8585- s = b[b.length - 1].toString();
8686- for (var i = b.length - 2; i >= 0; --i)
8787- s += (max + b[i]).toString().substring(1);
8888- return s;
8989-};
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+ }
901029191-/**
9292- * Convert to Number value representation.
9393- * Will probably overflow 2^53 and thus become approximate.
9494- */
9595-Int10.prototype.valueOf = function () {
9696- var b = this.buf,
9797- v = 0;
9898- for (var i = b.length - 1; i >= 0; --i)
9999- v = v * max + b[i];
100100- return v;
101101-};
102102-103103-/**
104104- * Return value as a simple Number (if it is <= 10000000000000), or return this.
105105- */
106106-Int10.prototype.simplify = function () {
107107- var b = this.buf;
108108- return (b.length == 1) ? b[0] : this;
109109-};
110110-111111-return Int10;
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+ }
112111113113-});
112112+}
···1010fi
1111cat dumpasn1.cfg | \
1212tr -d '\r' | \
1313-awk -v url="$URL" '
1313+awk -v apos="'" -v q='"' -v url="$URL" '
1414 function clean() {
1515 oid = "";
1616 comment = "";
···1919 }
2020 BEGIN {
2121 FS = "= *";
2222- apos = sprintf("%c", 39);
2322 clean();
2423 print "// Converted from: " url;
2524 print "// which is made by Peter Gutmann and whose license states:";
2625 print "// You can use this code in whatever way you want,";
2726 print "// as long as you don" apos "t try to claim you wrote it.";
2828- print "(typeof define != " apos "undefined" apos " ? define : function (factory) { " apos "use strict" apos ";";
2929- print " if (typeof module == " apos "object" apos ") module.exports = factory();";
3030- print " else window.oids = factory();";
3131- print "})(function () {";
3232- print apos "use strict" apos ";";
3333- print "return {";
2727+ print "export const oids = {";
3428 }
3529 /^OID/ { oid = $2; }
3630 /^Comment/ { comment = $2; }
···4943 }
5044 }
5145 END {
5252- print "\"END\": \"\""
5353- print "};});"
4646+ print "};"
5447 }
5548' >oids.js
5649echo Conversion completed.
+15-3
updateRFC.sh
···11#/bin/sh
22+RFCs="5280 5208 3369 3161 2986 4211 4210 8017 4511"
23downloadRFC() {
34 URL="https://www.ietf.org/rfc/rfc$1.txt"
45 if [ -x /usr/bin/fetch ]; then
···1011 exit 1
1112 fi
1213}
1313-echo '{}' > rfcasn1.json # start from scratch
1414+echo '{}' > rfcdef.json # start from scratch
1415mkdir -p rfc
1516cd rfc
1616-for n in 5280 3369 3161; do
1717+for n in $RFCs; do
1718 downloadRFC $n
1818- ../parseRFC.js rfc$n.txt ../rfcasn1.json
1919+ ../parseRFC.js rfc$n.txt ../rfcdef.json
1920done
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
2032echo Conversion completed.