diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/index-dark.css b/index-dark.css new file mode 100644 index 0000000..4980cfe --- /dev/null +++ b/index-dark.css @@ -0,0 +1,156 @@ +:root { + --main-bg-color: #0d1116; + --main-text-color: #f8f8f2; + --headline-text-color: #8be9fd; + --button-border-color: #616469; + --input-border-color: #373b43; + --input-bg-color: #0c0e11; + --link-color: #58a6ff; + --header-bg-color: #161b22; +} +html, body { + background-color: var(--main-bg-color); + color: var(--main-text-color); + font-family: Arial, Helvetica, sans-serif; + text-align: justify; + font-size: 10pt; + margin: 0px; +} +h1 { + font-weight: 200; +} +input, +textarea { + background-color: var(--input-bg-color); + color: var(--main-text-color); + border: 1px solid var(--input-border-color); +} +input[type="button"] { + background-color: var(--input-border-color); + color: var(--main-text-color); + border: 1px solid var(--button-border-color); +} +::-webkit-file-upload-button { + background-color: var(--input-border-color); + color: var(--main-text-color); + border: 1px solid var(--button-border-color); +} +select { + background-color: var(--input-bg-color); + color: var(--main-text-color); + border: 1px solid var(--input-border-color); + +} +a { + color: var(--link-color); +} +header { + background-color: var(--header-bg-color); + padding: 8px; + padding-left: 16px; +} +#help { + background-color: black; + margin: 0px; + padding: 4px; + padding-left: 20px; +} +.tt { + font-family: monospace; +} +.license .ref { + position: relative; +} +.license .hidden { + visibility: hidden; + position: absolute; + bottom: 0em; + /*white-space: pre;*/ + background-color: #4b4a4a; + border: solid 1px black; + padding: 2px; + margin-left: 15%; + margin-right: 15%; +} +.license:hover .hidden { + /*display: block;*/ + visibility: visible; +} +.node { + position: relative; +} +.sub { + padding-left: 1.5em; + border-left: solid 1px #383838; +} +.head { + height: 1em; + white-space: nowrap; +} +.node:hover > .head, .node.hover > .head { + color: var(--link-color); + font-weight: bold; +} +.node:hover > .head:hover, .node.hover > .head.hover { + color: rgb(155, 155, 234); +} +.node.collapsed { + font-style: italic; +} +.node.collapsed > .sub { + display: none; +} +.node.collapsed.hover > .sub { + display: block; +} +.value { + display: none; + position: absolute; + z-index: 2; + top: 1.2em; + left: 0; + background-color: var(--input-border-color); + border: solid 1px var(--button-border-color); + padding: 2px; +} +.head:hover + .value, .head.hover + .value { + display: block; +} +.preview { + margin-left: 1em; + color: #b5b3b3; + font-weight: normal; +} +.preview > .oid { + margin-left: 1em; + color: #989797; + font-weight: normal; +} +.spaces { + font-size: 0px; +} +#tree { + font-family: monospace; +} +#tree > p { + font-family: Arial, Helvetica, sans-serif; +} +#dump { + z-index: 1; + background-color: var(--input-bg-color); + border: solid 1px var(--input-border-color); + font-family: monospace; + white-space: pre; + padding: 5px; +} +#dump .tag { color: #58a6ff; } +#dump .dlen { color: darkcyan; } +#dump .ulen { color: #00b6b6; } +#dump .intro { color: #58a6ff; } +#dump .outro { color: #00b6b6; } +#dump .skip { color: #707070; background-color: #222222; } +#dump .hexCurrent { background-color: #727272; } +#dump .hexCurrent .hex { background-color: #474747; } +#dump .hexCurrent .tag { color: #6db0fc; } +#dump .hexCurrent .dlen { color: #00b6b6; } +#file { display: none; } diff --git a/index.html b/index.html index 0048652..d7ef9ca 100644 --- a/index.html +++ b/index.html @@ -4,75 +4,99 @@ ASN.1 JavaScript decoder - + -

ASN.1 JavaScript decoder

-
-
-
-
-
- -
- - - - -
- Examples: - - -
-
-
-

Instructions

-

This page contains a JavaScript generic ASN.1 parser that can decode any valid ASN.1 DER or BER structure whether Base64-encoded (raw base64, PEM armoring and begin-base64 are recognized) or Hex-encoded.

-

This tool can be used online at the address http://lapo.it/asn1js/ or offline, unpacking the ZIP file in a directory and opening index.html in a browser

-

On the left of the page will be printed a tree representing the hierarchical structure, on the right side an hex dump will be shown.
- Hovering on the tree highlights ancestry (the hovered node and all its ancestors get colored) and the position of the hovered node gets highlighted in the hex dump (with header and content in a different colors).
- Clicking a node in the tree will hide its sub-nodes (collapsed nodes can be noticed because they will become italic).

-
-

Copyright

-
+

ASN.1 JavaScript decoder

+
+
+
+
+
+
+
+
+ +

- Permission 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.
+ + + +

+ + + + + + +
+ +   +
Examples: + + +
+

- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -

-

ASN.1 JavaScript decoder Copyright © 2008-2022 Lapo Luchini; released as opensource under the ISC license.

+ Theme: + + +
+
+
+

Instructions

+

This page contains a JavaScript generic ASN.1 parser that can decode any valid ASN.1 DER or BER structure whether Base64-encoded (raw base64, PEM armoring and begin-base64 are recognized) or Hex-encoded.

+

This tool can be used online at the address http://lapo.it/asn1js/ or offline, unpacking the ZIP file in a directory and opening index.html in a browser

+

On the left of the page will be printed a tree representing the hierarchical structure, on the right side an hex dump will be shown.
+ Hovering on the tree highlights ancestry (the hovered node and all its ancestors get colored) and the position of the hovered node gets highlighted in the hex dump (with header and content in a different colors).
+ Clicking a node in the tree will hide its sub-nodes (collapsed nodes can be noticed because they will become italic).

+
+

Copyright

+
+

ASN.1 JavaScript decoder Copyright © 2008-2022 Lapo Luchini; released as opensource under the ISC license.

+
+

OBJECT IDENTIFIER values are recognized using data taken from Peter Gutmann's dumpasn1 program.

+

Links

+
-

OBJECT IDENTIFIER values are recognized using data taken from Peter Gutmann's dumpasn1 program.

-

Links

- -
- - - - - - - + + + + + + + + diff --git a/index.js b/index.js index f3f34de..2e6fbda 100644 --- a/index.js +++ b/index.js @@ -1,10 +1,53 @@ -(typeof define != 'undefined' ? define : function (factory) { 'use strict'; - if (typeof module == 'object') factory(function (name) { return require(name); }); - else factory(function (name) { return window[name.substring(2)]; }); -})(function (require) { -"use strict"; +(typeof define != 'undefined' + ? define + : function (factory) { + 'use strict'; + if (typeof module == 'object') + factory(function (name) { + return require(name); + }); + else + factory(function (name) { + return window[name.substring(2)]; + }); + })(function (require) { + 'use strict'; -var ASN1 = require('./asn1'), + // set dark theme depending on os settings + const themeSelect = document.querySelector('#theme-select'); + function setTheme() { + const storedTheme = localStorage.getItem('theme'); + let theme = 'os'; + if (storedTheme) { + theme = storedTheme; + } + + themeSelect.value = theme; + + if (theme == 'os') { + const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); + if (prefersDarkScheme.matches) { + theme = 'dark'; + } else { + theme = 'light'; + } + } + + const themeLink = document.querySelector('#theme-link'); + if (theme == 'dark') { + themeLink.href = 'index-dark.css'; + } else { + themeLink.href = 'index.css'; + } + } + setTheme(); + themeSelect.addEventListener('change', function () { + const selectedTheme = themeSelect.value; + localStorage.setItem('theme', selectedTheme); + setTheme(); + }); + + var ASN1 = require('./asn1'), Base64 = require('./base64'), Hex = require('./hex'), maxLength = 10240, @@ -17,148 +60,136 @@ var ASN1 = require('./asn1'), examples = id('examples'), hash = null; -require('./dom'); // side effect: augment ASN1 -if (!window.console || !window.console.log) // IE8 with closed developer tools + require('./dom'); // side effect: augment ASN1 + if (!window.console || !window.console.log) + // IE8 with closed developer tools window.console = { log: function () {} }; -function id(elem) { + function id(elem) { return document.getElementById(elem); -} -function text(el, string) { - if ('textContent' in el) - el.textContent = string; - else - el.innerText = string; -} -function decode(der, offset) { + } + function text(el, string) { + if ('textContent' in el) el.textContent = string; + else el.innerText = string; + } + function decode(der, offset) { offset = offset || 0; tree.innerHTML = ''; dump.innerHTML = ''; try { - var asn1 = ASN1.decode(der, offset); - tree.appendChild(asn1.toDOM()); - if (wantHex.checked) - dump.appendChild(asn1.toHexDOM()); - var b64 = (der.length < maxLength) ? asn1.toB64String() : ''; - if (area.value === '') - area.value = Base64.pretty(b64); - try { - window.location.hash = hash = '#' + b64; - } catch (e) { // fails with "Access Denied" on IE with URLs longer than ~2048 chars - window.location.hash = hash = '#'; - } - var endOffset = asn1.posEnd(); - if (endOffset < der.length) { - var p = document.createElement('p'); - p.innerText = 'Input contains ' + (der.length - endOffset) + ' more bytes to decode.'; - var button = document.createElement('button'); - button.innerText = 'try to decode'; - button.onclick = function () { - decode(der, endOffset); - }; - p.appendChild(button); - tree.insertBefore(p, tree.firstChild); - } + var asn1 = ASN1.decode(der, offset); + tree.appendChild(asn1.toDOM()); + if (wantHex.checked) dump.appendChild(asn1.toHexDOM()); + var b64 = der.length < maxLength ? asn1.toB64String() : ''; + if (area.value === '') area.value = Base64.pretty(b64); + try { + window.location.hash = hash = '#' + b64; + } catch (e) { + // fails with "Access Denied" on IE with URLs longer than ~2048 chars + window.location.hash = hash = '#'; + } + var endOffset = asn1.posEnd(); + if (endOffset < der.length) { + var p = document.createElement('p'); + p.innerText = 'Input contains ' + (der.length - endOffset) + ' more bytes to decode.'; + var button = document.createElement('button'); + button.innerText = 'try to decode'; + button.onclick = function () { + decode(der, endOffset); + }; + p.appendChild(button); + tree.insertBefore(p, tree.firstChild); + } } catch (e) { - text(tree, e); + text(tree, e); } -} -function decodeText(val) { + } + function decodeText(val) { try { - var der = reHex.test(val) ? Hex.decode(val) : Base64.unarmor(val); - decode(der); + var der = reHex.test(val) ? Hex.decode(val) : Base64.unarmor(val); + decode(der); } catch (e) { - text(tree, e); - dump.innerHTML = ''; + text(tree, e); + dump.innerHTML = ''; } -} -function decodeBinaryString(str) { + } + function decodeBinaryString(str) { var der; try { - if (reHex.test(str)) - der = Hex.decode(str); - else if (Base64.re.test(str)) - der = Base64.unarmor(str); - else - der = str; - decode(der); + if (reHex.test(str)) der = Hex.decode(str); + else if (Base64.re.test(str)) der = Base64.unarmor(str); + else der = str; + decode(der); } catch (e) { - text(tree, 'Cannot decode file.'); - dump.innerHTML = ''; + text(tree, 'Cannot decode file.'); + dump.innerHTML = ''; } -} -// set up buttons -id('butDecode').onclick = function () { decodeText(area.value); }; -id('butClear').onclick = function () { + } + // set up buttons + id('butDecode').onclick = function () { + decodeText(area.value); + }; + id('butClear').onclick = function () { area.value = ''; file.value = ''; tree.innerHTML = ''; dump.innerHTML = ''; hash = window.location.hash = ''; -}; -id('butExample').onclick = function () { + }; + id('butExample').onclick = function () { console.log('Loading example:', examples.value); var request = new XMLHttpRequest(); request.open('GET', 'examples/' + examples.value, true); - request.onreadystatechange = function() { - if (this.readyState !== 4) - return; - if (this.status >= 200 && this.status < 400) { - area.value = this.responseText; - decodeText(this.responseText); - } else { - console.log('Error loading example.'); - } + request.onreadystatechange = function () { + if (this.readyState !== 4) return; + if (this.status >= 200 && this.status < 400) { + area.value = this.responseText; + decodeText(this.responseText); + } else { + console.log('Error loading example.'); + } }; request.send(); -}; -// this is only used if window.FileReader -function read(f) { + }; + // this is only used if window.FileReader + function read(f) { area.value = ''; // clear text area, will get b64 content var r = new FileReader(); r.onloadend = function () { - if (r.error) - alert("Your browser couldn't read the specified file (error code " + r.error.code + ")."); - else - decodeBinaryString(r.result); + if (r.error) alert("Your browser couldn't read the specified file (error code " + r.error.code + ').'); + else decodeBinaryString(r.result); }; r.readAsBinaryString(f); -} -function load() { - if (file.files.length === 0) - alert("Select a file to load first."); - else - read(file.files[0]); -} -function loadFromHash() { + } + function load() { + if (file.files.length === 0) alert('Select a file to load first.'); + else read(file.files[0]); + } + function loadFromHash() { if (window.location.hash && window.location.hash != hash) { - hash = window.location.hash; - // Firefox is not consistent with other browsers and returns an - // already-decoded hash string so we risk double-decoding here, - // but since % is not allowed in base64 nor hexadecimal, it's ok - var val = decodeURIComponent(hash.substr(1)); - if (val.length) - decodeText(val); + hash = window.location.hash; + // Firefox is not consistent with other browsers and returns an + // already-decoded hash string so we risk double-decoding here, + // but since % is not allowed in base64 nor hexadecimal, it's ok + var val = decodeURIComponent(hash.substr(1)); + if (val.length) decodeText(val); } -} -function stop(e) { + } + function stop(e) { e.stopPropagation(); e.preventDefault(); -} -function dragAccept(e) { + } + function dragAccept(e) { stop(e); - if (e.dataTransfer.files.length > 0) - read(e.dataTransfer.files[0]); -} -// main -if ('onhashchange' in window) - window.onhashchange = loadFromHash; -loadFromHash(); -document.ondragover = stop; -document.ondragleave = stop; -if ('FileReader' in window && 'readAsBinaryString' in (new FileReader())) { + if (e.dataTransfer.files.length > 0) read(e.dataTransfer.files[0]); + } + // main + if ('onhashchange' in window) window.onhashchange = loadFromHash; + loadFromHash(); + document.ondragover = stop; + document.ondragleave = stop; + if ('FileReader' in window && 'readAsBinaryString' in new FileReader()) { file.style.display = 'block'; file.onchange = load; document.ondrop = dragAccept; -} - + } });