···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
+1-1
LICENSE
···11ISC License
2233-Copyright (c) 2008-2022 Lapo Luchini <lapo@lapo.it>
33+Copyright (c) 2008-2025 Lapo Luchini <lapo@lapo.it>
4455Permission to use, copy, modify, and/or distribute this software for any
66purpose with or without fee is hereby granted, provided that the above
+57-23
README.md
···3344asn1js is a JavaScript generic ASN.1 parser/decoder that can decode any valid ASN.1 DER or BER structures.
5566-An example page that can decode Base64-encoded (raw base64, PEM armoring and `begin-base64` are recognized) or Hex-encoded (or local files with some browsers) is included and can be used both [online on the official website](https://lapo.it/asn1js/) or [offline (ZIP file)](https://lapo.it/asn1js/asn1js.zip).
66+An example page that can decode Base64-encoded (raw base64, PEM armoring and `begin-base64` are recognized) or Hex-encoded (or local files with some browsers) is included and can be used both [online on the official website](https://asn1js.eu/) or [offline (ZIP file)](https://lapo.it/asn1js/asn1js.zip) by opening `index-local.html`.
7788-Usage with `npm` / `yarn`
99--------------------------
88+Usage with `nodejs`
99+-------------------
10101111This package can be installed with either npm or yarn via the following commands:
12121313```sh
1414npm install @lapo/asn1js
1515-# or with yarn
1515+# or other tools
1616+pnpm install @lapo/asn1js
1617yarn add @lapo/asn1js
1718```
18191919-Assuming a standard javascript bundler is setup you can import it like so:
2020+You can import the classes like this:
20212122```js
2222-const ASN1 = require('@lapo/asn1js');
2323-// or with ES modules
2424-import ASN1 from '@lapo/asn1js';
2323+import { ASN1 } from '@lapo/asn1js';
2524```
26252726A submodule of this package can also be imported:
28272928```js
3030-const Hex = require('@lapo/asn1js/hex');
3131-// or with ES modules
3232-import Hex from '@lapo/asn1js/hex';
2929+import { Hex } from '@lapo/asn1js/hex.js';
3330```
34313535-Usage with RequireJS
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
3654--------------------
37553838-Can be [tested on JSFiddle](https://jsfiddle.net/lapo/tmdq35ug/).
5656+Can be [tested on JSFiddle](https://jsfiddle.net/lapo/y6t2wo7q/).
39574058```html
4141-<script type="text/javascript" src="https://unpkg.com/requirejs/require.js"></script>
4259<script>
4343-require([
4444- 'https://unpkg.com/@lapo/asn1js/asn1.js',
4545- 'https://unpkg.com/@lapo/asn1js/hex.js'
4646-], function(ASN1, Hex) {
4747- document.body.innerText = ASN1.decode(Hex.decode('06032B6570')).content();
4848-});
6060+import { ASN1 } from 'https://unpkg.com/@lapo/asn1js@2.0.0/asn1.js';
6161+import { Hex } from 'https://unpkg.com/@lapo/asn1js@2.0.0/hex.js';
6262+6363+document.body.innerText = ASN1.decode(Hex.decode('06032B6570')).content();
4964</script>
5065```
51666767+Local usage
6868+--------------------
6969+7070+Since unfortunately ESM modules are not working on `file:` protocol due to [CORS issues](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#other_differences_between_modules_and_standard_scripts), there is a bundled [single-file version working locally](https://asn1js.eu/index-local.html). It doesn't work online (due to [CSP](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) restrictions about inline content) but can be saved locally and opened in a browser.
7171+7272+Usage from CLI
7373+--------------------
7474+7575+You can dump an ASN.1 structure from the command line using the following command (no need to even install it):
7676+7777+```sh
7878+npx @lapo/asn1js ed25519.cer
7979+```
8080+5281ISC license
5382-----------
54835555-ASN.1 JavaScript decoder Copyright (c) 2008-2022 Lapo Luchini <lapo@lapo.it>
8484+ASN.1 JavaScript decoder Copyright (c) 2008-2025 Lapo Luchini <lapo@lapo.it>
56855786Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
5887···6695- extended tag support added by [Pรฉter Budai](https://www.peterbudai.eu/)
6796- patches by [Gergely Nagy](https://github.com/ngg)
6897- Relative OID support added by [Mistial Developer](https://github.com/mistial-dev)
9898+- dark mode and other UI improvements by [Oliver Burgmaier](https://github.com/olibu/)
9999+- patches by [Nicolai Sรธborg](https://github.com/NicolaiSoeborg)
6910070101links
71102-----
721037373-- [official website](https://lapo.it/asn1js/)
7474-- [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)
75108- [GitHub mirror](https://github.com/lapo-luchini/asn1js)
109109+- [ChangeLog on GitHub](https://github.com/lapo-luchini/asn1js/blob/trunk/CHANGELOG.md)
76110- [Ohloh code stats](https://www.openhub.net/p/asn1js)
+786-525
asn1.js
···11// ASN.1 JavaScript decoder
22-// Copyright (c) 2008-2022 Lapo Luchini <lapo@lapo.it>
22+// Copyright (c) 2008 Lapo Luchini <lapo@lapo.it>
3344// Permission to use, copy, modify, and/or distribute this software for any
55// purpose with or without fee is hereby granted, provided that the above
66// copyright notice and this permission notice appear in all copies.
77-//
77+//
88// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
99// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1010// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
···1313// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1414// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15151616-(typeof define != 'undefined' ? define : function (factory) { 'use strict';
1717- if (typeof module == 'object') module.exports = factory(function (name) { return require(name); });
1818- else window.asn1 = factory(function (name) { return window[name.substring(2)]; });
1919-})(function (require) {
2020-"use strict";
1616+import { oids } from './oids.js';
21172222-var Int10 = require('./int10'),
2323- oids = require('./oids'),
2424- ellipsis = "\u2026",
1818+const
1919+ ellipsis = '\u2026',
2520 reTimeS = /^(\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|(-(?:0\d|1[0-2])|[+](?:0\d|1[0-4]))([0-5]\d)?)?$/,
2626- reTimeL = /^(\d\d\d\d)(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])([01]\d|2[0-3])(?:([0-5]\d)(?:([0-5]\d)(?:[.,](\d{1,3}))?)?)?(Z|(-(?:0\d|1[0-2])|[+](?:0\d|1[0-4]))([0-5]\d)?)?$/;
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- }
6565- }
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);
7979- }
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);
8686- }
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;
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');
9498 }
9595- return true;
9696-};
9797-Stream.prototype.parseStringISO = function (start, end, maxLength) {
9898- var s = "";
9999- for (var i = start; i < end; ++i)
100100- s += String.fromCharCode(this.get(i));
101101- return { size: s.length, str: stringCut(s, maxLength) };
102102-};
103103-var tableT61 = [
104104- ['', ''],
105105- ['AEIOUaeiou', 'รรรรรร รจรฌรฒรน'], // Grave
106106- ['ACEILNORSUYZacegilnorsuyz', 'รฤรรฤนลรลลรรลนรกฤรฉฤฃรญฤบลรณลลรบรฝลบ'], // Acute
107107- ['ACEGHIJOSUWYaceghijosuwy', 'รฤรฤฤครฤดรลรลดลถรขฤรชฤฤฅรฎฤตรดลรปลตลท'], // Circumflex
108108- ['AINOUainou', 'รฤจรรลจรฃฤฉรฑรตลฉ'], // Tilde
109109- ['AEIOUaeiou', 'ฤฤฤชลลชฤฤฤซลลซ'], // Macron
110110- ['AGUagu', 'ฤฤลฌฤฤลญ'], // Breve
111111- ['CEGIZcegz', 'ฤฤฤ ฤฐลปฤฤฤกลผ'], // Dot
112112- ['AEIOUYaeiouy', 'รรรรรลธรครซรฏรถรผรฟ'], // Umlaut or diรฆresis
113113- ['', ''],
114114- ['AUau', 'ร ลฎรฅลฏ'], // Ring
115115- ['CGKLNRSTcklnrst', 'รฤขฤถฤปล ลลลขรงฤทฤผลลลลฃ'], // Cedilla
116116- ['', ''],
117117- ['OUou', 'ลลฐลลฑ'], // Double Acute
118118- ['AEIUaeiu', 'ฤฤฤฎลฒฤ ฤฤฏลณ'], // Ogonek
119119- ['CDELNRSTZcdelnrstz', 'ฤฤฤฤฝลลล ลคลฝฤฤฤฤพลลลกลฅลพ'] // Caron
120120-];
121121-Stream.prototype.parseStringT61 = function (start, end, maxLength) {
122122- // warning: this code is not very well tested so far
123123- function merge(c, d) {
124124- var t = tableT61[c - 0xC0];
125125- var i = t[0].indexOf(String.fromCharCode(d));
126126- return (i < 0) ? '\0' : t[1].charAt(i);
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);
127111 }
128128- var s = "", c;
129129- for (var i = start; i < end; ++i) {
130130- c = this.get(i);
131131- if (c >= 0xA4 && c <= 0xBF)
132132- s += '$ยฅ#ยงยค\0\0ยซ\0\0\0\0ยฐยฑยฒยณรยตยถยทรท\0\0ยปยผยฝยพยฟ'.charAt(c - 0xA4);
133133- else if (c >= 0xE0 && c <= 0xFF)
134134- s += 'โฆรรยชฤฆ\0ฤฒฤฟลรลยบรลฆลลฤธรฆฤรฐฤงฤฑฤณลลรธลรรพลงล\0'.charAt(c - 0xE0);
135135- else if (c >= 0xC0 && c <= 0xCF)
136136- s += merge(c, this.get(++i));
137137- else // using ISO 8859-1 for characters undefined (or equal) in T.61
138138- s += String.fromCharCode(c);
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);
139120 }
140140- return { size: s.length, str: stringCut(s, maxLength) };
141141-};
142142-Stream.prototype.parseStringUTF = function (start, end, maxLength) {
143143- function ex(c) { // must be 10xxxxxx
144144- if ((c < 0x80) || (c >= 0xC0))
145145- throw new Error('Invalid UTF-8 continuation byte: ' + c);
146146- return (c & 0x3F);
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;
147143 }
148148- function surrogate(cp) {
149149- if (cp < 0x10000)
150150- throw new Error('UTF-8 overlong encoding, codepoint encoded in 4 bytes: ' + cp);
151151- // we could use String.fromCodePoint(cp) but let's be nice to older browsers and use surrogate pairs
152152- cp -= 0x10000;
153153- return String.fromCharCode((cp >> 10) + 0xD800, (cp & 0x3FF) + 0xDC00);
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;
154173 }
155155- var s = "";
156156- for (var i = start; i < end; ) {
157157- var c = this.get(i++);
158158- if (c < 0x80) // 0xxxxxxx (7 bit)
159159- s += String.fromCharCode(c);
160160- else if (c < 0xC0)
161161- throw new Error('Invalid UTF-8 starting byte: ' + c);
162162- else if (c < 0xE0) // 110xxxxx 10xxxxxx (11 bit)
163163- s += String.fromCharCode(((c & 0x1F) << 6) | ex(this.get(i++)));
164164- else if (c < 0xF0) // 1110xxxx 10xxxxxx 10xxxxxx (16 bit)
165165- s += String.fromCharCode(((c & 0x0F) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++)));
166166- else if (c < 0xF8) // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (21 bit)
167167- s += surrogate(((c & 0x07) << 18) | (ex(this.get(i++)) << 12) | (ex(this.get(i++)) << 6) | ex(this.get(i++)));
168168- else
169169- throw new Error('Invalid UTF-8 starting byte (since 2003 it is restricted to 4 bytes): ' + c);
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;
170188 }
171171- return { size: s.length, str: stringCut(s, maxLength) };
172172-};
173173-Stream.prototype.parseStringBMP = function (start, end, maxLength) {
174174- var s = "", hi, lo;
175175- for (var i = start; i < end; ) {
176176- hi = this.get(i++);
177177- lo = this.get(i++);
178178- s += String.fromCharCode((hi << 8) | lo);
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) };
179202 }
180180- return { size: s.length, str: stringCut(s, maxLength) };
181181-};
182182-Stream.prototype.parseTime = function (start, end, shortYear) {
183183- var s = this.parseStringISO(start, end).str,
184184- m = (shortYear ? reTimeS : reTimeL).exec(s);
185185- if (!m)
186186- return "Unrecognized time: " + s;
187187- if (shortYear) {
188188- // to avoid querying the timer, use the fixed range [1970, 2069]
189189- // it will conform with ITU X.400 [-10, +40] sliding window until 2030
190190- m[1] = +m[1];
191191- m[1] += (m[1] < 70) ? 2000 : 1900;
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) };
192231 }
193193- s = m[1] + "-" + m[2] + "-" + m[3] + " " + m[4];
194194- if (m[5]) {
195195- s += ":" + m[5];
196196- if (m[6]) {
197197- s += ":" + m[6];
198198- if (m[7])
199199- s += "." + m[7];
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);
200250 }
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) };
201280 }
202202- if (m[8]) {
203203- s += " UTC";
204204- if (m[9])
205205- s += m[9] + ":" + (m[10] || "00");
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);
295295+ }
296296+ return { size: s.length, str: stringCut(s, maxLength) };
206297 }
207207- return s;
208208-};
209209-Stream.prototype.parseInteger = function (start, end) {
210210- var v = this.get(start),
211211- neg = (v > 127),
212212- pad = neg ? 255 : 0,
213213- len,
214214- s = '';
215215- // skip unuseful bits (not allowed in DER)
216216- while (v == pad && ++start < end)
217217- v = this.get(start);
218218- len = end - start;
219219- if (len === 0)
220220- return neg ? '-1' : '0';
221221- // show bit length of huge integers
222222- if (len > 4) {
223223- s = v;
224224- len <<= 3;
225225- while (((s ^ pad) & 0x80) == 0) {
226226- s <<= 1;
227227- --len;
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+ }
228325 }
229229- s = "(" + len + " bit)\n";
326326+ if (m[8]) {
327327+ s += ' UTC';
328328+ if (m[9])
329329+ s += m[9] + ':' + (m[10] || '00');
330330+ }
331331+ return s;
230332 }
231231- // decode the integer
232232- if (neg) v = v - 256;
233233- var n = new Int10(v);
234234- for (var i = start + 1; i < end; ++i)
235235- n.mulAdd(256, this.get(i));
236236- return s + n.toString();
237237-};
238238-Stream.prototype.parseBitString = function (start, end, maxLength) {
239239- var unusedBits = this.get(start);
240240- if (unusedBits > 7)
241241- throw 'Invalid BitString with unusedBits=' + unusedBits;
242242- var lenBit = ((end - start - 1) << 3) - unusedBits,
243243- s = "";
244244- for (var i = start + 1; i < end; ++i) {
245245- var b = this.get(i),
246246- skip = (i == end - 1) ? unusedBits : 0;
247247- for (var j = 7; j >= skip; --j)
248248- s += (b >> j) & 1 ? "1" : "0";
249249- if (s.length > maxLength)
250250- s = stringCut(s, maxLength);
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';
360360+ }
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;
251367 }
252252- return { size: lenBit, str: s };
253253-};
254254-function checkPrintable(s) {
255255- var i, v;
256256- for (i = 0; i < s.length; ++i) {
257257- v = s.charCodeAt(i);
258258- if (v < 32 && v != 9 && v != 10 && v != 13) // [\t\r\n] are (kinda) printable
259259- throw new Error('Unprintable character at index ' + i + ' (code ' + s.str.charCodeAt(i) + ")");
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';
387387+ if (s.length > maxLength)
388388+ s = stringCut(s, maxLength);
389389+ }
390390+ return { size: lenBit, str: s };
260391 }
261261-}
262262-Stream.prototype.parseOctetString = function (start, end, maxLength) {
263263- var len = end - start,
264264- s;
265265- try {
266266- s = this.parseStringUTF(start, end, maxLength);
267267- checkPrintable(s.str);
268268- return { size: end - start, str: s.str };
269269- } catch (e) {
270270- // ignore
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
407407+ }
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 };
271418 }
272272- maxLength /= 2; // we work in bytes
273273- if (len > maxLength)
274274- end = start + maxLength;
275275- s = '';
276276- for (var i = start; i < end; ++i)
277277- s += this.hexByte(this.get(i));
278278- if (len > maxLength)
279279- s += ellipsis;
280280- return { size: len, str: s };
281281-};
282282-Stream.prototype.parseOID = function (start, end, maxLength, isRelative) {
283283- var s = '',
284284- n = new Int10(),
285285- bits = 0;
286286- for (var i = start; i < end; ++i) {
287287- var v = this.get(i);
288288- n.mulAdd(128, v & 0x7F);
289289- bits += 7;
290290- if (!(v & 0x80)) { // finished
291291- if (s === '') {
292292- n = n.simplify();
293293- if (isRelative) {
294294- s = (n instanceof Int10) ? n.toString() : "" + n;
295295- } else if (n instanceof Int10) {
296296- n.sub(80);
297297- s = "2." + n.toString();
298298- } else {
299299- var m = n < 80 ? n < 40 ? 0 : 1 : 2;
300300- s = m + "." + (n - m * 40);
301301- }
302302- } else
303303- s += "." + n.toString();
304304- if (s.length > maxLength)
305305- return stringCut(s, maxLength);
306306- n = new Int10();
419419+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,
307431 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+ }
308454 }
309309- }
310310- if (bits > 0)
311311- s += ".incomplete";
312312- if (typeof oids === 'object' && !isRelative) {
313313- var oid = oids[s];
314314- if (oid) {
315315- if (oid.d) s += "\n" + oid.d;
316316- if (oid.c) s += "\n" + oid.c;
317317- if (oid.w) s += "\n(warning!)";
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+ }
318465 }
466466+ return s;
319467 }
320320- return s;
321321-};
322322-Stream.prototype.parseRelativeOID = function (start, end, maxLength) {
323323- return this.parseOID(start, end, maxLength, true);
324324-};
325468326326-function ASN1(stream, header, length, tag, tagLen, sub) {
327327- if (!(tag instanceof ASN1Tag)) throw 'Invalid tag value.';
328328- this.stream = stream;
329329- this.header = header;
330330- this.length = length;
331331- this.tag = tag;
332332- this.tagLen = tagLen;
333333- this.sub = sub;
334334-}
335335-ASN1.prototype.typeName = function () {
336336- switch (this.tag.tagClass) {
337337- case 0: // universal
338338- switch (this.tag.tagNumber) {
339339- case 0x00: return "EOC";
340340- case 0x01: return "BOOLEAN";
341341- case 0x02: return "INTEGER";
342342- case 0x03: return "BIT_STRING";
343343- case 0x04: return "OCTET_STRING";
344344- case 0x05: return "NULL";
345345- case 0x06: return "OBJECT_IDENTIFIER";
346346- case 0x07: return "ObjectDescriptor";
347347- case 0x08: return "EXTERNAL";
348348- case 0x09: return "REAL";
349349- case 0x0A: return "ENUMERATED";
350350- case 0x0B: return "EMBEDDED_PDV";
351351- case 0x0C: return "UTF8String";
352352- case 0x0D: return "RELATIVE_OID";
353353- case 0x10: return "SEQUENCE";
354354- case 0x11: return "SET";
355355- case 0x12: return "NumericString";
356356- case 0x13: return "PrintableString"; // ASCII subset
357357- case 0x14: return "TeletexString"; // aka T61String
358358- case 0x15: return "VideotexString";
359359- case 0x16: return "IA5String"; // ASCII
360360- case 0x17: return "UTCTime";
361361- case 0x18: return "GeneralizedTime";
362362- case 0x19: return "GraphicString";
363363- case 0x1A: return "VisibleString"; // ASCII subset
364364- case 0x1B: return "GeneralString";
365365- case 0x1C: return "UniversalString";
366366- case 0x1E: return "BMPString";
367367- }
368368- return "Universal_" + this.tag.tagNumber.toString();
369369- case 1: return "Application_" + this.tag.tagNumber.toString();
370370- case 2: return "[" + this.tag.tagNumber.toString() + "]"; // Context
371371- case 3: return "Private_" + this.tag.tagNumber.toString();
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);
372478 }
373373-};
479479+}
480480+374481function recurse(el, parser, maxLength) {
375375- var avoidRecurse = true;
482482+ let avoidRecurse = true;
376483 if (el.tag.tagConstructed && el.sub) {
377484 avoidRecurse = false;
378485 el.sub.forEach(function (e1) {
···382489 }
383490 if (avoidRecurse)
384491 return el.stream[parser](el.posContent(), el.posContent() + Math.abs(el.length), maxLength);
385385- var d = { size: 0, str: '' };
492492+ let d = { size: 0, str: '' };
386493 el.sub.forEach(function (el) {
387387- var d1 = recurse(el, parser, maxLength - d.str.length);
494494+ let d1 = recurse(el, parser, maxLength - d.str.length);
388495 d.size += d1.size;
389496 d.str += d1.str;
390497 });
391498 return d;
392499}
393393-/** A string preview of the content (intended for humans). */
394394-ASN1.prototype.content = function (maxLength) {
395395- if (this.tag === undefined)
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+ }
610610+ switch (this.tag.tagNumber) {
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));
661661+ }
396662 return null;
397397- if (maxLength === undefined)
398398- maxLength = Infinity;
399399- var content = this.posContent(),
400400- len = Math.abs(this.length);
401401- if (!this.tag.isUniversal()) {
402402- if (this.sub !== null)
403403- return "(" + this.sub.length + " elem)";
404404- var d1 = this.stream.parseOctetString(content, content + len, maxLength);
405405- return "(" + d1.size + " byte)\n" + d1.str;
663663+ }
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) + ']';
671671+ }
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;
707707+ }
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;
406715 }
407407- switch (this.tag.tagNumber) {
408408- case 0x01: // BOOLEAN
409409- return (this.stream.get(content) === 0) ? "false" : "true";
410410- case 0x02: // INTEGER
411411- return this.stream.parseInteger(content, content + len);
412412- case 0x03: // BIT_STRING
413413- var d = recurse(this, 'parseBitString', maxLength);
414414- return "(" + d.size + " bit)\n" + d.str;
415415- case 0x04: // OCTET_STRING
416416- d = recurse(this, 'parseOctetString', maxLength);
417417- return "(" + d.size + " byte)\n" + d.str;
418418- //case 0x05: // NULL
419419- case 0x06: // OBJECT_IDENTIFIER
420420- return this.stream.parseOID(content, content + len, maxLength);
421421- //case 0x07: // ObjectDescriptor
422422- //case 0x08: // EXTERNAL
423423- //case 0x09: // REAL
424424- case 0x0A: // ENUMERATED
425425- return this.stream.parseInteger(content, content + len);
426426- //case 0x0B: // EMBEDDED_PDV
427427- case 0x0D: // RELATIVE-OID
428428- return this.stream.parseRelativeOID(content, content + len, maxLength);
429429- case 0x10: // SEQUENCE
430430- case 0x11: // SET
431431- if (this.sub !== null)
432432- return "(" + this.sub.length + " elem)";
433433- else
434434- return "(no elem)";
435435- case 0x0C: // UTF8String
436436- return recurse(this, 'parseStringUTF', maxLength).str;
437437- case 0x14: // TeletexString
438438- return recurse(this, 'parseStringT61', maxLength).str;
439439- case 0x12: // NumericString
440440- case 0x13: // PrintableString
441441- case 0x15: // VideotexString
442442- case 0x16: // IA5String
443443- case 0x1A: // VisibleString
444444- case 0x1B: // GeneralString
445445- //case 0x19: // GraphicString
446446- //case 0x1C: // UniversalString
447447- return recurse(this, 'parseStringISO', maxLength).str;
448448- case 0x1E: // BMPString
449449- return recurse(this, 'parseStringBMP', maxLength).str;
450450- case 0x17: // UTCTime
451451- case 0x18: // GeneralizedTime
452452- return this.stream.parseTime(content, content + len, (this.tag.tagNumber == 0x17));
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;
453723 }
454454- return null;
455455-};
456456-ASN1.prototype.toString = function () {
457457- return this.typeName() + "@" + this.stream.pos + "[header:" + this.header + ",length:" + this.length + ",sub:" + ((this.sub === null) ? 'null' : this.sub.length) + "]";
458458-};
459459-ASN1.prototype.toPrettyString = function (indent) {
460460- if (indent === undefined) indent = '';
461461- var s = indent + this.typeName() + " @" + this.stream.pos;
462462- if (this.length >= 0)
463463- s += "+";
464464- s += this.length;
465465- if (this.tag.tagConstructed)
466466- s += " (constructed)";
467467- else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null))
468468- s += " (encapsulates)";
469469- var content = this.content();
470470- if (content)
471471- s += ": " + content.replace(/\n/g, '|');
472472- s += "\n";
473473- if (this.sub !== null) {
474474- indent += ' ';
475475- for (var i = 0, max = this.sub.length; i < max; ++i)
476476- s += this.sub[i].toPrettyString(indent);
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);
477731 }
478478- return s;
479479-};
480480-ASN1.prototype.posStart = function () {
481481- return this.stream.pos;
482482-};
483483-ASN1.prototype.posContent = function () {
484484- return this.stream.pos + this.header;
485485-};
486486-ASN1.prototype.posEnd = function () {
487487- return this.stream.pos + this.header + Math.abs(this.length);
488488-};
489489-/** Position of the length. */
490490-ASN1.prototype.posLen = function() {
491491- return this.stream.pos + this.tagLen;
492492-};
493493-ASN1.prototype.toHexString = function () {
494494- return this.stream.hexDump(this.posStart(), this.posEnd(), true);
495495-};
496496-ASN1.prototype.toB64String = function () {
497497- return this.stream.b64Dump(this.posStart(), this.posEnd());
498498-};
499499-ASN1.decodeLength = function (stream) {
500500- var buf = stream.get(),
501501- len = buf & 0x7F;
502502- if (len == buf) // first bit was 0, short form
503503- return len;
504504- if (len === 0) // long form with length 0 is a special case
505505- return null; // undefined length
506506- if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways
507507- throw "Length over 48 bits not supported at position " + (stream.pos - 1);
508508- buf = 0;
509509- for (var i = 0; i < len; ++i)
510510- buf = (buf * 256) + stream.get();
511511- return buf;
512512-};
513513-function ASN1Tag(stream) {
514514- var buf = stream.get();
515515- this.tagClass = buf >> 6;
516516- this.tagConstructed = ((buf & 0x20) !== 0);
517517- this.tagNumber = buf & 0x1F;
518518- if (this.tagNumber == 0x1F) { // long tag
519519- var n = new Int10();
520520- do {
521521- buf = stream.get();
522522- n.mulAdd(128, buf & 0x7F);
523523- } while (buf & 0x80);
524524- this.tagNumber = n.simplify();
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;
525778 }
526526-}
527527-ASN1Tag.prototype.isUniversal = function () {
528528- return this.tagClass === 0x00;
529529-};
530530-ASN1Tag.prototype.isEOC = function () {
531531- return this.tagClass === 0x00 && this.tagNumber === 0x00;
532532-};
533533-ASN1.decode = function (stream, offset) {
534534- if (!(stream instanceof Stream))
535535- stream = new Stream(stream, offset || 0);
536536- var streamStart = new Stream(stream),
537537- tag = new ASN1Tag(stream),
538538- tagLen = stream.pos - streamStart.pos,
539539- len = ASN1.decodeLength(stream),
540540- start = stream.pos,
541541- header = start - streamStart.pos,
542542- sub = null,
543543- getSub = function () {
544544- sub = [];
545545- if (len !== null) {
546546- // definite length
547547- var end = start + len;
548548- if (end > stream.enc.length)
549549- throw 'Container at offset ' + start + ' has a length of ' + len + ', which is past the end of the stream';
550550- while (stream.pos < end)
551551- sub[sub.length] = ASN1.decode(stream);
552552- if (stream.pos != end)
553553- throw 'Content size is not correct for container at offset ' + start;
554554- } else {
555555- // undefined length
556556- try {
557557- for (;;) {
558558- var s = ASN1.decode(stream);
559559- if (s.tag.isEOC())
560560- break;
561561- sub[sub.length] = s;
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);
562843 }
563563- len = start - stream.pos; // undefined lengths are represented as negative values
564564- } catch (e) {
565565- throw 'Exception while decoding undefined length content at offset ' + start + ': ' + e;
566844 }
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);
567849 }
568568- };
569569- if (tag.tagConstructed) {
570570- // must have valid content
571571- getSub();
572572- } else if (tag.isUniversal() && ((tag.tagNumber == 0x03) || (tag.tagNumber == 0x04))) {
573573- // sometimes BitString and OctetString are used to encapsulate ASN.1
574574- try {
575575- if (tag.tagNumber == 0x03)
576576- if (stream.get() != 0)
577577- throw "BIT STRINGs with unused bits cannot encapsulate.";
578578- getSub();
579579- for (var i = 0; i < sub.length; ++i)
580580- if (sub[i].tag.isEOC())
581581- throw 'EOC is not supposed to be actual content.';
582582- } catch (e) {
583583- // but silently ignore when they don't
584584- sub = null;
585585- //DEBUG console.log('Could not decode structure at ' + start + ':', e);
586850 }
587587- }
588588- if (sub === null) {
589589- if (len === null)
590590- throw "We can't skip over an invalid tag with undefined length at offset " + start;
591591- stream.pos = start + Math.abs(len);
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);
855855+ }
856856+ return new type(streamStart, header, len, tag, tagLen, sub);
592857 }
593593- return new ASN1(streamStart, header, len, tag, tagLen, sub);
594594-};
595858596596-return ASN1;
597597-598598-});
859859+}
+79-82
base64.js
···11// Base64 JavaScript decoder
22-// Copyright (c) 2008-2022 Lapo Luchini <lapo@lapo.it>
22+// Copyright (c) 2008 Lapo Luchini <lapo@lapo.it>
3344// Permission to use, copy, modify, and/or distribute this software for any
55// purpose with or without fee is hereby granted, provided that the above
66// copyright notice and this permission notice appear in all copies.
77-//
77+//
88// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
99// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1010// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
···1313// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1414// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15151616-(typeof define != 'undefined' ? define : function (factory) { 'use strict';
1717- if (typeof module == 'object') module.exports = factory();
1818- else window.base64 = factory();
1919-})(function () {
2020-"use strict";
1616+const
1717+ haveU8 = (typeof Uint8Array == 'function');
1818+1919+let decoder; // populated on first usage
21202222-var Base64 = {},
2323- decoder, // populated on first usage
2424- haveU8 = (typeof Uint8Array == 'function');
2121+export class Base64 {
25222626-Base64.decode = function (a) {
2727- var isString = (typeof a == 'string');
2828- var i;
2929- if (decoder === undefined) {
3030- var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
3131- ignore = "= \f\n\r\t\u00A0\u2028\u2029";
3232- decoder = [];
3333- for (i = 0; i < 64; ++i)
3434- decoder[b64.charCodeAt(i)] = i;
3535- for (i = 0; i < ignore.length; ++i)
3636- decoder[ignore.charCodeAt(i)] = -1;
3737- // RFC 3548 URL & file safe encoding
3838- decoder['-'.charCodeAt(0)] = decoder['+'.charCodeAt(0)];
3939- decoder['_'.charCodeAt(0)] = decoder['/'.charCodeAt(0)];
4040- }
4141- var out = haveU8 ? new Uint8Array(a.length * 3 >> 2) : [];
4242- var bits = 0, char_count = 0, len = 0;
4343- for (i = 0; i < a.length; ++i) {
4444- var c = isString ? a.charCodeAt(i) : a[i];
4545- if (c == 61) // '='.charCodeAt(0)
2323+ static decode(a) {
2424+ let isString = (typeof a == 'string');
2525+ let i;
2626+ if (decoder === undefined) {
2727+ let b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
2828+ ignore = '= \f\n\r\t\u00A0\u2028\u2029';
2929+ decoder = [];
3030+ for (i = 0; i < 64; ++i)
3131+ decoder[b64.charCodeAt(i)] = i;
3232+ for (i = 0; i < ignore.length; ++i)
3333+ decoder[ignore.charCodeAt(i)] = -1;
3434+ // also support decoding Base64url (RFC 4648 section 5)
3535+ decoder['-'.charCodeAt(0)] = decoder['+'.charCodeAt(0)];
3636+ decoder['_'.charCodeAt(0)] = decoder['/'.charCodeAt(0)];
3737+ }
3838+ let out = haveU8 ? new Uint8Array(a.length * 3 >> 2) : [];
3939+ let bits = 0, char_count = 0, len = 0;
4040+ for (i = 0; i < a.length; ++i) {
4141+ let c = isString ? a.charCodeAt(i) : a[i];
4242+ if (c == 61) // '='.charCodeAt(0)
4343+ break;
4444+ c = decoder[c];
4545+ if (c == -1)
4646+ continue;
4747+ if (c === undefined)
4848+ throw 'Illegal character at offset ' + i;
4949+ bits |= c;
5050+ if (++char_count >= 4) {
5151+ out[len++] = (bits >> 16);
5252+ out[len++] = (bits >> 8) & 0xFF;
5353+ out[len++] = bits & 0xFF;
5454+ bits = 0;
5555+ char_count = 0;
5656+ } else {
5757+ bits <<= 6;
5858+ }
5959+ }
6060+ switch (char_count) {
6161+ case 1:
6262+ throw 'Base64 encoding incomplete: at least 2 bits missing';
6363+ case 2:
6464+ out[len++] = (bits >> 10);
4665 break;
4747- c = decoder[c];
4848- if (c == -1)
4949- continue;
5050- if (c === undefined)
5151- throw 'Illegal character at offset ' + i;
5252- bits |= c;
5353- if (++char_count >= 4) {
6666+ case 3:
5467 out[len++] = (bits >> 16);
5568 out[len++] = (bits >> 8) & 0xFF;
5656- out[len++] = bits & 0xFF;
5757- bits = 0;
5858- char_count = 0;
5959- } else {
6060- bits <<= 6;
6969+ break;
6170 }
6262- }
6363- switch (char_count) {
6464- case 1:
6565- throw "Base64 encoding incomplete: at least 2 bits missing";
6666- case 2:
6767- out[len++] = (bits >> 10);
6868- break;
6969- case 3:
7070- out[len++] = (bits >> 16);
7171- out[len++] = (bits >> 8) & 0xFF;
7272- break;
7171+ if (haveU8 && out.length > len) // in case it was originally longer because of ignored characters
7272+ out = out.subarray(0, len);
7373+ return out;
7374 }
7474- if (haveU8 && out.length > len) // in case it was originally longer because of ignored characters
7575- out = out.subarray(0, len);
7676- return out;
7777-};
78757979-Base64.pretty = function (str) {
8080- // fix padding
8181- if (str.length % 4 > 0)
8282- str = (str + '===').slice(0, str.length + str.length % 4);
8383- // convert RFC 3548 to standard Base64
8484- str = str.replace(/-/g, '+').replace(/_/g, '/');
8585- // 80 column width
8686- return str.replace(/(.{80})/g, '$1\n');
8787-};
7676+ static pretty(str) {
7777+ // fix padding
7878+ let pad = 4 - str.length % 4;
7979+ if (pad < 4)
8080+ str += '==='.slice(0, pad);
8181+ // convert Base64url (RFC 4648 section 5) to standard Base64 (RFC 4648 section 4)
8282+ str = str.replace(/-/g, '+').replace(/_/g, '/');
8383+ // 80 column width
8484+ return str.replace(/.{80}/g, '$&\n');
8585+ }
88868989-Base64.re = /-----BEGIN [^-]+-----([A-Za-z0-9+/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+/=\s]+)====|^([A-Za-z0-9+/=\s]+)$/;
9090-Base64.unarmor = function (a) {
9191- var m = Base64.re.exec(a);
9292- if (m) {
9393- if (m[1])
9494- a = m[1];
9595- else if (m[2])
9696- a = m[2];
9797- else if (m[3])
9898- a = m[3];
9999- else
100100- throw "RegExp out of sync";
8787+ static unarmor(a) {
8888+ let m = Base64.re.exec(a);
8989+ if (m) {
9090+ if (m[1])
9191+ a = m[1];
9292+ else if (m[2])
9393+ a = m[2];
9494+ else if (m[3])
9595+ a = m[3];
9696+ else
9797+ throw 'RegExp out of sync';
9898+ }
9999+ return Base64.decode(a);
101100 }
102102- return Base64.decode(a);
103103-};
104101105105-return Base64;
102102+}
106103107107-});
104104+Base64.re = /-----BEGIN [^-]+-----([A-Za-z0-9+/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+/=\s]+)====|^([A-Za-z0-9+/=\s]+)$/;
···11+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
···11+LDAPMessage example as found on ldap.com.
22+33+Original link:
44+https://ldap.com/ldapv3-wire-protocol-reference-ldap-message/
55+66+begin-base64 644 ldapmessage.der
77+MDUCAQVKEWRjPWV4YW1wbGUsZGM9Y29toB0wGwQWMS4yLjg0MC4xMTM1NTYuMS40LjgwNQEB/w==
88+====
···11// Hex JavaScript decoder
22-// Copyright (c) 2008-2022 Lapo Luchini <lapo@lapo.it>
22+// Copyright (c) 2008 Lapo Luchini <lapo@lapo.it>
3344// Permission to use, copy, modify, and/or distribute this software for any
55// purpose with or without fee is hereby granted, provided that the above
66// copyright notice and this permission notice appear in all copies.
77-//
77+//
88// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
99// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1010// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
···1313// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1414// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15151616-(typeof define != 'undefined' ? define : function (factory) { 'use strict';
1717- if (typeof module == 'object') module.exports = factory();
1818- else window.hex = factory();
1919-})(function () {
2020-"use strict";
1616+const
1717+ haveU8 = (typeof Uint8Array == 'function');
1818+1919+let decoder; // populated on first usage
21202222-var Hex = {},
2323- decoder, // populated on first usage
2424- haveU8 = (typeof Uint8Array == 'function');
2121+export class Hex {
25222626-/**
2727- * Decodes an hexadecimal value.
2828- * @param {string|Array|Uint8Array} a - a string representing hexadecimal data, or an array representation of its charcodes
2929- */
3030-Hex.decode = function(a) {
3131- var isString = (typeof a == 'string');
3232- var i;
3333- if (decoder === undefined) {
3434- var hex = "0123456789ABCDEF",
3535- ignore = " \f\n\r\t\u00A0\u2028\u2029";
3636- decoder = [];
3737- for (i = 0; i < 16; ++i)
3838- decoder[hex.charCodeAt(i)] = i;
3939- hex = hex.toLowerCase();
4040- for (i = 10; i < 16; ++i)
4141- decoder[hex.charCodeAt(i)] = i;
4242- for (i = 0; i < ignore.length; ++i)
4343- decoder[ignore.charCodeAt(i)] = -1;
4444- }
4545- var out = haveU8 ? new Uint8Array(a.length >> 1) : [],
4646- bits = 0,
4747- char_count = 0,
4848- len = 0;
4949- for (i = 0; i < a.length; ++i) {
5050- var c = isString ? a.charCodeAt(i) : a[i];
5151- c = decoder[c];
5252- if (c == -1)
5353- continue;
5454- if (c === undefined)
5555- throw 'Illegal character at offset ' + i;
5656- bits |= c;
5757- if (++char_count >= 2) {
5858- out[len++] = bits;
5959- bits = 0;
6060- char_count = 0;
6161- } else {
6262- bits <<= 4;
2323+ /**
2424+ * Decodes an hexadecimal value.
2525+ * @param {string|Array|Uint8Array} a - a string representing hexadecimal data, or an array representation of its charcodes
2626+ */
2727+ static decode(a) {
2828+ let isString = (typeof a == 'string');
2929+ let i;
3030+ if (decoder === undefined) {
3131+ let hex = '0123456789ABCDEF',
3232+ ignore = ' \f\n\r\t\u00A0\u2028\u2029';
3333+ decoder = [];
3434+ for (i = 0; i < 16; ++i)
3535+ decoder[hex.charCodeAt(i)] = i;
3636+ hex = hex.toLowerCase();
3737+ for (i = 10; i < 16; ++i)
3838+ decoder[hex.charCodeAt(i)] = i;
3939+ for (i = 0; i < ignore.length; ++i)
4040+ decoder[ignore.charCodeAt(i)] = -1;
4141+ }
4242+ let out = haveU8 ? new Uint8Array(a.length >> 1) : [],
4343+ bits = 0,
4444+ char_count = 0,
4545+ len = 0;
4646+ for (i = 0; i < a.length; ++i) {
4747+ let c = isString ? a.charCodeAt(i) : a[i];
4848+ c = decoder[c];
4949+ if (c == -1)
5050+ continue;
5151+ if (c === undefined)
5252+ throw 'Illegal character at offset ' + i;
5353+ bits |= c;
5454+ if (++char_count >= 2) {
5555+ out[len++] = bits;
5656+ bits = 0;
5757+ char_count = 0;
5858+ } else {
5959+ bits <<= 4;
6060+ }
6361 }
6262+ if (char_count)
6363+ throw 'Hex encoding incomplete: 4 bits missing';
6464+ if (haveU8 && out.length > len) // in case it was originally longer because of ignored characters
6565+ out = out.subarray(0, len);
6666+ return out;
6467 }
6565- if (char_count)
6666- throw "Hex encoding incomplete: 4 bits missing";
6767- if (haveU8 && out.length > len) // in case it was originally longer because of ignored characters
6868- out = out.subarray(0, len);
6969- return out;
7070-};
71687272-return Hex;
7373-7474-});
6969+}
···11-(typeof define != 'undefined' ? define : function (factory) { 'use strict';
22- if (typeof module == 'object') factory(function (name) { return require(name); });
33- else factory(function (name) { return window[name.substring(2)]; });
44-})(function (require) {
55-"use strict";
11+import './theme.js';
22+import { ASN1DOM } from './dom.js';
33+import { Base64 } from './base64.js';
44+import { Hex } from './hex.js';
55+import { Defs } from './defs.js';
66+import { tags } from './tags.js';
6777-var ASN1 = require('./asn1'),
88- Base64 = require('./base64'),
99- Hex = require('./hex'),
88+const
109 maxLength = 10240,
1110 reHex = /^\s*(?:[0-9A-Fa-f][0-9A-Fa-f]\s*)+$/,
1211 tree = id('tree'),
1312 dump = id('dump'),
1414- wantHex = id('wantHex'),
1313+ wantHex = checkbox('wantHex'),
1414+ trimHex = checkbox('trimHex'),
1515+ wantDef = checkbox('wantDef'),
1516 area = id('area'),
1617 file = id('file'),
1718 examples = id('examples'),
1818- hash = null;
1919+ selectDefs = id('definitions'),
2020+ selectTag = id('tags');
19212020-require('./dom'); // side effect: augment ASN1
2222+let hash = null;
2323+2124if (!window.console || !window.console.log) // IE8 with closed developer tools
2225 window.console = { log: function () {} };
2326function id(elem) {
2427 return document.getElementById(elem);
2528}
2629function text(el, string) {
2727- if ('textContent' in el)
2828- el.textContent = string;
2929- else
3030- el.innerText = string;
3030+ if ('textContent' in el) el.textContent = string;
3131+ else el.innerText = string;
3132}
3232-function decode(der, offset) {
3333- offset = offset || 0;
3333+function checkbox(name) {
3434+ const el = id(name);
3535+ const cfg = localStorage.getItem(name);
3636+ if (cfg === 'false')
3737+ el.checked = false;
3838+ el.onchange = () => localStorage.setItem(name, el.checked);
3939+ return el;
4040+}
4141+function show(asn1) {
3442 tree.innerHTML = '';
3543 dump.innerHTML = '';
4444+ let ul = document.createElement('ul');
4545+ ul.className = 'treecollapse';
4646+ tree.appendChild(ul);
4747+ ul.appendChild(asn1.toDOM());
4848+ if (wantHex.checked) dump.appendChild(asn1.toHexDOM(undefined, trimHex.checked));
4949+}
5050+export function decode(der, offset) {
5151+ offset = offset || 0;
3652 try {
3737- var asn1 = ASN1.decode(der, offset);
3838- tree.appendChild(asn1.toDOM());
3939- if (wantHex.checked)
4040- dump.appendChild(asn1.toHexDOM());
4141- var b64 = (der.length < maxLength) ? asn1.toB64String() : '';
4242- if (area.value === '')
4343- area.value = Base64.pretty(b64);
5353+ const asn1 = ASN1DOM.decode(der, offset);
5454+ if (wantDef.checked) {
5555+ selectDefs.innerHTML = '';
5656+ const types = Defs.commonTypes
5757+ .map(type => {
5858+ const stats = Defs.match(asn1, type);
5959+ return { type, match: stats.recognized / stats.total };
6060+ })
6161+ .sort((a, b) => b.match - a.match);
6262+ for (const t of types) {
6363+ t.element = document.createElement('option');
6464+ t.element.innerText = (t.match * 100).toFixed(1) + '% ' + t.type.description;
6565+ selectDefs.appendChild(t.element);
6666+ }
6767+ let not = document.createElement('option');
6868+ not.innerText = 'no definition';
6969+ selectDefs.appendChild(not);
7070+ Defs.match(asn1, types[0].type);
7171+ selectDefs.onchange = () => {
7272+ for (const t of types) {
7373+ if (t.element == selectDefs.selectedOptions[0]) {
7474+ Defs.match(asn1, t.type);
7575+ show(asn1);
7676+ return;
7777+ }
7878+ }
7979+ Defs.match(asn1, null);
8080+ show(asn1);
8181+ };
8282+ } else
8383+ selectDefs.innerHTML = '<option>no definition</option>';
8484+ show(asn1);
8585+ let b64 = der.length < maxLength ? asn1.toB64String() : '';
8686+ if (area.value === '') area.value = Base64.pretty(b64);
4487 try {
4588 window.location.hash = hash = '#' + b64;
4646- } catch (e) { // fails with "Access Denied" on IE with URLs longer than ~2048 chars
8989+ } catch (ignore) {
9090+ // fails with "Access Denied" on IE with URLs longer than ~2048 chars
4791 window.location.hash = hash = '#';
4892 }
4949- var endOffset = asn1.posEnd();
9393+ let endOffset = asn1.posEnd();
5094 if (endOffset < der.length) {
5151- var p = document.createElement('p');
9595+ let p = document.createElement('p');
5296 p.innerText = 'Input contains ' + (der.length - endOffset) + ' more bytes to decode.';
5353- var button = document.createElement('button');
9797+ let button = document.createElement('button');
5498 button.innerText = 'try to decode';
5599 button.onclick = function () {
56100 decode(der, endOffset);
···62106 text(tree, e);
63107 }
64108}
6565-function decodeText(val) {
109109+export function decodeText(val) {
66110 try {
6767- var der = reHex.test(val) ? Hex.decode(val) : Base64.unarmor(val);
111111+ let der = reHex.test(val) ? Hex.decode(val) : Base64.unarmor(val);
68112 decode(der);
69113 } catch (e) {
70114 text(tree, e);
71115 dump.innerHTML = '';
72116 }
73117}
7474-function decodeBinaryString(str) {
7575- var der;
118118+export function decodeBinaryString(str) {
119119+ let der;
76120 try {
7777- if (reHex.test(str))
7878- der = Hex.decode(str);
7979- else if (Base64.re.test(str))
8080- der = Base64.unarmor(str);
8181- else
8282- der = str;
121121+ if (reHex.test(str)) der = Hex.decode(str);
122122+ else if (Base64.re.test(str)) der = Base64.unarmor(str);
123123+ else der = str;
83124 decode(der);
8484- } catch (e) {
125125+ } catch (ignore) {
85126 text(tree, 'Cannot decode file.');
86127 dump.innerHTML = '';
87128 }
88129}
89130// set up buttons
9090-id('butDecode').onclick = function () { decodeText(area.value); };
9191-id('butClear').onclick = function () {
9292- area.value = '';
9393- file.value = '';
9494- tree.innerHTML = '';
9595- dump.innerHTML = '';
9696- hash = window.location.hash = '';
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};
9898-id('butExample').onclick = function () {
9999- console.log('Loading example:', examples.value);
100100- var request = new XMLHttpRequest();
101101- request.open('GET', 'examples/' + examples.value, true);
102102- request.onreadystatechange = function() {
103103- if (this.readyState !== 4)
104104- return;
105105- if (this.status >= 200 && this.status < 400) {
106106- area.value = this.responseText;
107107- decodeText(this.responseText);
108108- } else {
109109- console.log('Error loading example.');
110110- }
111111- };
112112- request.send();
113113-};
159159+for (const [name, onClick] of Object.entries(butClickHandlers)) {
160160+ let elem = id(name);
161161+ if (elem)
162162+ elem.onclick = onClick;
163163+}
114164// this is only used if window.FileReader
115165function read(f) {
116166 area.value = ''; // clear text area, will get b64 content
117117- var r = new FileReader();
167167+ let r = new FileReader();
118168 r.onloadend = function () {
119119- if (r.error)
120120- alert("Your browser couldn't read the specified file (error code " + r.error.code + ").");
121121- else
122122- decodeBinaryString(r.result);
169169+ if (r.error) alert("Your browser couldn't read the specified file (error code " + r.error.code + ').');
170170+ else decodeBinaryString(r.result);
123171 };
124172 r.readAsBinaryString(f);
125173}
126174function load() {
127127- if (file.files.length === 0)
128128- alert("Select a file to load first.");
129129- else
130130- read(file.files[0]);
175175+ if (file.files.length === 0) alert('Select a file to load first.');
176176+ else read(file.files[0]);
131177}
132178function loadFromHash() {
133179 if (window.location.hash && window.location.hash != hash) {
···135181 // Firefox is not consistent with other browsers and returns an
136182 // already-decoded hash string so we risk double-decoding here,
137183 // but since % is not allowed in base64 nor hexadecimal, it's ok
138138- var val = decodeURIComponent(hash.substr(1));
139139- if (val.length)
140140- decodeText(val);
184184+ let val = decodeURIComponent(hash.substr(1));
185185+ if (val.length) decodeText(val);
141186 }
142187}
143188function stop(e) {
···146191}
147192function dragAccept(e) {
148193 stop(e);
149149- if (e.dataTransfer.files.length > 0)
150150- read(e.dataTransfer.files[0]);
194194+ if (e.dataTransfer.files.length > 0) read(e.dataTransfer.files[0]);
151195}
152196// main
153153-if ('onhashchange' in window)
154154- window.onhashchange = loadFromHash;
197197+if ('onhashchange' in window) window.onhashchange = loadFromHash;
155198loadFromHash();
156199document.ondragover = stop;
157200document.ondragleave = stop;
158158-if ('FileReader' in window && 'readAsBinaryString' in (new FileReader())) {
201201+if ('FileReader' in window && 'readAsBinaryString' in new FileReader()) {
159202 file.style.display = 'block';
160203 file.onchange = load;
161204 document.ondrop = dragAccept;
162205}
163163-164164-});
206206+for (let tag in tags) {
207207+ let date = tags[tag];
208208+ let el = document.createElement('option');
209209+ el.value = tag;
210210+ el.innerText = date + ' ' + tag;
211211+ selectTag.appendChild(el);
212212+}
213213+selectTag.onchange = function (ev) {
214214+ let tag = ev.target.selectedOptions[0].value;
215215+ window.location.href = 'https://rawcdn.githack.com/lapo-luchini/asn1js/' + tag + '/index.html';
216216+};
-113
int10.js
···11-// Big integer base-10 printing library
22-// Copyright (c) 2008-2022 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-/**
2525- * Arbitrary length base-10 value.
2626- * @param {number} value - Optional initial value (will be 0 otherwise).
2727- */
2828-function Int10(value) {
2929- this.buf = [+value || 0];
3030-}
3131-3232-/**
3333- * Multiply value by m and add c.
3434- * @param {number} m - multiplier, must be < =256
3535- * @param {number} c - value to add
3636- */
3737-Int10.prototype.mulAdd = function (m, c) {
3838- // assert(m <= 256)
3939- var b = this.buf,
4040- l = b.length,
4141- i, t;
4242- for (i = 0; i < l; ++i) {
4343- t = b[i] * m + c;
4444- if (t < max)
4545- c = 0;
4646- else {
4747- c = 0|(t / max);
4848- t -= c * max;
4949- }
5050- b[i] = t;
5151- }
5252- if (c > 0)
5353- b[i] = c;
5454-};
5555-5656-/**
5757- * Subtract value.
5858- * @param {number} c - value to subtract
5959- */
6060-Int10.prototype.sub = function (c) {
6161- var b = this.buf,
6262- l = b.length,
6363- i, t;
6464- for (i = 0; i < l; ++i) {
6565- t = b[i] - c;
6666- if (t < 0) {
6767- t += max;
6868- c = 1;
6969- } else
7070- c = 0;
7171- b[i] = t;
7272- }
7373- while (b[b.length - 1] === 0)
7474- b.pop();
7575-};
7676-7777-/**
7878- * Convert to decimal string representation.
7979- * @param {*} base - optional value, only value accepted is 10
8080- */
8181-Int10.prototype.toString = function (base) {
8282- if ((base || 10) != 10)
8383- throw 'only base 10 is supported';
8484- var b = this.buf,
8585- s = b[b.length - 1].toString();
8686- for (var i = b.length - 2; i >= 0; --i)
8787- s += (max + b[i]).toString().substring(1);
8888- return s;
8989-};
9090-9191-/**
9292- * Convert to Number value representation.
9393- * Will probably overflow 2^53 and thus become approximate.
9494- */
9595-Int10.prototype.valueOf = function () {
9696- var b = this.buf,
9797- v = 0;
9898- for (var i = b.length - 1; i >= 0; --i)
9999- v = v * max + b[i];
100100- return v;
101101-};
102102-103103-/**
104104- * Return value as a simple Number (if it is <= 10000000000000), or return this.
105105- */
106106-Int10.prototype.simplify = function () {
107107- var b = this.buf;
108108- return (b.length == 1) ? b[0] : this;
109109-};
110110-111111-return Int10;
112112-113113-});
···1010fi
1111cat dumpasn1.cfg | \
1212tr -d '\r' | \
1313-awk -v url="$URL" '
1313+awk -v apos="'" -v q='"' -v url="$URL" '
1414 function clean() {
1515 oid = "";
1616 comment = "";
···1919 }
2020 BEGIN {
2121 FS = "= *";
2222- apos = sprintf("%c", 39);
2322 clean();
2423 print "// Converted from: " url;
2524 print "// which is made by Peter Gutmann and whose license states:";
2625 print "// You can use this code in whatever way you want,";
2726 print "// as long as you don" apos "t try to claim you wrote it.";
2828- print "(typeof define != " apos "undefined" apos " ? define : function (factory) { " apos "use strict" apos ";";
2929- print " if (typeof module == " apos "object" apos ") module.exports = factory();";
3030- print " else window.oids = factory();";
3131- print "})(function () {";
3232- print apos "use strict" apos ";";
3333- print "return {";
2727+ print "export const oids = {";
3428 }
3529 /^OID/ { oid = $2; }
3630 /^Comment/ { comment = $2; }
···4943 }
5044 }
5145 END {
5252- print "\"END\": \"\""
5353- print "};});"
4646+ print "};"
5447 }
5548' >oids.js
5649echo Conversion completed.
+15-3
updateRFC.sh
···11#/bin/sh
22+RFCs="5280 5208 3369 3161 2986 4211 4210 8017 4511"
23downloadRFC() {
34 URL="https://www.ietf.org/rfc/rfc$1.txt"
45 if [ -x /usr/bin/fetch ]; then
···1011 exit 1
1112 fi
1213}
1313-echo '{}' > rfcasn1.json # start from scratch
1414+echo '{}' > rfcdef.json # start from scratch
1415mkdir -p rfc
1516cd rfc
1616-for n in 5280 3369 3161; do
1717+for n in $RFCs; do
1718 downloadRFC $n
1818- ../parseRFC.js rfc$n.txt ../rfcasn1.json
1919+ ../parseRFC.js rfc$n.txt ../rfcdef.json
1920done
2121+cd ..
2222+{
2323+ echo "// content parsed from ASN.1 definitions as found in the following RFCs: $RFCs"
2424+ echo "// Copyright (C) The IETF Trust (2008)"
2525+ echo "// as far as I can tell this file is allowed under the following clause:"
2626+ echo "// It is acceptable under the current IETF rules (RFC 5378) to modify extracted code if necessary."
2727+ echo "// https://trustee.ietf.org/about/faq/#reproducing-rfcs"
2828+ echo -n "export const rfcdef = "
2929+ cat rfcdef.json
3030+ echo ";"
3131+} > rfcdef.js
2032echo Conversion completed.