···11+# ChangeLog
22+33+## 2.1.1 - 2025-10-24
44+55+### Changed
66+77+- update dev dependencies
88+- fix test suite that was reporting no error with empty responses
99+1010+### Added
1111+1212+- add content length check for BOOLEAN, INTEGER, OID ([GitHub #104](https://github.com/lapo-luchini/asn1js/pull/104))
1313+1414+## 2.1.0 - 2025-08-03
1515+1616+### Changed
1717+1818+- when fields are CHOICEs now both the field name and the choice name are shown (fixes [GitHub #102](https://github.com/lapo-luchini/asn1js/issues/102))
1919+- upgrade minimum NodeJS version supported from 12.20.0 to 14.6.0 due to usage of ?. and ?? operators in defs.js (ECMAScript 2020); older code is still linted against ECMAScript 2015 for now
2020+2121+### Added
2222+2323+- add tests to check expected decoding
2424+2525+## 2.0.6 - 2025-07-29
2626+2727+### Added
2828+2929+- add proper support for standard Base64 (we previously only supported Base64url) (fixes [GitHub #99](https://github.com/lapo-luchini/asn1js/pull/99))
3030+- improve test harness
3131+3232+## 2.0.5 - 2025-04-12
3333+3434+### Added
3535+3636+- add `index-local.html` for local `file://` usage without needing a web server
3737+- add definitions support for `LDAPMessage`
3838+- #TODO continue producing old ChangeLog entries
+3-2
LICENSE
···11-ASN.1 JavaScript decoder Copyright (c) 2008-2020 Lapo Luchini <lapo@lapo.it>
11+ISC License
22+33+Copyright (c) 2008-2025 Lapo Luchini <lapo@lapo.it>
2435Permission to use, copy, modify, and/or distribute this software for any
46purpose with or without fee is hereby granted, provided that the above
···1113WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1214ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1315OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1414-
+67-15
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-```
1313+```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 ASN1 = 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());
3939+```
4040+4141+On older NodeJS you instead need to use async `import`:
4242+4343+```js
4444+async function main() {
4545+ const
4646+ { ASN1 } = await import('@lapo/asn1js'),
4747+ { Hex } = await import('@lapo/asn1js/hex.js');
4848+ console.log(ASN1.decode(Hex.decode('06032B6570')).content());
4949+}
5050+main();
5151+```
5252+5353+Usage on the web
5454+--------------------
5555+5656+Can be [tested on JSFiddle](https://jsfiddle.net/lapo/y6t2wo7q/).
5757+5858+```html
5959+<script>
6060+import { ASN1 } from 'https://unpkg.com/@lapo/asn1js@2.0.0/asn1.js';
6161+import { Hex } from 'https://unpkg.com/@lapo/asn1js@2.0.0/hex.js';
6262+6363+document.body.innerText = ASN1.decode(Hex.decode('06032B6570')).content();
6464+</script>
6565+```
6666+6767+Local usage
6868+--------------------
6969+7070+Since unfortunately ESM modules are not working on `file:` protocol due to [CORS issues](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#other_differences_between_modules_and_standard_scripts), there is a bundled [single-file version working locally](https://asn1js.eu/index-local.html). It doesn't work online (due to [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) restrictions about inline content) but can be saved locally and opened in a browser.
7171+7272+Usage from CLI
7373+--------------------
7474+7575+You can dump an ASN.1 structure from the command line using the following command (no need to even install it):
7676+7777+```sh
7878+npx @lapo/asn1js ed25519.cer
3379```
34803581ISC license
3682-----------
37833838-ASN.1 JavaScript decoder Copyright (c) 2008-2020 Lapo Luchini <lapo@lapo.it>
8484+ASN.1 JavaScript decoder Copyright (c) 2008-2025 Lapo Luchini <lapo@lapo.it>
39854086Permission 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.
4187···4894- BMPString support added by [Felipe Gasper](https://github.com/FGasper)
4995- extended tag support added by [Pรฉter Budai](https://www.peterbudai.eu/)
5096- patches by [Gergely Nagy](https://github.com/ngg)
9797+- Relative OID support added by [Mistial Developer](https://github.com/mistial-dev)
9898+- dark mode and other UI improvements by [Oliver Burgmaier](https://github.com/olibu/)
9999+- patches by [Nicolai Sรธborg](https://github.com/NicolaiSoeborg)
5110052101links
53102-----
541035555-- [official website](https://lapo.it/asn1js/)
5656-- [InDefero tracker](http://idf.lapo.it/p/asn1js/)
104104+- [official website](https://asn1js.eu/)
105105+- [alternate website](https://lapo.it/asn1js/)
106106+- [single-file version working locally](https://asn1js.eu/index-local.html) (just save this link)
107107+- [InDefero tracker](http://idf.lapo.it/p/asn1js/) (currently offline)
57108- [GitHub mirror](https://github.com/lapo-luchini/asn1js)
109109+- [ChangeLog on GitHub](https://github.com/lapo-luchini/asn1js/blob/trunk/CHANGELOG.md)
58110- [Ohloh code stats](https://www.openhub.net/p/asn1js)
+800-460
asn1.js
···11// ASN.1 JavaScript decoder
22-// Copyright (c) 2008-2020 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]; });
1919-})(function (require) {
2020-"use strict";
1616+import { oids } from './oids.js';
21172222-var Int10 = require('int10'),
2323- oids = require('oids'),
2424- ellipsis = "\u2026",
2525- reTimeS = /^(\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|[-+](?:[0]\d|1[0-2])([0-5]\d)?)?$/,
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-5]\d)?)?$/;
1818+const
1919+ ellipsis = '\u2026',
2020+ 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)?)?$/,
2121+ 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+ hexDigits = '0123456789ABCDEF',
2323+ b64Std = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
2424+ b64URL = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
2525+ tableT61 = [
2626+ ['', ''],
2727+ ['AEIOUaeiou', 'รรรรรร รจรฌรฒรน'], // Grave
2828+ ['ACEILNORSUYZacegilnorsuyz', 'รฤรรฤนลรลลรรลนรกฤรฉฤฃรญฤบลรณลลรบรฝลบ'], // Acute
2929+ ['ACEGHIJOSUWYaceghijosuwy', 'รฤรฤฤครฤดรลรลดลถรขฤรชฤฤฅรฎฤตรดลรปลตลท'], // Circumflex
3030+ ['AINOUainou', 'รฤจรรลจรฃฤฉรฑรตลฉ'], // Tilde
3131+ ['AEIOUaeiou', 'ฤฤฤชลลชฤฤฤซลลซ'], // Macron
3232+ ['AGUagu', 'ฤฤลฌฤฤลญ'], // Breve
3333+ ['CEGIZcegz', 'ฤฤฤ ฤฐลปฤฤฤกลผ'], // Dot
3434+ ['AEIOUYaeiouy', 'รรรรรลธรครซรฏรถรผรฟ'], // Umlaut or diรฆresis
3535+ ['', ''],
3636+ ['AUau', 'ร ลฎรฅลฏ'], // Ring
3737+ ['CGKLNRSTcklnrst', 'รฤขฤถฤปล ลลลขรงฤทฤผลลลลฃ'], // Cedilla
3838+ ['', ''],
3939+ ['OUou', 'ลลฐลลฑ'], // Double Acute
4040+ ['AEIUaeiu', 'ฤฤฤฎลฒฤ ฤฤฏลณ'], // Ogonek
4141+ ['CDELNRSTZcdelnrstz', 'ฤฤฤฤฝลลล ลคลฝฤฤฤฤพลลลกลฅลพ'], // Caron
4242+ ];
27434444+/**
4545+ * Truncates a string to a specified length and adds an ellipsis if needed.
4646+ * @param {string} str - The input string to truncate
4747+ * @param {number} len - The maximum length of the string
4848+ * @returns {string} The truncated string
4949+ */
2850function stringCut(str, len) {
2951 if (str.length > len)
3052 str = str.substring(0, len) + ellipsis;
3153 return str;
3254}
33553434-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;
5656+/**
5757+ * Checks if a string contains only printable characters (ASCII 32-126, plus tab, newline, carriage return)
5858+ * @param {string} s - The string to check
5959+ * @throws {Error} If an unprintable character is found
6060+ */
6161+function checkPrintable(s) {
6262+ let i, v;
6363+ for (i = 0; i < s.length; ++i) {
6464+ v = s.charCodeAt(i);
6565+ if (v < 32 && v != 9 && v != 10 && v != 13) // [\t\r\n] are (kinda) printable
6666+ throw new Error('Unprintable character at index ' + i + ' (code ' + s.str.charCodeAt(i) + ')');
4267 }
4368}
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- }
6969+7070+/**
7171+ * Class to manage a stream of bytes, with a zero-copy approach.
7272+ * It uses an existing array or binary string and advances a position index.
7373+ */
7474+export class Stream {
7575+7676+ /**
7777+ * Creates a new Stream object.
7878+ * @param {Stream|array|string} enc data (will not be copied)
7979+ * @param {?number} pos starting position (mandatory when `end` is not a Stream)
8080+ */
8181+ constructor(enc, pos) {
8282+ if (enc instanceof Stream) {
8383+ this.enc = enc.enc;
8484+ this.pos = enc.pos;
8585+ } else {
8686+ this.enc = enc;
8787+ this.pos = pos;
8888+ }
8989+ if (typeof this.pos != 'number')
9090+ throw new Error('"pos" must be a numeric value');
9191+ // Set up the raw byte access function based on the type of data
9292+ if (typeof this.enc == 'string')
9393+ this.getRaw = pos => this.enc.charCodeAt(pos);
9494+ else if (typeof this.enc[0] == 'number')
9595+ this.getRaw = pos => this.enc[pos];
9696+ else
9797+ throw new Error('"enc" must be a numeric array or a string');
6598 }
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);
9999+100100+ /**
101101+ * Get the byte at current position (and increment it) or at a specified position (and avoid moving current position).
102102+ * @param {?number} pos read position if specified, else current position (and increment it)
103103+ * @returns {number} The byte value at the specified position
104104+ */
105105+ get(pos) {
106106+ if (pos === undefined)
107107+ pos = this.pos++;
108108+ if (pos >= this.enc.length)
109109+ throw new Error('Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length);
110110+ return this.getRaw(pos);
79111 }
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);
112112+113113+ /**
114114+ * Convert a single byte to a hexadecimal string (of length 2).
115115+ * @param {number} b - The byte to convert
116116+ * @returns {string} Hexadecimal representation of the byte
117117+ */
118118+ static hexByte(b) {
119119+ return hexDigits.charAt((b >> 4) & 0xF) + hexDigits.charAt(b & 0xF);
86120 }
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;
121121+122122+ /**
123123+ * Hexadecimal dump of a specified region of the stream.
124124+ * @param {number} start - starting position (included)
125125+ * @param {number} end - ending position (excluded)
126126+ * @param {string} type - 'raw', 'byte' or 'dump' (default)
127127+ * @returns {string} Hexadecimal representation of the data
128128+ */
129129+ hexDump(start, end, type = 'dump') {
130130+ let s = '';
131131+ for (let i = start; i < end; ++i) {
132132+ if (type == 'byte' && i > start)
133133+ s += ' ';
134134+ s += Stream.hexByte(this.get(i));
135135+ if (type == 'dump')
136136+ switch (i & 0xF) {
137137+ case 0x7: s += ' '; break;
138138+ case 0xF: s += '\n'; break;
139139+ default: s += ' ';
140140+ }
141141+ }
142142+ return s;
94143 }
9595- return true;
9696-};
9797-Stream.prototype.parseStringISO = function (start, end) {
9898- var s = "";
9999- for (var i = start; i < end; ++i)
100100- s += String.fromCharCode(this.get(i));
101101- return s;
102102-};
103103-Stream.prototype.parseStringUTF = function (start, end) {
104104- function ex(c) { // must be 10xxxxxx
105105- if ((c < 0x80) || (c >= 0xC0))
106106- throw new Error('Invalid UTF-8 continuation byte: ' + c);
107107- return (c & 0x3F);
144144+145145+ /**
146146+ * Base64url dump of a specified region of the stream (according to RFC 4648 section 5).
147147+ * @param {number} start - starting position (included)
148148+ * @param {number} end - ending position (excluded)
149149+ * @param {string} type - 'url' (default, section 5 without padding) or 'std' (section 4 with padding)
150150+ * @returns {string} Base64 encoded representation of the data
151151+ */
152152+ b64Dump(start, end, type = 'url') {
153153+ const b64 = type === 'url' ? b64URL : b64Std,
154154+ extra = (end - start) % 3;
155155+ let s = '',
156156+ i, c;
157157+ for (i = start; i + 2 < end; i += 3) {
158158+ c = this.get(i) << 16 | this.get(i + 1) << 8 | this.get(i + 2);
159159+ s += b64.charAt(c >> 18 & 0x3F);
160160+ s += b64.charAt(c >> 12 & 0x3F);
161161+ s += b64.charAt(c >> 6 & 0x3F);
162162+ s += b64.charAt(c & 0x3F);
163163+ }
164164+ if (extra > 0) {
165165+ c = this.get(i) << 16;
166166+ if (extra > 1) c |= this.get(i + 1) << 8;
167167+ s += b64.charAt(c >> 18 & 0x3F);
168168+ s += b64.charAt(c >> 12 & 0x3F);
169169+ if (extra == 2) s += b64.charAt(c >> 6 & 0x3F);
170170+ if (b64 === b64Std) s += '==='.slice(0, 3 - extra);
171171+ }
172172+ return s;
108173 }
109109- function surrogate(cp) {
110110- if (cp < 0x10000)
111111- throw new Error('UTF-8 overlong encoding, codepoint encoded in 4 bytes: ' + cp);
112112- // we could use String.fromCodePoint(cp) but let's be nice to older browsers and use surrogate pairs
113113- cp -= 0x10000;
114114- return String.fromCharCode((cp >> 10) + 0xD800, (cp & 0x3FF) + 0xDC00);
174174+175175+ /**
176176+ * Check if a region of the stream contains only ASCII characters (32-176)
177177+ * @param {number} start - starting position (included)
178178+ * @param {number} end - ending position (excluded)
179179+ * @returns {boolean} True if all characters are ASCII, false otherwise
180180+ */
181181+ isASCII(start, end) {
182182+ for (let i = start; i < end; ++i) {
183183+ let c = this.get(i);
184184+ if (c < 32 || c > 176)
185185+ return false;
186186+ }
187187+ return true;
115188 }
116116- var s = "";
117117- for (var i = start; i < end; ) {
118118- var c = this.get(i++);
119119- if (c < 0x80) // 0xxxxxxx (7 bit)
120120- s += String.fromCharCode(c);
121121- else if (c < 0xC0)
122122- throw new Error('Invalid UTF-8 starting byte: ' + c);
123123- else if (c < 0xE0) // 110xxxxx 10xxxxxx (11 bit)
124124- s += String.fromCharCode(((c & 0x1F) << 6) | ex(this.get(i++)));
125125- else if (c < 0xF0) // 1110xxxx 10xxxxxx 10xxxxxx (16 bit)
126126- s += String.fromCharCode(((c & 0x0F) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++)));
127127- else if (c < 0xF8) // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (21 bit)
128128- s += surrogate(((c & 0x07) << 18) | (ex(this.get(i++)) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++)));
129129- else
130130- throw new Error('Invalid UTF-8 starting byte (since 2003 it is restricted to 4 bytes): ' + c);
189189+190190+ /**
191191+ * Parse a region of the stream as an ISO string
192192+ * @param {number} start - starting position (included)
193193+ * @param {number} end - ending position (excluded)
194194+ * @param {number} maxLength - maximum length of the output string
195195+ * @returns {Object} Object with size and str properties
196196+ */
197197+ parseStringISO(start, end, maxLength) {
198198+ let s = '';
199199+ for (let i = start; i < end; ++i)
200200+ s += String.fromCharCode(this.get(i));
201201+ return { size: s.length, str: stringCut(s, maxLength) };
131202 }
132132- return s;
133133-};
134134-Stream.prototype.parseStringBMP = function (start, end) {
135135- var str = "", hi, lo;
136136- for (var i = start; i < end; ) {
137137- hi = this.get(i++);
138138- lo = this.get(i++);
139139- str += String.fromCharCode((hi << 8) | lo);
203203+204204+ /**
205205+ * Parse a region of the stream as a T.61 string
206206+ * @param {number} start - starting position (included)
207207+ * @param {number} end - ending position (excluded)
208208+ * @param {number} maxLength - maximum length of the output string
209209+ * @returns {Object} Object with size and str properties
210210+ */
211211+ parseStringT61(start, end, maxLength) {
212212+ // warning: this code is not very well tested so far
213213+ function merge(c, d) {
214214+ const t = tableT61[c - 0xC0];
215215+ const i = t[0].indexOf(String.fromCharCode(d));
216216+ return (i < 0) ? '\0' : t[1].charAt(i);
217217+ }
218218+ let s = '', c;
219219+ for (let i = start; i < end; ++i) {
220220+ c = this.get(i);
221221+ if (c >= 0xA4 && c <= 0xBF)
222222+ s += '$ยฅ#ยงยค\0\0ยซ\0\0\0\0ยฐยฑยฒยณรยตยถยทรท\0\0ยปยผยฝยพยฟ'.charAt(c - 0xA4);
223223+ else if (c >= 0xE0 && c <= 0xFF)
224224+ s += 'โฆรรยชฤฆ\0ฤฒฤฟลรลยบรลฆลลฤธรฆฤรฐฤงฤฑฤณลลรธลรรพลงล\0'.charAt(c - 0xE0);
225225+ else if (c >= 0xC0 && c <= 0xCF)
226226+ s += merge(c, this.get(++i));
227227+ else // using ISO 8859-1 for characters undefined (or equal) in T.61
228228+ s += String.fromCharCode(c);
229229+ }
230230+ return { size: s.length, str: stringCut(s, maxLength) };
140231 }
141141- return str;
142142-};
143143-Stream.prototype.parseTime = function (start, end, shortYear) {
144144- var s = this.parseStringISO(start, end),
145145- m = (shortYear ? reTimeS : reTimeL).exec(s);
146146- if (!m)
147147- return "Unrecognized time: " + s;
148148- if (shortYear) {
149149- // to avoid querying the timer, use the fixed range [1970, 2069]
150150- // it will conform with ITU X.400 [-10, +40] sliding window until 2030
151151- m[1] = +m[1];
152152- m[1] += (m[1] < 70) ? 2000 : 1900;
232232+233233+ /**
234234+ * Parse a region of the stream as a UTF-8 string
235235+ * @param {number} start - starting position (included)
236236+ * @param {number} end - ending position (excluded)
237237+ * @param {number} maxLength - maximum length of the output string
238238+ * @returns {Object} Object with size and str properties
239239+ */
240240+ parseStringUTF(start, end, maxLength) {
241241+ /**
242242+ * Helper function to process UTF-8 continuation bytes
243243+ * @param {number} c - The continuation byte
244244+ * @returns {number} The extracted data bits
245245+ */
246246+ function ex(c) { // must be 10xxxxxx
247247+ if ((c < 0x80) || (c >= 0xC0))
248248+ throw new Error('Invalid UTF-8 continuation byte: ' + c);
249249+ return (c & 0x3F);
250250+ }
251251+ /**
252252+ * Helper function to convert a code point to a surrogate pair
253253+ * @param {number} cp - The code point to convert
254254+ * @returns {string} The surrogate pair as a string
255255+ */
256256+ function surrogate(cp) {
257257+ if (cp < 0x10000)
258258+ throw new Error('UTF-8 overlong encoding, codepoint encoded in 4 bytes: ' + cp);
259259+ // we could use String.fromCodePoint(cp) but let's be nice to older browsers and use surrogate pairs
260260+ cp -= 0x10000;
261261+ return String.fromCharCode((cp >> 10) + 0xD800, (cp & 0x3FF) + 0xDC00);
262262+ }
263263+ let s = '';
264264+ for (let i = start; i < end; ) {
265265+ const c = this.get(i++);
266266+ if (c < 0x80) // 0xxxxxxx (7 bit)
267267+ s += String.fromCharCode(c);
268268+ else if (c < 0xC0)
269269+ throw new Error('Invalid UTF-8 starting byte: ' + c);
270270+ else if (c < 0xE0) // 110xxxxx 10xxxxxx (11 bit)
271271+ s += String.fromCharCode(((c & 0x1F) << 6) | ex(this.get(i++)));
272272+ else if (c < 0xF0) // 1110xxxx 10xxxxxx 10xxxxxx (16 bit)
273273+ s += String.fromCharCode(((c & 0x0F) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++)));
274274+ else if (c < 0xF8) // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (21 bit)
275275+ s += surrogate(((c & 0x07) << 18) | (ex(this.get(i++)) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++)));
276276+ else
277277+ throw new Error('Invalid UTF-8 starting byte (since 2003 it is restricted to 4 bytes): ' + c);
278278+ }
279279+ return { size: s.length, str: stringCut(s, maxLength) };
153280 }
154154- s = m[1] + "-" + m[2] + "-" + m[3] + " " + m[4];
155155- if (m[5]) {
156156- s += ":" + m[5];
157157- if (m[6]) {
158158- s += ":" + m[6];
159159- if (m[7])
160160- s += "." + m[7];
281281+282282+ /**
283283+ * Parse a region of the stream as a BMP (Basic Multilingual Plane) string
284284+ * @param {number} start - starting position (included)
285285+ * @param {number} end - ending position (excluded)
286286+ * @param {number} maxLength - maximum length of the output string
287287+ * @returns {Object} Object with size and str properties
288288+ */
289289+ parseStringBMP(start, end, maxLength) {
290290+ let s = '', hi, lo;
291291+ for (let i = start; i < end; ) {
292292+ hi = this.get(i++);
293293+ lo = this.get(i++);
294294+ s += String.fromCharCode((hi << 8) | lo);
161295 }
296296+ return { size: s.length, str: stringCut(s, maxLength) };
162297 }
163163- if (m[8]) {
164164- s += " UTC";
165165- if (m[8] != 'Z') {
166166- s += m[8];
298298+299299+ /**
300300+ * Parse a region of the stream as a time string
301301+ * @param {number} start - starting position (included)
302302+ * @param {number} end - ending position (excluded)
303303+ * @param {boolean} shortYear - Whether to parse as short year (2-digit)
304304+ * @returns {string} Formatted time string
305305+ */
306306+ parseTime(start, end, shortYear) {
307307+ let s = this.parseStringISO(start, end).str,
308308+ m = (shortYear ? reTimeS : reTimeL).exec(s);
309309+ if (!m)
310310+ throw new Error('Unrecognized time: ' + s);
311311+ if (shortYear) {
312312+ // to avoid querying the timer, use the fixed range [1970, 2069]
313313+ // it will conform with ITU X.400 [-10, +40] sliding window until 2030
314314+ m[1] = +m[1];
315315+ m[1] += (m[1] < 70) ? 2000 : 1900;
316316+ }
317317+ s = m[1] + '-' + m[2] + '-' + m[3] + ' ' + m[4];
318318+ if (m[5]) {
319319+ s += ':' + m[5];
320320+ if (m[6]) {
321321+ s += ':' + m[6];
322322+ if (m[7])
323323+ s += '.' + m[7];
324324+ }
325325+ }
326326+ if (m[8]) {
327327+ s += ' UTC';
167328 if (m[9])
168168- s += ":" + m[9];
329329+ s += m[9] + ':' + (m[10] || '00');
169330 }
331331+ return s;
170332 }
171171- return s;
172172-};
173173-Stream.prototype.parseInteger = function (start, end) {
174174- var v = this.get(start),
175175- neg = (v > 127),
176176- pad = neg ? 255 : 0,
177177- len,
178178- s = '';
179179- // skip unuseful bits (not allowed in DER)
180180- while (v == pad && ++start < end)
181181- v = this.get(start);
182182- len = end - start;
183183- if (len === 0)
184184- return neg ? '-1' : '0';
185185- // show bit length of huge integers
186186- if (len > 4) {
187187- s = v;
188188- len <<= 3;
189189- while (((s ^ pad) & 0x80) == 0) {
190190- s <<= 1;
191191- --len;
333333+334334+ /**
335335+ * Parse a region of the stream as an integer
336336+ * @param {number} start - starting position (included)
337337+ * @param {number} end - ending position (excluded)
338338+ * @returns {string} Formatted integer string
339339+ */
340340+ parseInteger(start, end) {
341341+ let v = this.get(start),
342342+ s = '';
343343+ const neg = (v > 127),
344344+ pad = neg ? 255 : 0;
345345+ // skip unuseful bits (not allowed in DER)
346346+ while (v == pad && ++start < end)
347347+ v = this.get(start);
348348+ const len = end - start;
349349+ if (len === 0)
350350+ return neg ? '-1' : '0';
351351+ // show bit length of huge integers
352352+ if (len > 4) {
353353+ let v2 = v,
354354+ lenBit = len << 3;
355355+ while (((v2 ^ pad) & 0x80) == 0) {
356356+ v2 <<= 1;
357357+ --lenBit;
358358+ }
359359+ s = '(' + lenBit + ' bit)\n';
192360 }
193193- s = "(" + len + " bit)\n";
361361+ // decode the integer
362362+ if (neg) v = v - 256;
363363+ let n = BigInt(v);
364364+ for (let i = start + 1; i < end; ++i)
365365+ n = (n << 8n) | BigInt(this.get(i));
366366+ return s + n;
194367 }
195195- // decode the integer
196196- if (neg) v = v - 256;
197197- var n = new Int10(v);
198198- for (var i = start + 1; i < end; ++i)
199199- n.mulAdd(256, this.get(i));
200200- return s + n.toString();
201201-};
202202-Stream.prototype.parseBitString = function (start, end, maxLength) {
203203- var unusedBits = this.get(start);
204204- if (unusedBits > 7)
205205- throw 'Invalid BitString with unusedBits=' + unusedBits;
206206- var lenBit = ((end - start - 1) << 3) - unusedBits,
207207- intro = "(" + lenBit + " bit)\n",
208208- s = "";
209209- for (var i = start + 1; i < end; ++i) {
210210- var b = this.get(i),
211211- skip = (i == end - 1) ? unusedBits : 0;
212212- for (var j = 7; j >= skip; --j)
213213- s += (b >> j) & 1 ? "1" : "0";
214214- if (s.length > maxLength)
215215- return intro + stringCut(s, maxLength);
216216- }
217217- return intro + s;
218218-};
219219-Stream.prototype.parseOctetString = function (start, end, maxLength) {
220220- if (this.isASCII(start, end))
221221- return stringCut(this.parseStringISO(start, end), maxLength);
222222- var len = end - start,
223223- s = "(" + len + " byte)\n";
224224- maxLength /= 2; // we work in bytes
225225- if (len > maxLength)
226226- end = start + maxLength;
227227- for (var i = start; i < end; ++i)
228228- s += this.hexByte(this.get(i));
229229- if (len > maxLength)
230230- s += ellipsis;
231231- return s;
232232-};
233233-Stream.prototype.parseOID = function (start, end, maxLength) {
234234- var s = '',
235235- n = new Int10(),
236236- bits = 0;
237237- for (var i = start; i < end; ++i) {
238238- var v = this.get(i);
239239- n.mulAdd(128, v & 0x7F);
240240- bits += 7;
241241- if (!(v & 0x80)) { // finished
242242- if (s === '') {
243243- n = n.simplify();
244244- if (n instanceof Int10) {
245245- n.sub(80);
246246- s = "2." + n.toString();
247247- } else {
248248- var m = n < 80 ? n < 40 ? 0 : 1 : 2;
249249- s = m + "." + (n - m * 40);
250250- }
251251- } else
252252- s += "." + n.toString();
368368+369369+ /**
370370+ * Parse a region of the stream as a bit string.
371371+ * @param {number} start - starting position (included)
372372+ * @param {number} end - ending position (excluded)
373373+ * @param {number} maxLength - maximum length of the output string
374374+ * @returns {Object} Object with size and str properties
375375+ */
376376+ parseBitString(start, end, maxLength) {
377377+ const unusedBits = this.get(start);
378378+ if (unusedBits > 7)
379379+ throw new Error('Invalid BitString with unusedBits=' + unusedBits);
380380+ const lenBit = ((end - start - 1) << 3) - unusedBits;
381381+ let s = '';
382382+ for (let i = start + 1; i < end; ++i) {
383383+ let b = this.get(i),
384384+ skip = (i == end - 1) ? unusedBits : 0;
385385+ for (let j = 7; j >= skip; --j)
386386+ s += (b >> j) & 1 ? '1' : '0';
253387 if (s.length > maxLength)
254254- return stringCut(s, maxLength);
255255- n = new Int10();
256256- bits = 0;
388388+ s = stringCut(s, maxLength);
257389 }
390390+ return { size: lenBit, str: s };
258391 }
259259- if (bits > 0)
260260- s += ".incomplete";
261261- if (typeof oids === 'object') {
262262- var oid = oids[s];
263263- if (oid) {
264264- if (oid.d) s += "\n" + oid.d;
265265- if (oid.c) s += "\n" + oid.c;
266266- if (oid.w) s += "\n(warning!)";
392392+393393+ /**
394394+ * Parse a region of the stream as an octet string.
395395+ * @param {number} start - starting position (included)
396396+ * @param {number} end - ending position (excluded)
397397+ * @param {number} maxLength - maximum length of the output string
398398+ * @returns {Object} Object with size and str properties
399399+ */
400400+ parseOctetString(start, end, maxLength) {
401401+ try {
402402+ let s = this.parseStringUTF(start, end, maxLength);
403403+ checkPrintable(s.str);
404404+ return { size: end - start, str: s.str };
405405+ } catch (ignore) {
406406+ // If UTF-8 parsing fails, fall back to hexadecimal dump
267407 }
408408+ const len = end - start;
409409+ maxLength /= 2; // we work in bytes
410410+ if (len > maxLength)
411411+ end = start + maxLength;
412412+ let s = '';
413413+ for (let i = start; i < end; ++i)
414414+ s += Stream.hexByte(this.get(i));
415415+ if (len > maxLength)
416416+ s += ellipsis;
417417+ return { size: len, str: s };
268418 }
269269- return s;
270270-};
271419272272-function ASN1(stream, header, length, tag, tagLen, sub) {
273273- if (!(tag instanceof ASN1Tag)) throw 'Invalid tag value.';
274274- this.stream = stream;
275275- this.header = header;
276276- this.length = length;
277277- this.tag = tag;
278278- this.tagLen = tagLen;
279279- this.sub = sub;
420420+ /**
421421+ * Parse a region of the stream as an OID (Object Identifier).
422422+ * @param {number} start - starting position (included)
423423+ * @param {number} end - ending position (excluded)
424424+ * @param {number} maxLength - maximum length of the output string
425425+ * @param {boolean} isRelative - Whether the OID is relative
426426+ * @returns {string} Formatted OID string
427427+ */
428428+ parseOID(start, end, maxLength, isRelative) {
429429+ let s = '',
430430+ n = 0n,
431431+ bits = 0;
432432+ for (let i = start; i < end; ++i) {
433433+ let v = this.get(i);
434434+ // Shift bits and add the lower 7 bits of the byte
435435+ n = (n << 7n) | BigInt(v & 0x7F);
436436+ bits += 7;
437437+ // If the most significant bit is 0, this is the last byte of the OID component
438438+ if (!(v & 0x80)) { // finished
439439+ // If this is the first component, handle it specially
440440+ if (s === '') {
441441+ if (isRelative) {
442442+ s = n.toString();
443443+ } else {
444444+ let m = n < 80 ? n < 40 ? 0n : 1n : 2n;
445445+ s = m + '.' + (n - m * 40n);
446446+ }
447447+ } else
448448+ s += '.' + n;
449449+ if (s.length > maxLength)
450450+ return stringCut(s, maxLength);
451451+ n = 0n;
452452+ bits = 0;
453453+ }
454454+ }
455455+ if (bits > 0)
456456+ s += '.incomplete';
457457+ // If OIDs mapping is available and the OID is absolute, try to resolve it
458458+ if (typeof oids === 'object' && !isRelative) {
459459+ let oid = oids[s];
460460+ if (oid) {
461461+ if (oid.d) s += '\n' + oid.d;
462462+ if (oid.c) s += '\n' + oid.c;
463463+ if (oid.w) s += '\n(warning!)';
464464+ }
465465+ }
466466+ return s;
467467+ }
468468+469469+ /**
470470+ * Parse a region of the stream as a relative OID (Object Identifier).
471471+ * @param {number} start - starting position (included)
472472+ * @param {number} end - ending position (excluded)
473473+ * @param {number} maxLength - maximum length of the output string
474474+ * @returns {string} Formatted relative OID string
475475+ */
476476+ parseRelativeOID(start, end, maxLength) {
477477+ return this.parseOID(start, end, maxLength, true);
478478+ }
280479}
281281-ASN1.prototype.typeName = function () {
282282- switch (this.tag.tagClass) {
283283- case 0: // universal
480480+481481+function recurse(el, parser, maxLength) {
482482+ let avoidRecurse = true;
483483+ if (el.tag.tagConstructed && el.sub) {
484484+ avoidRecurse = false;
485485+ el.sub.forEach(function (e1) {
486486+ if (e1.tag.tagClass != el.tag.tagClass || e1.tag.tagNumber != el.tag.tagNumber)
487487+ avoidRecurse = true;
488488+ });
489489+ }
490490+ if (avoidRecurse)
491491+ return el.stream[parser](el.posContent(), el.posContent() + Math.abs(el.length), maxLength);
492492+ let d = { size: 0, str: '' };
493493+ el.sub.forEach(function (el) {
494494+ let d1 = recurse(el, parser, maxLength - d.str.length);
495495+ d.size += d1.size;
496496+ d.str += d1.str;
497497+ });
498498+ return d;
499499+}
500500+501501+class ASN1Tag {
502502+ constructor(stream) {
503503+ let buf = stream.get();
504504+ this.tagClass = buf >> 6;
505505+ this.tagConstructed = ((buf & 0x20) !== 0);
506506+ this.tagNumber = buf & 0x1F;
507507+ if (this.tagNumber == 0x1F) { // long tag
508508+ let n = 0n;
509509+ do {
510510+ buf = stream.get();
511511+ n = (n << 7n) | BigInt(buf & 0x7F);
512512+ } while (buf & 0x80);
513513+ this.tagNumber = n <= Number.MAX_SAFE_INTEGER ? Number(n) : n;
514514+ }
515515+ }
516516+ isUniversal() {
517517+ return this.tagClass === 0x00;
518518+ }
519519+ isEOC() {
520520+ return this.tagClass === 0x00 && this.tagNumber === 0x00;
521521+ }
522522+}
523523+524524+/**
525525+ * ASN1 class for parsing ASN.1 encoded data.
526526+ * Instances of this class represent an ASN.1 element and provides methods to parse and display its content.
527527+ */
528528+export class ASN1 {
529529+ /**
530530+ * Creates an ASN1 parser object.
531531+ * @param {Stream} stream - The stream containing the ASN.1 data.
532532+ * @param {number} header - The header length.
533533+ * @param {number} length - The length of the data.
534534+ * @param {ASN1Tag} tag - The ASN.1 tag.
535535+ * @param {number} tagLen - The length of the tag.
536536+ * @param {Array} sub - The sub-elements.
537537+ */
538538+ constructor(stream, header, length, tag, tagLen, sub) {
539539+ if (!(tag instanceof ASN1Tag)) throw new Error('Invalid tag value.');
540540+ this.stream = stream;
541541+ this.header = header;
542542+ this.length = length;
543543+ this.tag = tag;
544544+ this.tagLen = tagLen;
545545+ this.sub = sub;
546546+ }
547547+548548+ /**
549549+ * Get the type name of the ASN.1 element.
550550+ * @returns {string} The type name.
551551+ */
552552+ typeName() {
553553+ switch (this.tag.tagClass) {
554554+ case 0: // universal
555555+ switch (this.tag.tagNumber) {
556556+ case 0x00: return 'EOC';
557557+ case 0x01: return 'BOOLEAN';
558558+ case 0x02: return 'INTEGER';
559559+ case 0x03: return 'BIT_STRING';
560560+ case 0x04: return 'OCTET_STRING';
561561+ case 0x05: return 'NULL';
562562+ case 0x06: return 'OBJECT_IDENTIFIER';
563563+ case 0x07: return 'ObjectDescriptor';
564564+ case 0x08: return 'EXTERNAL';
565565+ case 0x09: return 'REAL';
566566+ case 0x0A: return 'ENUMERATED';
567567+ case 0x0B: return 'EMBEDDED_PDV';
568568+ case 0x0C: return 'UTF8String';
569569+ case 0x0D: return 'RELATIVE_OID';
570570+ case 0x10: return 'SEQUENCE';
571571+ case 0x11: return 'SET';
572572+ case 0x12: return 'NumericString';
573573+ case 0x13: return 'PrintableString'; // ASCII subset
574574+ case 0x14: return 'TeletexString'; // aka T61String
575575+ case 0x15: return 'VideotexString';
576576+ case 0x16: return 'IA5String'; // ASCII
577577+ case 0x17: return 'UTCTime';
578578+ case 0x18: return 'GeneralizedTime';
579579+ case 0x19: return 'GraphicString';
580580+ case 0x1A: return 'VisibleString'; // ASCII subset
581581+ case 0x1B: return 'GeneralString';
582582+ case 0x1C: return 'UniversalString';
583583+ case 0x1E: return 'BMPString';
584584+ }
585585+ return 'Universal_' + this.tag.tagNumber.toString();
586586+ case 1: return 'Application_' + this.tag.tagNumber.toString();
587587+ case 2: return '[' + this.tag.tagNumber.toString() + ']'; // Context
588588+ case 3: return 'Private_' + this.tag.tagNumber.toString();
589589+ }
590590+ }
591591+592592+ /**
593593+ * Get a string preview of the content (intended for humans).
594594+ * @param {number} maxLength - The maximum length of the content.
595595+ * @returns {string|null} The content preview or null if not supported.
596596+ */
597597+ content(maxLength) {
598598+ if (this.tag === undefined)
599599+ return null;
600600+ if (maxLength === undefined)
601601+ maxLength = Infinity;
602602+ const content = this.posContent(),
603603+ len = Math.abs(this.length);
604604+ if (!this.tag.isUniversal()) {
605605+ if (this.sub !== null)
606606+ return '(' + this.sub.length + ' elem)';
607607+ let d1 = this.stream.parseOctetString(content, content + len, maxLength);
608608+ return '(' + d1.size + ' byte)\n' + d1.str;
609609+ }
284610 switch (this.tag.tagNumber) {
285285- case 0x00: return "EOC";
286286- case 0x01: return "BOOLEAN";
287287- case 0x02: return "INTEGER";
288288- case 0x03: return "BIT_STRING";
289289- case 0x04: return "OCTET_STRING";
290290- case 0x05: return "NULL";
291291- case 0x06: return "OBJECT_IDENTIFIER";
292292- case 0x07: return "ObjectDescriptor";
293293- case 0x08: return "EXTERNAL";
294294- case 0x09: return "REAL";
295295- case 0x0A: return "ENUMERATED";
296296- case 0x0B: return "EMBEDDED_PDV";
297297- case 0x0C: return "UTF8String";
298298- case 0x10: return "SEQUENCE";
299299- case 0x11: return "SET";
300300- case 0x12: return "NumericString";
301301- case 0x13: return "PrintableString"; // ASCII subset
302302- case 0x14: return "TeletexString"; // aka T61String
303303- case 0x15: return "VideotexString";
304304- case 0x16: return "IA5String"; // ASCII
305305- case 0x17: return "UTCTime";
306306- case 0x18: return "GeneralizedTime";
307307- case 0x19: return "GraphicString";
308308- case 0x1A: return "VisibleString"; // ASCII subset
309309- case 0x1B: return "GeneralString";
310310- case 0x1C: return "UniversalString";
311311- case 0x1E: return "BMPString";
611611+ case 0x01: // BOOLEAN
612612+ if (len != 1) return 'invalid length ' + len;
613613+ return (this.stream.get(content) === 0) ? 'false' : 'true';
614614+ case 0x02: // INTEGER
615615+ if (len < 1) return 'invalid length ' + len;
616616+ return this.stream.parseInteger(content, content + len);
617617+ case 0x03: { // BIT_STRING
618618+ let d = recurse(this, 'parseBitString', maxLength);
619619+ return '(' + d.size + ' bit)\n' + d.str;
620620+ }
621621+ case 0x04: { // OCTET_STRING
622622+ let d = recurse(this, 'parseOctetString', maxLength);
623623+ return '(' + d.size + ' byte)\n' + d.str;
624624+ }
625625+ //case 0x05: // NULL
626626+ case 0x06: // OBJECT_IDENTIFIER
627627+ if (len < 1) return 'invalid length ' + len; // pgut001's dumpasn1.c enforces a minimum lenght of 3
628628+ return this.stream.parseOID(content, content + len, maxLength);
629629+ //case 0x07: // ObjectDescriptor
630630+ //case 0x08: // EXTERNAL
631631+ //case 0x09: // REAL
632632+ case 0x0A: // ENUMERATED
633633+ return this.stream.parseInteger(content, content + len);
634634+ //case 0x0B: // EMBEDDED_PDV
635635+ case 0x0D: // RELATIVE-OID
636636+ return this.stream.parseRelativeOID(content, content + len, maxLength);
637637+ case 0x10: // SEQUENCE
638638+ case 0x11: // SET
639639+ if (this.sub !== null)
640640+ return '(' + this.sub.length + ' elem)';
641641+ else
642642+ return '(no elem)';
643643+ case 0x0C: // UTF8String
644644+ return recurse(this, 'parseStringUTF', maxLength).str;
645645+ case 0x14: // TeletexString
646646+ return recurse(this, 'parseStringT61', maxLength).str;
647647+ case 0x12: // NumericString
648648+ case 0x13: // PrintableString
649649+ case 0x15: // VideotexString
650650+ case 0x16: // IA5String
651651+ case 0x1A: // VisibleString
652652+ case 0x1B: // GeneralString
653653+ //case 0x19: // GraphicString
654654+ //case 0x1C: // UniversalString
655655+ return recurse(this, 'parseStringISO', maxLength).str;
656656+ case 0x1E: // BMPString
657657+ return recurse(this, 'parseStringBMP', maxLength).str;
658658+ case 0x17: // UTCTime
659659+ case 0x18: // GeneralizedTime
660660+ return this.stream.parseTime(content, content + len, (this.tag.tagNumber == 0x17));
312661 }
313313- return "Universal_" + this.tag.tagNumber.toString();
314314- case 1: return "Application_" + this.tag.tagNumber.toString();
315315- case 2: return "[" + this.tag.tagNumber.toString() + "]"; // Context
316316- case 3: return "Private_" + this.tag.tagNumber.toString();
317317- }
318318-};
319319-ASN1.prototype.content = function (maxLength) { // a preview of the content (intended for humans)
320320- if (this.tag === undefined)
321662 return null;
322322- if (maxLength === undefined)
323323- maxLength = Infinity;
324324- var content = this.posContent(),
325325- len = Math.abs(this.length);
326326- if (!this.tag.isUniversal()) {
327327- if (this.sub !== null)
328328- return "(" + this.sub.length + " elem)";
329329- return this.stream.parseOctetString(content, content + len, maxLength);
330663 }
331331- switch (this.tag.tagNumber) {
332332- case 0x01: // BOOLEAN
333333- return (this.stream.get(content) === 0) ? "false" : "true";
334334- case 0x02: // INTEGER
335335- return this.stream.parseInteger(content, content + len);
336336- case 0x03: // BIT_STRING
337337- return this.sub ? "(" + this.sub.length + " elem)" :
338338- this.stream.parseBitString(content, content + len, maxLength);
339339- case 0x04: // OCTET_STRING
340340- return this.sub ? "(" + this.sub.length + " elem)" :
341341- this.stream.parseOctetString(content, content + len, maxLength);
342342- //case 0x05: // NULL
343343- case 0x06: // OBJECT_IDENTIFIER
344344- return this.stream.parseOID(content, content + len, maxLength);
345345- //case 0x07: // ObjectDescriptor
346346- //case 0x08: // EXTERNAL
347347- //case 0x09: // REAL
348348- case 0x0A: // ENUMERATED
349349- return this.stream.parseInteger(content, content + len);
350350- //case 0x0B: // EMBEDDED_PDV
351351- case 0x10: // SEQUENCE
352352- case 0x11: // SET
353353- if (this.sub !== null)
354354- return "(" + this.sub.length + " elem)";
355355- else
356356- return "(no elem)";
357357- case 0x0C: // UTF8String
358358- return stringCut(this.stream.parseStringUTF(content, content + len), maxLength);
359359- case 0x12: // NumericString
360360- case 0x13: // PrintableString
361361- case 0x14: // TeletexString
362362- case 0x15: // VideotexString
363363- case 0x16: // IA5String
364364- //case 0x19: // GraphicString
365365- case 0x1A: // VisibleString
366366- case 0x1B: // GeneralString
367367- //case 0x1C: // UniversalString
368368- return stringCut(this.stream.parseStringISO(content, content + len), maxLength);
369369- case 0x1E: // BMPString
370370- return stringCut(this.stream.parseStringBMP(content, content + len), maxLength);
371371- case 0x17: // UTCTime
372372- case 0x18: // GeneralizedTime
373373- return this.stream.parseTime(content, content + len, (this.tag.tagNumber == 0x17));
664664+665665+ /**
666666+ * Get a string representation of the ASN.1 element.
667667+ * @returns {string} The string representation.
668668+ */
669669+ toString() {
670670+ return this.typeName() + '@' + this.stream.pos + '[header:' + this.header + ',length:' + this.length + ',sub:' + ((this.sub === null) ? 'null' : this.sub.length) + ']';
374671 }
375375- return null;
376376-};
377377-ASN1.prototype.toString = function () {
378378- return this.typeName() + "@" + this.stream.pos + "[header:" + this.header + ",length:" + this.length + ",sub:" + ((this.sub === null) ? 'null' : this.sub.length) + "]";
379379-};
380380-ASN1.prototype.toPrettyString = function (indent) {
381381- if (indent === undefined) indent = '';
382382- var s = indent + this.typeName() + " @" + this.stream.pos;
383383- if (this.length >= 0)
384384- s += "+";
385385- s += this.length;
386386- if (this.tag.tagConstructed)
387387- s += " (constructed)";
388388- else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null))
389389- s += " (encapsulates)";
390390- var content = this.content();
391391- if (content)
392392- s += ": " + content.replace(/\n/g, '|');
393393- s += "\n";
394394- if (this.sub !== null) {
395395- indent += ' ';
396396- for (var i = 0, max = this.sub.length; i < max; ++i)
397397- s += this.sub[i].toPrettyString(indent);
672672+673673+ /**
674674+ * Get a pretty string representation of the ASN.1 element.
675675+ * @param {string} indent - The indentation string.
676676+ * @returns {string} The pretty string representation.
677677+ */
678678+ toPrettyString(indent) {
679679+ if (indent === undefined) indent = '';
680680+ let s = indent;
681681+ if (this.def) {
682682+ if (this.def.id)
683683+ s += this.def.id + ' ';
684684+ if (this.def.name && this.def.name != this.typeName().replace(/_/g, ' '))
685685+ s+= this.def.name + ' ';
686686+ if (this.def.mismatch)
687687+ s += '[?] ';
688688+ }
689689+ s += this.typeName() + ' @' + this.stream.pos;
690690+ if (this.length >= 0)
691691+ s += '+';
692692+ s += this.length;
693693+ if (this.tag.tagConstructed)
694694+ s += ' (constructed)';
695695+ else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null))
696696+ s += ' (encapsulates)';
697697+ let content = this.content();
698698+ if (content)
699699+ s += ': ' + content.replace(/\n/g, '|');
700700+ s += '\n';
701701+ if (this.sub !== null) {
702702+ indent += ' ';
703703+ for (let i = 0, max = this.sub.length; i < max; ++i)
704704+ s += this.sub[i].toPrettyString(indent);
705705+ }
706706+ return s;
398707 }
399399- return s;
400400-};
401401-ASN1.prototype.posStart = function () {
402402- return this.stream.pos;
403403-};
404404-ASN1.prototype.posContent = function () {
405405- return this.stream.pos + this.header;
406406-};
407407-ASN1.prototype.posEnd = function () {
408408- return this.stream.pos + this.header + Math.abs(this.length);
409409-};
410410-/** Position of the length. */
411411-ASN1.prototype.posLen = function() {
412412- return this.stream.pos + this.tagLen;
413413-};
414414-ASN1.prototype.toHexString = function () {
415415- return this.stream.hexDump(this.posStart(), this.posEnd(), true);
416416-};
417417-ASN1.prototype.toB64String = function () {
418418- return this.stream.b64Dump(this.posStart(), this.posEnd());
419419-};
420420-ASN1.decodeLength = function (stream) {
421421- var buf = stream.get(),
422422- len = buf & 0x7F;
423423- if (len == buf)
424424- return len;
425425- if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways
426426- throw "Length over 48 bits not supported at position " + (stream.pos - 1);
427427- if (len === 0)
428428- return null; // undefined
429429- buf = 0;
430430- for (var i = 0; i < len; ++i)
431431- buf = (buf * 256) + stream.get();
432432- return buf;
433433-};
434434-function ASN1Tag(stream) {
435435- var buf = stream.get();
436436- this.tagClass = buf >> 6;
437437- this.tagConstructed = ((buf & 0x20) !== 0);
438438- this.tagNumber = buf & 0x1F;
439439- if (this.tagNumber == 0x1F) { // long tag
440440- var n = new Int10();
441441- do {
442442- buf = stream.get();
443443- n.mulAdd(128, buf & 0x7F);
444444- } while (buf & 0x80);
445445- this.tagNumber = n.simplify();
708708+709709+ /**
710710+ * Get the starting position of the element in the stream.
711711+ * @returns {number} The starting position.
712712+ */
713713+ posStart() {
714714+ return this.stream.pos;
715715+ }
716716+717717+ /**
718718+ * Get the position of the content in the stream.
719719+ * @returns {number} The content position.
720720+ */
721721+ posContent() {
722722+ return this.stream.pos + this.header;
723723+ }
724724+725725+ /**
726726+ * Get the ending position of the element in the stream.
727727+ * @returns {number} The ending position.
728728+ */
729729+ posEnd() {
730730+ return this.stream.pos + this.header + Math.abs(this.length);
731731+ }
732732+733733+ /**
734734+ * Get the position of the length in the stream.
735735+ * @returns {number} The length position.
736736+ */
737737+ posLen() {
738738+ return this.stream.pos + this.tagLen;
739739+ }
740740+741741+ /**
742742+ * Get a hexadecimal dump of the node.
743743+ * @param {string} [type='raw'] - The dump type: 'raw', 'byte', or 'dump'.
744744+ * @returns {string} The hexadecimal dump.
745745+ */
746746+ toHexString(type = 'raw') {
747747+ return this.stream.hexDump(this.posStart(), this.posEnd(), type);
748748+ }
749749+750750+ /**
751751+ * Get a base64url dump of the node (according to RFC 4648 section 5).
752752+ * @param {string} [type='url'] - The dump type: 'url' (section 5 without padding) or 'std' (section 4 with padding).
753753+ * @returns {string} The base64 encoded representation.
754754+ */
755755+ toB64String(type = 'url') {
756756+ return this.stream.b64Dump(this.posStart(), this.posEnd(), type);
757757+ }
758758+759759+ /**
760760+ * Decode the length field of an ASN.1 element.
761761+ * @param {Stream} stream - The stream to read from.
762762+ * @returns {number|null} The decoded length, or null for indefinite length.
763763+ * @throws {Error} If the length is invalid or exceeds 48 bits.
764764+ */
765765+ static decodeLength(stream) {
766766+ const buf = stream.get(),
767767+ len = buf & 0x7F;
768768+ if (len == buf) // first bit was 0, short form
769769+ return len;
770770+ if (len === 0) // long form with length 0 is a special case
771771+ return null; // undefined length
772772+ if (len > 6) // no reason to use BigInt, as it would be a huge buffer anyways
773773+ throw new Error('Length over 48 bits not supported at position ' + (stream.pos - 1));
774774+ let value = 0;
775775+ for (let i = 0; i < len; ++i)
776776+ value = (value << 8) | stream.get();
777777+ return value;
446778 }
447447-}
448448-ASN1Tag.prototype.isUniversal = function () {
449449- return this.tagClass === 0x00;
450450-};
451451-ASN1Tag.prototype.isEOC = function () {
452452- return this.tagClass === 0x00 && this.tagNumber === 0x00;
453453-};
454454-ASN1.decode = function (stream, offset) {
455455- if (!(stream instanceof Stream))
456456- stream = new Stream(stream, offset || 0);
457457- var streamStart = new Stream(stream),
458458- tag = new ASN1Tag(stream),
459459- tagLen = stream.pos - streamStart.pos,
460460- len = ASN1.decodeLength(stream),
461461- start = stream.pos,
462462- header = start - streamStart.pos,
463463- sub = null,
464464- getSub = function () {
465465- sub = [];
466466- if (len !== null) {
467467- // definite length
468468- var end = start + len;
469469- if (end > stream.enc.length)
470470- throw 'Container at offset ' + start + ' has a length of ' + len + ', which is past the end of the stream';
471471- while (stream.pos < end)
472472- sub[sub.length] = ASN1.decode(stream);
473473- if (stream.pos != end)
474474- throw 'Content size is not correct for container at offset ' + start;
475475- } else {
476476- // undefined length
477477- try {
478478- for (;;) {
479479- var s = ASN1.decode(stream);
480480- if (s.tag.isEOC())
481481- break;
482482- sub[sub.length] = s;
779779+780780+ /**
781781+ * Decode an ASN.1 element from a stream.
782782+ * @param {Stream|array|string} stream - The input data.
783783+ * @param {number} [offset=0] - The offset to start decoding from.
784784+ * @param {Function} [type=ASN1] - The class to instantiate.
785785+ * @returns {ASN1} The decoded ASN.1 element.
786786+ * @throws {Error} If the decoding fails.
787787+ */
788788+ static decode(stream, offset, type = ASN1) {
789789+ if (!(type == ASN1 || type.prototype instanceof ASN1))
790790+ throw new Error('Must pass a class that extends ASN1');
791791+ if (!(stream instanceof Stream))
792792+ stream = new Stream(stream, offset || 0);
793793+ let streamStart = new Stream(stream),
794794+ tag = new ASN1Tag(stream),
795795+ tagLen = stream.pos - streamStart.pos,
796796+ len = ASN1.decodeLength(stream),
797797+ start = stream.pos,
798798+ header = start - streamStart.pos,
799799+ sub = null,
800800+ getSub = function () {
801801+ sub = [];
802802+ if (len !== null) {
803803+ // definite length
804804+ let end = start + len;
805805+ if (end > stream.enc.length)
806806+ throw new Error('Container at offset ' + start + ' has a length of ' + len + ', which is past the end of the stream');
807807+ while (stream.pos < end)
808808+ sub[sub.length] = type.decode(stream);
809809+ if (stream.pos != end)
810810+ throw new Error('Content size is not correct for container at offset ' + start);
811811+ } else {
812812+ // undefined length
813813+ try {
814814+ for (;;) {
815815+ let s = type.decode(stream);
816816+ if (s.tag.isEOC())
817817+ break;
818818+ sub[sub.length] = s;
819819+ }
820820+ len = start - stream.pos; // undefined lengths are represented as negative values
821821+ } catch (e) {
822822+ throw new Error('Exception while decoding undefined length content at offset ' + start + ': ' + e);
823823+ }
824824+ }
825825+ };
826826+ if (tag.tagConstructed) {
827827+ // must have valid content
828828+ getSub();
829829+ } else if (tag.isUniversal() && ((tag.tagNumber == 0x03) || (tag.tagNumber == 0x04))) {
830830+ // sometimes BitString and OctetString are used to encapsulate ASN.1
831831+ try {
832832+ if (tag.tagNumber == 0x03)
833833+ if (stream.get() != 0)
834834+ throw new Error('BIT STRINGs with unused bits cannot encapsulate.');
835835+ getSub();
836836+ for (let s of sub) {
837837+ if (s.tag.isEOC())
838838+ throw new Error('EOC is not supposed to be actual content.');
839839+ try {
840840+ s.content();
841841+ } catch (e) {
842842+ throw new Error('Unable to parse content: ' + e);
483843 }
484484- len = start - stream.pos; // undefined lengths are represented as negative values
485485- } catch (e) {
486486- throw 'Exception while decoding undefined length content at offset ' + start + ': ' + e;
487844 }
845845+ } catch (ignore) {
846846+ // but silently ignore when they don't
847847+ sub = null;
848848+ //DEBUG console.log('Could not decode structure at ' + start + ':', e);
488849 }
489489- };
490490- if (tag.tagConstructed) {
491491- // must have valid content
492492- getSub();
493493- } else if (tag.isUniversal() && ((tag.tagNumber == 0x03) || (tag.tagNumber == 0x04))) {
494494- // sometimes BitString and OctetString are used to encapsulate ASN.1
495495- try {
496496- if (tag.tagNumber == 0x03)
497497- if (stream.get() != 0)
498498- throw "BIT STRINGs with unused bits cannot encapsulate.";
499499- getSub();
500500- for (var i = 0; i < sub.length; ++i)
501501- if (sub[i].tag.isEOC())
502502- throw 'EOC is not supposed to be actual content.';
503503- } catch (e) {
504504- // but silently ignore when they don't
505505- sub = null;
506506- //DEBUG console.log('Could not decode structure at ' + start + ':', e);
850850+ }
851851+ if (sub === null) {
852852+ if (len === null)
853853+ throw new Error("We can't skip over an invalid tag with undefined length at offset " + start);
854854+ stream.pos = start + Math.abs(len);
507855 }
856856+ return new type(streamStart, header, len, tag, tagLen, sub);
508857 }
509509- if (sub === null) {
510510- if (len === null)
511511- throw "We can't skip over an invalid tag with undefined length at offset " + start;
512512- stream.pos = start + Math.abs(len);
513513- }
514514- return new ASN1(streamStart, header, len, tag, tagLen, sub);
515515-};
516858517517-return ASN1;
518518-519519-});
859859+}
+79-80
base64.js
···11// Base64 JavaScript decoder
22-// Copyright (c) 2008-2020 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";
2121-2222-var Base64 = {},
2323- decoder, // populated on first usage
1616+const
2417 haveU8 = (typeof Uint8Array == 'function');
25182626-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)
1919+let decoder; // populated on first usage
2020+2121+export class Base64 {
2222+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 }
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;
6274 }
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;
7575+7676+ static pretty(str) {
7777+ // fix padding
7878+ let pad = 4 - str.length % 4;
7979+ if (pad < 4)
8080+ str += '==='.slice(0, pad);
8181+ // convert Base64url (RFC 4648 section 5) to standard Base64 (RFC 4648 section 4)
8282+ str = str.replace(/-/g, '+').replace(/_/g, '/');
8383+ // 80 column width
8484+ return str.replace(/.{80}/g, '$&\n');
7385 }
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-};
78867979-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-};
8888-8989-Base64.re = /-----BEGIN [^-]+-----([A-Za-z0-9+\/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([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
9898- 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);
99100 }
100100- return Base64.decode(a);
101101-};
102101103103-return Base64;
102102+}
104103105105-});
104104+Base64.re = /-----BEGIN [^-]+-----([A-Za-z0-9+/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+/=\s]+)====|^([A-Za-z0-9+/=\s]+)$/;
···11+CMPv2 example as found on Wireshark page.
22+33+Original link:
44+https://wiki.wireshark.org/CMP
55+66+Attachment found moved here:
77+https://wiki.wireshark.org/uploads/__moin_import__/attachments/SampleCaptures/cmp_IR_sequence_OpenSSL-Cryptlib.pcap
88+99+begin-base64 644 cmpv2.der
1010+MIICPjCB1QIBAqQCMACkRjBEMQswCQYDVQQGEwJERTEMMAoGA1UEChMDTlNOMREwDwYDVQQLEwhQ
1111+RyBSREUgMzEUMBIGA1UEAxMLTWFydGluJ3MgQ0GgERgPMjAxMDA3MDUwNzM1MzhaoTwwOgYJKoZI
1212+hvZ9B0INMC0EEJ5EpSD3zKjvmzHEK5+aoAAwCQYFKw4DAhoFAAICAfQwCgYIKwYBBQUIAQKiCwQJ
1313+b/KGO0ILNJqApBIEEJGOKFG/9crkwU+z/I5ICa6lEgQQnnbd7EB2QjRCwOHt9QWdBKCCAUkwggFF
1414+MIIBQTCBqAIBADCBoqaBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqVTOtjEEYELkomc3sMOy
1515+Too5a9YeC91IMn52cVx7doY4AeO6J9e8p+CtWNbzVF8aRgHUhh31m+/X3MkQOaY5i8nF33uxAxDL
1616+MDXttHjsqrF/tsgYuuHSs/Znz4PA1kLkdhKE9DLiGlCFaJH5QY5Hzl6bcS3ApuWCny0RRzIA1/cC
1717+AwEAAaGBkzANBgkqhkiG9w0BAQUFAAOBgQArOldjg75fDx7BaFp0oAknLDREvB1KyE+BV96R+lB+
1818+tRRhwv3dyc/GTvRw4GtaeDjWCjNPaDCl9ZvvVljaR2aMZvhaQV+DUmCMjFSP3DPiGuszBA6R2azX
1919+NKtnpJ3SGx2vk0+Iv05tXLhdnqQJZs5a3S3R30kn4Vw+4WQm3kb0fKAXAxUA9K8u+7hv5Rg6GDn6
2020+aoPxbUo6fpU=
2121+====
+9
examples/cms-password.p7m
···11+This is a PKCS#7/CMS encrypted with passwod.
22+$ echo content | openssl cms -encrypt -pwri_password test -aes256 -outform pem -out examples/cms-password.p7m
33+-----BEGIN CMS-----
44+MIHYBgkqhkiG9w0BBwOggcowgccCAQMxgYOjgYACAQCgGwYJKoZIhvcNAQUMMA4E
55+CED/DSxXMtH6AgIIADAsBgsqhkiG9w0BCRADCTAdBglghkgBZQMEASoEEDIQbJMC
66+Sfb3LpwHduj/meQEMKwrwq5M4V0stztm6OUTAsFY2zKDY20SApwSEeEcAh9TM42E
77+1palnHeqHTBpC8pIpjA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBByt+scPrdM
88+giR7WUOJyB3hgBDcD3UDMtZSep8X/3yy1/Yq
99+-----END CMS-----
+12
examples/crl-rfc5280.b64
···11+CRL example from RFC5280 as found here:
22+https://csrc.nist.gov/projects/pki-testing/sample-certificates-and-crls
33+44+begin-base64 644 crl-rfc5280.der
55+MIIBYDCBygIBATANBgkqhkiG9w0BAQUFADBDMRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZIm
66+iZPyLGQBGRYHZXhhbXBsZTETMBEGA1UEAxMKRXhhbXBsZSBDQRcNMDUwMjA1MTIwMDAwWhcNMDUw
77+MjA2MTIwMDAwWjAiMCACARIXDTA0MTExOTE1NTcwM1owDDAKBgNVHRUEAwoBAaAvMC0wHwYDVR0j
88+BBgwFoAUCGivhTPIOUp6+IKTjnBqSiCELDIwCgYDVR0UBAMCAQwwDQYJKoZIhvcNAQEFBQADgYEA
99+ItwYffcIzsx10NBqm60Q9HYjtIFutW2+DvsVFGzIF20f7pAXom9g5L2qjFXejoRvkvifEBInr0rU
1010+L4XiNkR9qqNMJTgV/wD9Pn7uPSYS69jnK2LiK8NGgO94gtEVxtCccmrLznrtZ5mLbnCBfUNCdMGm
1111+r8FVF6IzTNYGmCuk/C4=
1212+====
+45
examples/crl-rfc5280.b64.dump
···11+CertificateList SEQUENCE @0+352 (constructed): (3 elem)
22+ tbsCertList TBSCertList SEQUENCE @4+202 (constructed): (7 elem)
33+ version Version INTEGER @7+1: 1
44+ signature AlgorithmIdentifier SEQUENCE @10+13 (constructed): (2 elem)
55+ algorithm OBJECT_IDENTIFIER @12+9: 1.2.840.113549.1.1.5|sha1WithRSAEncryption|PKCS #1
66+ parameters ANY NULL @23+0
77+ issuer rdnSequence Name SEQUENCE @25+67 (constructed): (3 elem)
88+ RelativeDistinguishedName SET @27+19 (constructed): (1 elem)
99+ AttributeTypeAndValue SEQUENCE @29+17 (constructed): (2 elem)
1010+ type AttributeType OBJECT_IDENTIFIER @31+10: 0.9.2342.19200300.100.1.25|domainComponent|Men are from Mars, this OID is from Pluto
1111+ value AttributeValue [?] IA5String @43+3: com
1212+ RelativeDistinguishedName SET @48+23 (constructed): (1 elem)
1313+ AttributeTypeAndValue SEQUENCE @50+21 (constructed): (2 elem)
1414+ type AttributeType OBJECT_IDENTIFIER @52+10: 0.9.2342.19200300.100.1.25|domainComponent|Men are from Mars, this OID is from Pluto
1515+ value AttributeValue [?] IA5String @64+7: example
1616+ RelativeDistinguishedName SET @73+19 (constructed): (1 elem)
1717+ AttributeTypeAndValue SEQUENCE @75+17 (constructed): (2 elem)
1818+ type AttributeType OBJECT_IDENTIFIER @77+3: 2.5.4.3|commonName|X.520 DN component
1919+ value AttributeValue [?] PrintableString @82+10: Example CA
2020+ thisUpdate utcTime Time UTCTime @94+13: 2005-02-05 12:00:00 UTC
2121+ nextUpdate utcTime Time UTCTime @109+13: 2005-02-06 12:00:00 UTC
2222+ revokedCertificates SEQUENCE @124+34 (constructed): (1 elem)
2323+ SEQUENCE @126+32 (constructed): (3 elem)
2424+ userCertificate CertificateSerialNumber INTEGER @128+1: 18
2525+ revocationDate utcTime Time UTCTime @131+13: 2004-11-19 15:57:03 UTC
2626+ crlEntryExtensions Extensions SEQUENCE @146+12 (constructed): (1 elem)
2727+ Extension SEQUENCE @148+10 (constructed): (2 elem)
2828+ extnID OBJECT_IDENTIFIER @150+3: 2.5.29.21|cRLReason|X.509 extension
2929+ extnValue OCTET_STRING @155+3 (encapsulates): (3 byte)|0A0101
3030+ ENUMERATED @157+1: 1
3131+ crlExtensions [0] @160+47 (constructed): (1 elem)
3232+ Extensions SEQUENCE @162+45 (constructed): (2 elem)
3333+ Extension SEQUENCE @164+31 (constructed): (2 elem)
3434+ extnID OBJECT_IDENTIFIER @166+3: 2.5.29.35|authorityKeyIdentifier|X.509 extension
3535+ extnValue OCTET_STRING @171+24 (encapsulates): (24 byte)|301680140868AF8533C8394A7AF882938E706A4A20842C32
3636+ SEQUENCE @173+22 (constructed): (1 elem)
3737+ [0] @175+20: (20 byte)|0868AF8533C8394A7AF882938E706A4A20842C32
3838+ Extension SEQUENCE @197+10 (constructed): (2 elem)
3939+ extnID OBJECT_IDENTIFIER @199+3: 2.5.29.20|cRLNumber|X.509 extension
4040+ extnValue OCTET_STRING @204+3 (encapsulates): (3 byte)|02010C
4141+ INTEGER @206+1: 12
4242+ signatureAlgorithm AlgorithmIdentifier SEQUENCE @209+13 (constructed): (2 elem)
4343+ algorithm OBJECT_IDENTIFIER @211+9: 1.2.840.113549.1.1.5|sha1WithRSAEncryption|PKCS #1
4444+ parameters ANY NULL @222+0
4545+ signature BIT_STRING @224+129: (1024 bit)|0010001011011100000110000111110111110111000010001100111011001100011101011101000011010000011010101001101110101101000100001111010001110110001000111011010010000001011011101011010101101101101111100000111011111011000101010001010001101100110010000001011101101101000111111110111010010000000101111010001001101111011000001110010010111101101010101000110001010101110111101000111010000100011011111001001011111000100111110001000000010010001001111010111101001010110101000010111110000101111000100011011001000100011111011010101010100011010011000010010100111000000101011111111100000000111111010011111001111110111011100011110100100110000100101110101111011000111001110010101101100010111000100010101111000011010001101000000011101111011110001000001011010001000101011100011011010000100111000111001001101010110010111100111001111010111011010110011110011001100010110110111001110000100000010111110101000011010000100111010011000001101001101010111111000001010101010001011110100010001100110100110011010110000001101001100000101011101001001111110000101110
+13
examples/ed25519.cer
···11+X.509 certificate based on Daniel J. Bernsteinโs Curve25519 (as per RFC 8410).
22+$ openssl req -x509 -newkey ed25519 -keyout test.key -out test.cer -days 3652 -subj '/C=IT/L=Milano/CN=Test ed25519'
33+-----BEGIN CERTIFICATE-----
44+MIIBfzCCATGgAwIBAgIUfI5kSdcO2S0+LkpdL3b2VUJG10YwBQYDK2VwMDUxCzAJ
55+BgNVBAYTAklUMQ8wDQYDVQQHDAZNaWxhbm8xFTATBgNVBAMMDFRlc3QgZWQyNTUx
66+OTAeFw0yMDA5MDIxMzI1MjZaFw0zMDA5MDIxMzI1MjZaMDUxCzAJBgNVBAYTAklU
77+MQ8wDQYDVQQHDAZNaWxhbm8xFTATBgNVBAMMDFRlc3QgZWQyNTUxOTAqMAUGAytl
88+cAMhADupL/3LF2beQKKS95PeMPgKI6gxIV3QB9hjJC7/aCGFo1MwUTAdBgNVHQ4E
99+FgQUa6W9z536I1l4EmQXrh5y2JqASugwHwYDVR0jBBgwFoAUa6W9z536I1l4EmQX
1010+rh5y2JqASugwDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQBvc3e+KJZaMzbX5TT9
1111+kPP9QH8fAvkAV/IWDxZrBL9lhLaY0tDSv0zWbw624uidBKPgmVD5wm3ec60dNVeF
1212+ZYYG
1313+-----END CERTIFICATE-----
···11+LDAPMessage example as found on ldap.com.
22+33+Original link:
44+https://ldap.com/ldapv3-wire-protocol-reference-ldap-message/
55+66+begin-base64 644 ldapmessage.der
77+MDUCAQVKEWRjPWV4YW1wbGUsZGM9Y29toB0wGwQWMS4yLjg0MC4xMTM1NTYuMS40LjgwNQEB/w==
88+====
···11+This is a PKCS#7/CMS detached digital signature.
22+It is an old example generated in 2008 and as such uses obsolete cryptography: RSA1024 with SHA1.
33+-----BEGIN PKCS7-----
44+MIIDUAYJKoZIhvcNAQcCoIIDQTCCAz0CAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3
55+DQEHAaCCAfMwggHvMIIBWKADAgECAhAvoXazbunwSfREtACZZhlFMA0GCSqGSIb3
66+DQEBBQUAMAwxCjAIBgNVBAMMAWEwHhcNMDgxMDE1MTUwMzQxWhcNMDkxMDE1MTUw
77+MzQxWjAMMQowCAYDVQQDDAFhMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJ
88+Uwlwhu5hR8X01f+vG0mKPRHsVRjpZNxSEmsmFPdDiD9kylE3ertTDf0gRkpIvWfN
99+J+eymuxoXF0Qgl5gXAVuSrjupGD6J+VapixJiwLXJHokmDihLs3zfGARz08O3qnO
1010+5ofBy0pRxq5isu/bAAcjoByZ1sI/g0iAuotC1UFObwIDAQABo1IwUDAOBgNVHQ8B
1111+Af8EBAMCBPAwHQYDVR0OBBYEFEIGXQB4h+04Z3y/n7Nv94+CqPitMB8GA1UdIwQY
1212+MBaAFEIGXQB4h+04Z3y/n7Nv94+CqPitMA0GCSqGSIb3DQEBBQUAA4GBAE0G7tAi
1313+aacJxvP3fhEj+yP9VDxL0omrRRAEaMXwWaBf/Ggk1T/u+8/CDAdjuGNCiF6ctooK
1414+c8u8KpnZJsGqnpGQ4n6L2KjTtRUDh+hija0eJRBFdirPQe2HAebQGFnmOk6Mn7Ki
1515+QfBIsOzXim/bFqaBSbf06bLTQNwFouSO+jwOMYIBJTCCASECAQEwIDAMMQowCAYD
1616+VQQDDAFhAhAvoXazbunwSfREtACZZhlFMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0B
1717+CQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0wODEwMTUxNTAzNDNaMCMG
1818+CSqGSIb3DQEJBDEWBBQAAAAAAAAAAAAAAAAAAAAAAAAAADANBgkqhkiG9w0BAQEF
1919+AASBgHQe0ocjBn+ZVXWbb8CpZ2CxzFKiVrgZxZO2kMBwJoNyZx+jnICHdhfsX4Or
2020+cF5vIYVAZIRz5RxqFmuZELTfZ/K89zaK873DP9V7/ftBGpezWEp9h29AtAzI9lzS
2121+GB9gugiyB5JstXoM1L87KJmT05MeZxg1pvvFhwo1m/QOpcqz
2222+-----END PKCS7-----
···11// Hex JavaScript decoder
22-// Copyright (c) 2008-2020 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";
2121-2222-var Hex = {},
2323- decoder, // populated on first usage
1616+const
2417 haveU8 = (typeof Uint8Array == 'function');
25182626-Hex.decode = function(a) {
2727- var isString = (typeof a == 'string');
2828- var i;
2929- if (decoder === undefined) {
3030- var hex = "0123456789ABCDEF",
3131- ignore = " \f\n\r\t\u00A0\u2028\u2029";
3232- decoder = [];
3333- for (i = 0; i < 16; ++i)
3434- decoder[hex.charCodeAt(i)] = i;
3535- hex = hex.toLowerCase();
3636- for (i = 10; i < 16; ++i)
3737- decoder[hex.charCodeAt(i)] = i;
3838- for (i = 0; i < ignore.length; ++i)
3939- decoder[ignore.charCodeAt(i)] = -1;
4040- }
4141- var out = haveU8 ? new Uint8Array(a.length >> 1) : [],
4242- bits = 0,
4343- char_count = 0,
4444- len = 0;
4545- for (i = 0; i < a.length; ++i) {
4646- var c = isString ? a.charCodeAt(i) : a[i];
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 >= 2) {
5454- out[len++] = bits;
5555- bits = 0;
5656- char_count = 0;
5757- } else {
5858- bits <<= 4;
1919+let decoder; // populated on first usage
2020+2121+export class Hex {
2222+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+ }
5961 }
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;
6067 }
6161- if (char_count)
6262- throw "Hex encoding incomplete: 4 bits missing";
6363- if (haveU8 && out.length > len) // in case it was originally longer because of ignored characters
6464- out = out.subarray(0, len);
6565- return out;
6666-};
67686868-return Hex;
6969-7070-});
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]; });
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'),
1717- hash = null;
1818+ examples = id('examples'),
1919+ selectDefs = id('definitions'),
2020+ selectTag = id('tags');
18211919-require('dom'); // side effect: augment ASN1
2222+let hash = null;
2323+2424+if (!window.console || !window.console.log) // IE8 with closed developer tools
2525+ window.console = { log: function () {} };
2026function id(elem) {
2127 return document.getElementById(elem);
2228}
2329function text(el, string) {
2424- if ('textContent' in el)
2525- el.textContent = string;
2626- else
2727- el.innerText = string;
3030+ if ('textContent' in el) el.textContent = string;
3131+ else el.innerText = string;
3232+}
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;
2840}
2929-function decode(der, offset) {
3030- offset = offset || 0;
4141+function show(asn1) {
3142 tree.innerHTML = '';
3243 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;
3352 try {
3434- var asn1 = ASN1.decode(der, offset);
3535- tree.appendChild(asn1.toDOM());
3636- if (wantHex.checked)
3737- dump.appendChild(asn1.toHexDOM());
3838- var b64 = (der.length < maxLength) ? asn1.toB64String() : '';
3939- if (area.value === '')
4040- 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);
4187 try {
4288 window.location.hash = hash = '#' + b64;
4343- } catch (e) { // fails with "Access Denied" on IE with URLs longer than ~2048 chars
8989+ } catch (ignore) {
9090+ // fails with "Access Denied" on IE with URLs longer than ~2048 chars
4491 window.location.hash = hash = '#';
4592 }
4646- var endOffset = asn1.posEnd();
9393+ let endOffset = asn1.posEnd();
4794 if (endOffset < der.length) {
4848- var p = document.createElement('p');
9595+ let p = document.createElement('p');
4996 p.innerText = 'Input contains ' + (der.length - endOffset) + ' more bytes to decode.';
5050- var button = document.createElement('button');
9797+ let button = document.createElement('button');
5198 button.innerText = 'try to decode';
5299 button.onclick = function () {
53100 decode(der, endOffset);
···59106 text(tree, e);
60107 }
61108}
6262-function decodeText(val) {
109109+export function decodeText(val) {
63110 try {
6464- var der = reHex.test(val) ? Hex.decode(val) : Base64.unarmor(val);
111111+ let der = reHex.test(val) ? Hex.decode(val) : Base64.unarmor(val);
65112 decode(der);
66113 } catch (e) {
67114 text(tree, e);
68115 dump.innerHTML = '';
69116 }
70117}
7171-function decodeBinaryString(str) {
7272- var der;
118118+export function decodeBinaryString(str) {
119119+ let der;
73120 try {
7474- if (reHex.test(str))
7575- der = Hex.decode(str);
7676- else if (Base64.re.test(str))
7777- der = Base64.unarmor(str);
7878- else
7979- 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;
80124 decode(der);
8181- } catch (e) {
125125+ } catch (ignore) {
82126 text(tree, 'Cannot decode file.');
83127 dump.innerHTML = '';
84128 }
85129}
86130// set up buttons
8787-id('butDecode').onclick = function () { decodeText(area.value); };
8888-id('butClear').onclick = function () {
8989- area.value = '';
9090- tree.innerHTML = '';
9191- dump.innerHTML = '';
9292- hash = window.location.hash = '';
9393-};
9494-id('butExample').onclick = function () {
9595- var demo = 'MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIAwggHvMIIBWKADAgECAhAvoXazbunwSfREtACZZhlFMA0GCSqGSIb3DQEBBQUAMAwxCjAIBgNVBAMMAWEwHhcNMDgxMDE1MTUwMzQxWhcNMDkxMDE1MTUwMzQxWjAMMQowCAYDVQQDDAFhMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJUwlwhu5hR8X01f-vG0mKPRHsVRjpZNxSEmsmFPdDiD9kylE3ertTDf0gRkpIvWfNJ-eymuxoXF0Qgl5gXAVuSrjupGD6J-VapixJiwLXJHokmDihLs3zfGARz08O3qnO5ofBy0pRxq5isu_bAAcjoByZ1sI_g0iAuotC1UFObwIDAQABo1IwUDAOBgNVHQ8BAf8EBAMCBPAwHQYDVR0OBBYEFEIGXQB4h-04Z3y_n7Nv94-CqPitMB8GA1UdIwQYMBaAFEIGXQB4h-04Z3y_n7Nv94-CqPitMA0GCSqGSIb3DQEBBQUAA4GBAE0G7tAiaacJxvP3fhEj-yP9VDxL0omrRRAEaMXwWaBf_Ggk1T_u-8_CDAdjuGNCiF6ctooKc8u8KpnZJsGqnpGQ4n6L2KjTtRUDh-hija0eJRBFdirPQe2HAebQGFnmOk6Mn7KiQfBIsOzXim_bFqaBSbf06bLTQNwFouSO-jwOAAAxggElMIIBIQIBATAgMAwxCjAIBgNVBAMMAWECEC-hdrNu6fBJ9ES0AJlmGUUwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA4MTAxNTE1MDM0M1owIwYJKoZIhvcNAQkEMRYEFAAAAAAAAAAAAAAAAAAAAAAAAAAAMA0GCSqGSIb3DQEBAQUABIGAdB7ShyMGf5lVdZtvwKlnYLHMUqJWuBnFk7aQwHAmg3JnH6OcgId2F-xfg6twXm8hhUBkhHPlHGoWa5kQtN9n8rz3NorzvcM_1Xv9-0Eal7NYSn2Hb0C0DMj2XNIYH2C6CLIHkmy1egzUvzsomZPTkx5nGDWm-8WHCjWb9A6lyrMAAAAAAAA';
9696- decodeText(demo);
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+ },
97158};
159159+for (const [name, onClick] of Object.entries(butClickHandlers)) {
160160+ let elem = id(name);
161161+ if (elem)
162162+ elem.onclick = onClick;
163163+}
98164// this is only used if window.FileReader
99165function read(f) {
100166 area.value = ''; // clear text area, will get b64 content
101101- var r = new FileReader();
167167+ let r = new FileReader();
102168 r.onloadend = function () {
103103- if (r.error)
104104- alert("Your browser couldn't read the specified file (error code " + r.error.code + ").");
105105- else
106106- 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);
107171 };
108172 r.readAsBinaryString(f);
109173}
110174function load() {
111111- if (file.files.length === 0)
112112- alert("Select a file to load first.");
113113- else
114114- read(file.files[0]);
175175+ if (file.files.length === 0) alert('Select a file to load first.');
176176+ else read(file.files[0]);
115177}
116178function loadFromHash() {
117179 if (window.location.hash && window.location.hash != hash) {
···119181 // Firefox is not consistent with other browsers and returns an
120182 // already-decoded hash string so we risk double-decoding here,
121183 // but since % is not allowed in base64 nor hexadecimal, it's ok
122122- var val = decodeURIComponent(hash.substr(1));
123123- if (val.length)
124124- decodeText(val);
184184+ let val = decodeURIComponent(hash.substr(1));
185185+ if (val.length) decodeText(val);
125186 }
126187}
127188function stop(e) {
···130191}
131192function dragAccept(e) {
132193 stop(e);
133133- if (e.dataTransfer.files.length > 0)
134134- read(e.dataTransfer.files[0]);
194194+ if (e.dataTransfer.files.length > 0) read(e.dataTransfer.files[0]);
135195}
136196// main
137137-if ('onhashchange' in window)
138138- window.onhashchange = loadFromHash;
197197+if ('onhashchange' in window) window.onhashchange = loadFromHash;
139198loadFromHash();
140199document.ondragover = stop;
141200document.ondragleave = stop;
142142-if ('FileReader' in window && 'readAsBinaryString' in (new FileReader())) {
201201+if ('FileReader' in window && 'readAsBinaryString' in new FileReader()) {
143202 file.style.display = 'block';
144203 file.onchange = load;
145204 document.ondrop = dragAccept;
146205}
147147-148148-});
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+};
-90
int10.js
···11-// Big integer base-10 printing library
22-// Copyright (c) 2008-2020 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-(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";
2121-2222-var max = 10000000000000; // biggest 10^n integer that can still fit 2^53 when multiplied by 256
2323-2424-function Int10(value) {
2525- this.buf = [+value || 0];
2626-}
2727-2828-Int10.prototype.mulAdd = function (m, c) {
2929- // assert(m <= 256)
3030- var b = this.buf,
3131- l = b.length,
3232- i, t;
3333- for (i = 0; i < l; ++i) {
3434- t = b[i] * m + c;
3535- if (t < max)
3636- c = 0;
3737- else {
3838- c = 0|(t / max);
3939- t -= c * max;
4040- }
4141- b[i] = t;
4242- }
4343- if (c > 0)
4444- b[i] = c;
4545-};
4646-4747-Int10.prototype.sub = function (c) {
4848- // assert(m <= 256)
4949- var b = this.buf,
5050- l = b.length,
5151- i, t;
5252- for (i = 0; i < l; ++i) {
5353- t = b[i] - c;
5454- if (t < 0) {
5555- t += max;
5656- c = 1;
5757- } else
5858- c = 0;
5959- b[i] = t;
6060- }
6161- while (b[b.length - 1] === 0)
6262- b.pop();
6363-};
6464-6565-Int10.prototype.toString = function (base) {
6666- if ((base || 10) != 10)
6767- throw 'only base 10 is supported';
6868- var b = this.buf,
6969- s = b[b.length - 1].toString();
7070- for (var i = b.length - 2; i >= 0; --i)
7171- s += (max + b[i]).toString().substring(1);
7272- return s;
7373-};
7474-7575-Int10.prototype.valueOf = function () {
7676- var b = this.buf,
7777- v = 0;
7878- for (var i = b.length - 1; i >= 0; --i)
7979- v = v * max + b[i];
8080- return v;
8181-};
8282-8383-Int10.prototype.simplify = function () {
8484- var b = this.buf;
8585- return (b.length == 1) ? b[0] : this;
8686-};
8787-8888-return Int10;
8989-9090-});
···11-#/bin/sh
22-URL='https://www.cs.auckland.ac.nz/~pgut001/dumpasn1.cfg'
33-if [ -x /usr/bin/fetch ]; then
44- /usr/bin/fetch -m --no-verify-peer $URL
55-elif [ -x /usr/bin/wget ]; then
66- /usr/bin/wget -N --no-check-certificate $URL
77-elif [ ! -r dumpasn1.cfg ]; then
88- echo Please download $URL in this directory.
99- exit 1
1010-fi
1111-cat dumpasn1.cfg | \
1212-tr -d '\r' | \
1313-awk -v url="$URL" '
1414- function clean() {
1515- oid = "";
1616- comment = "";
1717- description = "";
1818- warning = "";
1919- }
2020- BEGIN {
2121- FS = "= *";
2222- apos = sprintf("%c", 39);
2323- clean();
2424- print "// Converted from: " url;
2525- print "// which is made by Peter Gutmann and whose license states:";
2626- print "// You can use this code in whatever way you want,";
2727- print "// as long as you don" apos "t try to claim you wrote it.";
2828- print "(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 {";
3434- }
3535- /^OID/ { oid = $2; }
3636- /^Comment/ { comment = $2; }
3737- /^Description/ { description = $2; }
3838- /^Warning/ { warning = ", \"w\": true"; }
3939- /^$/ {
4040- if (length(oid) > 0) {
4141- gsub(" ", ".", oid);
4242- gsub("\"", "\\\"", description);
4343- gsub("\"", "\\\"", comment);
4444- if (++seen[oid] > 1)
4545- print "Duplicate OID in line " NR ": " oid > "/dev/stderr";
4646- else
4747- printf "\"%s\": { \"d\": \"%s\", \"c\": \"%s\"%s },\n", oid, description, comment, warning;
4848- clean();
4949- }
5050- }
5151- END {
5252- print "\"END\": \"\""
5353- print "}});"
5454- }
5555-' >oids.js
5656-echo Conversion completed.
+49
updateOID.sh
···11+#/bin/sh
22+URL='https://www.cs.auckland.ac.nz/~pgut001/dumpasn1.cfg'
33+if [ -x /usr/bin/fetch ]; then
44+ /usr/bin/fetch -m --no-verify-peer $URL
55+elif [ -x /usr/bin/wget ]; then
66+ /usr/bin/wget -N --no-check-certificate $URL
77+elif [ ! -r dumpasn1.cfg ]; then
88+ echo Please download $URL in this directory.
99+ exit 1
1010+fi
1111+cat dumpasn1.cfg | \
1212+tr -d '\r' | \
1313+awk -v apos="'" -v q='"' -v url="$URL" '
1414+ function clean() {
1515+ oid = "";
1616+ comment = "";
1717+ description = "";
1818+ warning = "";
1919+ }
2020+ BEGIN {
2121+ FS = "= *";
2222+ clean();
2323+ print "// Converted from: " url;
2424+ print "// which is made by Peter Gutmann and whose license states:";
2525+ print "// You can use this code in whatever way you want,";
2626+ print "// as long as you don" apos "t try to claim you wrote it.";
2727+ print "export const oids = {";
2828+ }
2929+ /^OID/ { oid = $2; }
3030+ /^Comment/ { comment = $2; }
3131+ /^Description/ { description = $2; }
3232+ /^Warning/ { warning = ", \"w\": true"; }
3333+ /^$/ {
3434+ if (length(oid) > 0) {
3535+ gsub(" ", ".", oid);
3636+ gsub("\"", "\\\"", description);
3737+ gsub("\"", "\\\"", comment);
3838+ if (++seen[oid] > 1)
3939+ print "Duplicate OID in line " NR ": " oid > "/dev/stderr";
4040+ else
4141+ printf "\"%s\": { \"d\": \"%s\", \"c\": \"%s\"%s },\n", oid, description, comment, warning;
4242+ clean();
4343+ }
4444+ }
4545+ END {
4646+ print "};"
4747+ }
4848+' >oids.js
4949+echo Conversion completed.
+32
updateRFC.sh
···11+#/bin/sh
22+RFCs="5280 5208 3369 3161 2986 4211 4210 8017 4511"
33+downloadRFC() {
44+ URL="https://www.ietf.org/rfc/rfc$1.txt"
55+ if [ -x /usr/bin/fetch ]; then
66+ /usr/bin/fetch -m --no-verify-peer $URL
77+ elif [ -x /usr/bin/wget ]; then
88+ /usr/bin/wget -N --no-check-certificate $URL
99+ elif [ ! -r dumpasn1.cfg ]; then
1010+ echo Please download $URL in this directory.
1111+ exit 1
1212+ fi
1313+}
1414+echo '{}' > rfcdef.json # start from scratch
1515+mkdir -p rfc
1616+cd rfc
1717+for n in $RFCs; do
1818+ downloadRFC $n
1919+ ../parseRFC.js rfc$n.txt ../rfcdef.json
2020+done
2121+cd ..
2222+{
2323+ echo "// content parsed from ASN.1 definitions as found in the following RFCs: $RFCs"
2424+ echo "// Copyright (C) The IETF Trust (2008)"
2525+ echo "// as far as I can tell this file is allowed under the following clause:"
2626+ echo "// It is acceptable under the current IETF rules (RFC 5378) to modify extracted code if necessary."
2727+ echo "// https://trustee.ietf.org/about/faq/#reproducing-rfcs"
2828+ echo -n "export const rfcdef = "
2929+ cat rfcdef.json
3030+ echo ";"
3131+} > rfcdef.js
3232+echo Conversion completed.