JavaScript generic ASN.1 parser (mirror)
at github-64 9.6 kB view raw
1// ASN.1 JavaScript decoder 2// Copyright (c) 2008-2023 Lapo Luchini <lapo@lapo.it> 3 4// Permission to use, copy, modify, and/or distribute this software for any 5// purpose with or without fee is hereby granted, provided that the above 6// copyright notice and this permission notice appear in all copies. 7// 8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 16(typeof define != 'undefined' ? define : function (factory) { 'use strict'; 17 if (typeof module == 'object') module.exports = factory(function (name) { return require(name); }); 18 else window.dom = factory(function (name) { return window[name.substring(2)]; }); 19})(function (require) { 20'use strict'; 21 22const 23 ASN1 = require('./asn1'), 24 oids = require('./oids'), 25 lineLength = 80, 26 contentLength = 8 * lineLength, 27 DOM = { 28 ellipsis: '\u2026', 29 tag: function (tagName, className, text) { 30 let t = document.createElement(tagName); 31 t.className = className; 32 if (text) t.innerText = text; 33 return t; 34 }, 35 text: function (str) { 36 return document.createTextNode(str); 37 }, 38 space: function () { 39 return DOM.tag('span', 'spaces', ' '); 40 }, 41 breakLines: function (str, length) { 42 let lines = str.split(/\r?\n/), 43 o = ''; 44 for (let i = 0; i < lines.length; ++i) { 45 let line = lines[i]; 46 if (i > 0) o += '\n'; 47 while (line.length > length) { 48 o += line.substring(0, length); 49 o += '\n'; 50 line = line.substring(length); 51 } 52 o += line; 53 } 54 return o; 55 } 56 } 57 58class ASN1DOM extends ASN1 { 59 60 toDOM(spaces) { 61 spaces = spaces || ''; 62 let isOID = (typeof oids === 'object') && (this.tag.isUniversal() && (this.tag.tagNumber == 0x06) || (this.tag.tagNumber == 0x0D)); 63 let node = DOM.tag('div', 'node'); 64 node.asn1 = this; 65 let head = DOM.tag('div', 'head'); 66 head.appendChild(DOM.tag('span', 'spaces', spaces)); 67 const typeName = this.typeName().replace(/_/g, ' '); 68 if (this.def) { 69 if (this.def.id) { 70 head.appendChild(DOM.tag('span', 'name id', this.def.id)); 71 head.appendChild(DOM.space()); 72 } 73 if (this.def.name && this.def.name != typeName) { 74 head.appendChild(DOM.tag('span', 'name type', this.def.name)); 75 head.appendChild(DOM.space()); 76 } 77 if (this.def.mismatch) { 78 head.appendChild(DOM.tag('span', 'name type', '[?]')); 79 head.appendChild(DOM.space()); 80 } 81 } 82 head.appendChild(DOM.text(typeName)); 83 let content = this.content(contentLength); 84 let oid; 85 if (content !== null) { 86 let preview = DOM.tag('span', 'preview'), 87 shortContent; 88 if (isOID) 89 content = content.split('\n', 1)[0]; 90 shortContent = (content.length > lineLength) ? content.substring(0, lineLength) + DOM.ellipsis : content; 91 preview.appendChild(DOM.space()); 92 preview.appendChild(DOM.text(shortContent)); 93 if (isOID) { 94 oid = oids[content]; 95 if (oid) { 96 if (oid.d) { 97 preview.appendChild(DOM.space()); 98 let oidd = DOM.tag('span', 'oid description', oid.d); 99 preview.appendChild(oidd); 100 } 101 if (oid.c) { 102 preview.appendChild(DOM.space()); 103 let oidc = DOM.tag('span', 'oid comment', '(' + oid.c + ')'); 104 preview.appendChild(oidc); 105 } 106 } 107 } 108 head.appendChild(preview); 109 content = DOM.breakLines(content, lineLength); 110 content = content.replace(/</g, '&lt;'); 111 content = content.replace(/\n/g, '<br>'); 112 } 113 node.appendChild(head); 114 this.node = node; 115 this.head = head; 116 let value = DOM.tag('div', 'value'); 117 let s = 'Offset: ' + this.stream.pos + '<br>'; 118 s += 'Length: ' + this.header + '+'; 119 if (this.length >= 0) 120 s += this.length; 121 else 122 s += (-this.length) + ' (undefined)'; 123 if (this.tag.tagConstructed) 124 s += '<br>(constructed)'; 125 else if ((this.tag.isUniversal() && ((this.tag.tagNumber == 0x03) || (this.tag.tagNumber == 0x04))) && (this.sub !== null)) 126 s += '<br>(encapsulates)'; 127 //TODO if (this.tag.isUniversal() && this.tag.tagNumber == 0x03) s += "Unused bits: " 128 if (content !== null) { 129 s += '<br>Value:<br><b>' + content + '</b>'; 130 if (isOID && oid) { 131 if (oid.d) s += '<br>' + oid.d; 132 if (oid.c) s += '<br>' + oid.c; 133 if (oid.w) s += '<br>(warning!)'; 134 } 135 } 136 value.innerHTML = s; 137 node.appendChild(value); 138 let sub = DOM.tag('div', 'sub'); 139 if (this.sub !== null) { 140 spaces += '\xA0 '; 141 for (let i = 0, max = this.sub.length; i < max; ++i) 142 sub.appendChild(this.sub[i].toDOM(spaces)); 143 } 144 node.appendChild(sub); 145 head.onclick = function () { 146 node.className = (node.className == 'node collapsed') ? 'node' : 'node collapsed'; 147 }; 148 return node; 149 } 150 fakeHover(current) { 151 this.node.className += ' hover'; 152 if (current) 153 this.head.className += ' hover'; 154 } 155 fakeOut(current) { 156 let re = / ?hover/; 157 this.node.className = this.node.className.replace(re, ''); 158 if (current) 159 this.head.className = this.head.className.replace(re, ''); 160 } 161 toHexDOM_sub(node, className, stream, start, end) { 162 if (start >= end) 163 return; 164 let sub = DOM.tag('span', className, stream.hexDump(start, end)); 165 node.appendChild(sub); 166 } 167 toHexDOM(root, trim=true) { 168 let node = DOM.tag('span', 'hex'); 169 if (root === undefined) root = node; 170 this.head.hexNode = node; 171 this.head.onmouseover = function () { this.hexNode.className = 'hexCurrent'; }; 172 this.head.onmouseout = function () { this.hexNode.className = 'hex'; }; 173 node.asn1 = this; 174 node.onmouseover = function () { 175 let current = !root.selected; 176 if (current) { 177 root.selected = this.asn1; 178 this.className = 'hexCurrent'; 179 } 180 this.asn1.fakeHover(current); 181 }; 182 node.onmouseout = function () { 183 let current = (root.selected == this.asn1); 184 this.asn1.fakeOut(current); 185 if (current) { 186 root.selected = null; 187 this.className = 'hex'; 188 } 189 }; 190 if (root == node) { 191 let lineStart = this.posStart() & 0xF; 192 if (lineStart != 0) { 193 let skip = DOM.tag('span', 'skip'); 194 let skipStr = ''; 195 for (let j = lineStart; j > 0; --j) 196 skipStr += ' '; 197 if (lineStart >= 8) 198 skipStr += ' '; 199 skip.innerText = skipStr; 200 node.appendChild(skip); 201 } 202 } 203 this.toHexDOM_sub(node, 'tag', this.stream, this.posStart(), this.posLen()); 204 this.toHexDOM_sub(node, (this.length >= 0) ? 'dlen' : 'ulen', this.stream, this.posLen(), this.posContent()); 205 if (this.sub === null) { 206 let start = this.posContent(); 207 let end = this.posEnd(); 208 if (!trim || end - start < 10 * 16) 209 node.appendChild(DOM.text( 210 this.stream.hexDump(start, end))); 211 else { 212 let end1 = start + 5 * 16 - (start & 0xF); 213 let start2 = end - 16 - (end & 0xF); 214 node.appendChild(DOM.text(this.stream.hexDump(start, end1))); 215 node.appendChild(DOM.tag('span', 'skip', '\u2026 skipping ' + (start2 - end1) + ' bytes \u2026\n')); 216 node.appendChild(DOM.text(this.stream.hexDump(start2, end))); 217 } 218 } else if (this.sub.length > 0) { 219 let first = this.sub[0]; 220 let last = this.sub[this.sub.length - 1]; 221 this.toHexDOM_sub(node, 'intro', this.stream, this.posContent(), first.posStart()); 222 for (let i = 0, max = this.sub.length; i < max; ++i) 223 node.appendChild(this.sub[i].toHexDOM(root, trim)); 224 this.toHexDOM_sub(node, 'outro', this.stream, last.posEnd(), this.posEnd()); 225 } else 226 this.toHexDOM_sub(node, 'outro', this.stream, this.posContent(), this.posEnd()); 227 return node; 228 } 229 static decode(stream, offset) { 230 return ASN1.decode(stream, offset, ASN1DOM); 231 } 232 233} 234 235return ASN1DOM; 236 237});