JavaScript generic ASN.1 parser (mirror)
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

First version of an ASN.1 parser. It is capable of reading most of RFC5280, RFC3369, RFC3161.

+501
+501
parseRFC.js
··· 1 + #! /usr/bin/env node 2 + 3 + const 4 + util = require('util'), 5 + fs = require('fs'), 6 + patches = { // to fix some known RFCs' ASN.1 syntax errors 7 + 0: [ 8 + [ /\n\n[A-Z].*\n\f\n[A-Z].*\n\n/g, '' ], // page change 9 + ], 10 + 2459: [ // currently unsupported 11 + [ 'videotex (8) } (0..ub-integer-options)', 'videotex (8) }' ], 12 + [ /OBJECT IDENTIFIER \( id-qt-cps \| id-qt-unotice \)/g, 'OBJECT IDENTIFIER' ], 13 + [ /SIGNED \{ (SEQUENCE \{[^}]+\})\s*\}/g, 'SEQUENCE { toBeSigned $1, algorithm AlgorithmIdentifier, signature BIT STRING }' ], 14 + [ /EXTENSION\.&[^,]+/g, 'OBJECT IDENTIFIER'], 15 + ], 16 + 3161: [ // actual syntax errors 17 + [ /--.*}/g, '}' ], 18 + [ /^( +)--.*\n(?:\1 .*\n)+/mg, '' ], 19 + [ /addInfoNotAvailable \(17\)/g, '$&,' ], 20 + ], 21 + 5280: [ // currently unsupported 22 + [ 'videotex (8) } (0..ub-integer-options)', 'videotex (8) }' ], 23 + [ /OBJECT IDENTIFIER \( id-qt-cps \| id-qt-unotice \)/g, 'OBJECT IDENTIFIER' ], 24 + ], 25 + }; 26 + 27 + let asn1; 28 + let currentMod; 29 + 30 + function Parser(enc, pos) { 31 + this.enc = enc; 32 + this.pos = pos; 33 + this.start = pos; 34 + } 35 + Parser.prototype.getChar = function (pos) { 36 + if (pos === undefined) 37 + pos = this.pos++; 38 + if (pos >= this.enc.length) 39 + throw 'Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length; 40 + return this.enc.charAt(pos); 41 + }; 42 + Parser.prototype.exception = function (s, pos) { 43 + if (pos == undefined) pos = this.pos; 44 + let from = Math.max(pos - 30, this.start); 45 + let to = Math.min(pos + 30, this.enc.length); 46 + let ctx = ''; 47 + let arrow = ''; 48 + let i = from; 49 + for (; i < pos; ++i) { 50 + ctx += this.getChar(i); 51 + arrow += ' '; 52 + } 53 + ctx += this.getChar(i++); 54 + arrow += '^'; 55 + for (; i < to; ++i) 56 + ctx += this.getChar(i); 57 + throw new Error('[position ' + pos + '] ' + s + '\n' + ctx.replace(/\s/g, ' ') + '\n' + arrow); 58 + }; 59 + Parser.prototype.peek = function () { 60 + return (typeof this.enc == "string") ? this.enc.charCodeAt(this.pos) : this.enc[this.pos]; 61 + }; 62 + Parser.prototype.peekChar = function () { 63 + return (typeof this.enc == "string") ? this.enc.charAt(this.pos) : String.fromCharCode(this.enc[this.pos]); 64 + }; 65 + Parser.prototype.isWhitespace = function () { 66 + let c = this.peekChar(); 67 + return c == ' ' || c == '\n'; 68 + }; 69 + Parser.prototype.isDigit = function () { 70 + let c = this.peekChar(); 71 + return c >= '0' && c <= '9'; 72 + }; 73 + // const reWhitespace = /(?:\s|--(?:[}-]?[^\n}-])*(?:\n|--))*/y; 74 + const reWhitespace = /(?:\s|--(?:-?[^\n-])*(?:\n|--))*/my; 75 + Parser.prototype.skipWhitespace = function () { 76 + reWhitespace.lastIndex = this.pos; 77 + let s = reWhitespace.exec(this.enc); 78 + if (s) 79 + this.pos = reWhitespace.lastIndex; 80 + }; 81 + // DefStream.prototype.eat = function (str) { 82 + // for (let i = 0; i < str.length; ++i) { 83 + // let c = this.getChar(); 84 + // if (c != str.charAt(i)) 85 + // throw new Error("Found '" + c + "', was expecting '" + str.charAt(i) + "'"); 86 + // } 87 + // }; 88 + Parser.prototype.getRegEx = function (type, re) { 89 + this.skipWhitespace(); 90 + re.lastIndex = this.pos; 91 + let s = re.exec(this.enc); //TODO: does not work with typed arrays 92 + if (!s) 93 + this.exception("Found '" + this.peekChar() + "', was expecting a " + type); 94 + s = s[0]; 95 + // console.log('[debug] getRexEx@' + this.pos + ' = ' + s); 96 + this.pos = re.lastIndex; 97 + this.skipWhitespace(); 98 + return s; 99 + }; 100 + const reIdentifier = /[a-zA-Z](?:[-]?[a-zA-Z0-9])*/y; 101 + Parser.prototype.parseIdentifier = function () { 102 + let id = this.getRegEx('identifier', reIdentifier); 103 + // console.log('[debug] parseIdentifier = ' + id); 104 + return id; 105 + }; 106 + const reNumber = /0|[1-9][0-9]*/y; 107 + Parser.prototype.parseNumber = function () { 108 + let id = this.getRegEx('number', reNumber); 109 + // console.log('[debug] parseNumber = ' + id); 110 + return id; 111 + }; 112 + const reToken = /[(){},\[\];]|::=|OPTIONAL|DEFAULT|NULL|TRUE|FALSE|\.\.|OF|SIZE|MIN|MAX|DEFINED BY|DEFINITIONS|TAGS|BEGIN|EXPORTS|IMPORTS|FROM|END/y; 113 + Parser.prototype.parseToken = function () { 114 + let tok = this.getRegEx('token', reToken); 115 + return tok; 116 + }; 117 + Parser.prototype.tryToken = function (expect) { 118 + let p = this.pos; 119 + let t; 120 + try { t = this.parseToken(); } catch (e) {} 121 + // console.log('[debug] tryToken(' + expect + ') = ' + t); 122 + if (t == expect) 123 + return true; 124 + else { 125 + this.pos = p; 126 + return false; 127 + } 128 + }; 129 + Parser.prototype.expectToken = function (expect) { 130 + let p = this.pos; 131 + let t; 132 + try { t = this.parseToken(); } 133 + catch (e) { console.log('[debug] expectToken', e); } 134 + // console.log('[debug] expectToken(' + expect + ') = ' + t); 135 + if (t != expect) { 136 + this.pos = p; 137 + this.exception("Found '" + t + "', was expecting '" + expect + "'"); 138 + } 139 + }; 140 + Parser.prototype.parseNumberOrValue = function () { 141 + if (this.isDigit()) 142 + return +this.parseNumber(); 143 + return this.parseIdentifier(); 144 + }; 145 + Parser.prototype.parseRange = function () { 146 + let min = this.tryToken('MIN') ? 'MIN' : this.parseNumberOrValue(); 147 + if (this.tryToken('..')) { 148 + let max = this.tryToken('MAX') ? 'MAX' : this.parseNumberOrValue(); 149 + return [min, max]; 150 + } 151 + return min; 152 + }; 153 + const reType = /ANY|BOOLEAN|INTEGER|(?:BIT|OCTET)\s+STRING|OBJECT\s+IDENTIFIER|SEQUENCE|SET|CHOICE|ENUMERATED|(?:Generalized|UTC)Time|(?:BMP|General|Graphic|IA5|ISO64|Numeric|Printable|Teletex|T61|Universal|UTF8|Videotex|Visible)String/y; 154 + Parser.prototype.parseBuiltinType = function () { 155 + let x = { 156 + name: this.getRegEx('type', reType), 157 + type: 'builtin' 158 + }; 159 + // console.log('[debug] parseType = ' + x.name); 160 + try { 161 + switch (x.name) { 162 + case 'ANY': 163 + if (this.tryToken('DEFINED BY')) 164 + x.definedBy = this.parseIdentifier(); 165 + break; 166 + case 'BOOLEAN': 167 + case 'OCTET STRING': 168 + case 'OBJECT IDENTIFIER': 169 + break; 170 + case 'CHOICE': 171 + x.content = this.parseElementTypeList(); 172 + break; 173 + case 'SEQUENCE': 174 + case 'SET': 175 + if (this.peekChar() == '{') { 176 + x.content = this.parseElementTypeList(); 177 + } else { 178 + x.typeOf = 1; 179 + if (this.tryToken('SIZE')) { 180 + this.expectToken('('); 181 + x.size = this.parseRange(); 182 + this.expectToken(')'); 183 + } 184 + this.expectToken('OF'); 185 + x.content = [this.parseType()]; 186 + } 187 + break; 188 + case 'INTEGER': 189 + if (this.tryToken('(')) { 190 + x.range = this.parseRange(); 191 + this.expectToken(')'); 192 + } 193 + // intentional fall-thru 194 + case 'ENUMERATED': 195 + case 'BIT STRING': 196 + if (this.tryToken('{')) { 197 + x.content = {}; 198 + do { 199 + let id = this.parseIdentifier(); 200 + this.expectToken('('); 201 + let val = this.parseNumber(); //TODO: signed 202 + this.expectToken(')'); 203 + x.content[id] = +val; 204 + } while (this.tryToken(',')); 205 + this.expectToken('}'); 206 + } 207 + break; 208 + case 'BMPString': 209 + case 'GeneralString': 210 + case 'GraphicString': 211 + case 'IA5String': 212 + case 'ISO646String': 213 + case 'NumericString': 214 + case 'PrintableString': 215 + case 'TeletexString': 216 + case 'T61String': 217 + case 'UniversalString': 218 + case 'UTF8String': 219 + case 'VideotexString': 220 + case 'VisibleString': 221 + if (this.tryToken('(')) { 222 + if (this.tryToken('SIZE')) { 223 + this.expectToken('('); 224 + x.size = this.parseRange(); 225 + this.expectToken(')'); 226 + } 227 + this.expectToken(')'); 228 + } 229 + break; 230 + default: 231 + x.content = 'TODO:unknown' 232 + } 233 + } catch (e) { 234 + console.log('[debug] parseBuiltinType content', e); 235 + x.content = 'TODO:exception'; 236 + } 237 + return x; 238 + }; 239 + const reTagClass = /UNIVERSAL|APPLICATION|PRIVATE|/y; 240 + const reTagType = /IMPLICIT|EXPLICIT|/y; 241 + Parser.prototype.parseTaggedType = function () { 242 + this.expectToken('['); 243 + let tagClass = this.getRegEx('class', reTagClass) || 'CONTEXT'; //TODO: use module defaults 244 + let t = this.parseNumber(); 245 + this.expectToken(']'); 246 + let plicit = this.getRegEx('explicit/implicit', reTagType); 247 + let x = this.parseType(); 248 + return { 249 + name: '[' + t + ']', 250 + type: 'tag', 251 + "class": tagClass, 252 + explicit: (plicit == 'EXPLICIT'), 253 + content: [{ name: '', type: x }] 254 + }; 255 + }; 256 + function searchImportedType(id) { 257 + /*for (let imp of Object.values(currentMod.imports)) 258 + for (let name of imp.types) 259 + if (name == id) { 260 + if (!(imp.oid in asn1)) 261 + return null; 262 + if (id in asn1[imp.oid].types) 263 + return asn1[imp.oid].types[id]; 264 + return null; 265 + }*/ 266 + return null; 267 + } 268 + Parser.prototype.parseType = function () { 269 + if (this.peekChar() == '[') 270 + return this.parseTaggedType(); 271 + let p = this.pos; 272 + try { 273 + return this.parseBuiltinType(); 274 + } catch (e) { 275 + // console.log('[debug] parseAssignment failed on parseType', e); 276 + this.pos = p; 277 + let x = { 278 + name: this.parseIdentifier(), 279 + type: 'defined' 280 + }; 281 + let from = searchImportedType(x.name); 282 + if (from) 283 + x.module = from; 284 + return x; 285 + //TODO "restricted string type" 286 + } 287 + }; 288 + Parser.prototype.parseValueBoolean = function () { 289 + let p = this.pos; 290 + let t = this.parseToken(); 291 + if (t == 'TRUE') 292 + return true; 293 + if (t == 'FALSE') 294 + return false; 295 + this.pos = p; 296 + this.exception("Found '" + t + "', was expecting a boolean"); 297 + }; 298 + function searchImportedValue(id) { 299 + for (let imp of Object.values(currentMod.imports)) 300 + for (let name of imp.types) 301 + if (name == id) { 302 + if (!(imp.oid in asn1)) 303 + throw new Error('Cannot find module: ' + imp.oid + ' ' + id); 304 + if (id in asn1[imp.oid].values) 305 + return asn1[imp.oid].values[id]; 306 + throw new Error('Cannot find imported value: ' + imp.oid + ' ' + id); 307 + } 308 + throw new Error('Cannot find imported value in any module: ' + id); 309 + } 310 + Parser.prototype.parseValueOID = function () { 311 + this.expectToken('{'); 312 + let v = ''; 313 + while (true) { 314 + if (this.tryToken('}')) 315 + return v; 316 + let p = this.pos; 317 + let val; 318 + if (this.isDigit()) 319 + val = this.parseNumber(); 320 + else { 321 + this.pos = p; 322 + let id = this.parseIdentifier(); 323 + if (this.tryToken('(')) { 324 + val = this.parseNumber(); 325 + this.expectToken(')'); 326 + } else { 327 + if (id in currentMod.values) // defined in local module 328 + val = currentMod.values[id].value; 329 + else 330 + val = searchImportedValue(id); 331 + } 332 + } 333 + if (v.length) v += '.'; 334 + v += val; 335 + } 336 + }; 337 + Parser.prototype.parseValue = function (type) { 338 + let c = this.peekChar(); 339 + if (c == '{') 340 + return this.parseValueOID(); 341 + if (c >= '0' && c <= '9') 342 + return +this.parseNumber(); 343 + if (c == '-') 344 + return -this.parseNumber(); 345 + let p = this.pos; 346 + try { 347 + switch (this.parseToken()) { 348 + case 'TRUE': 349 + return true; 350 + case 'FALSE': 351 + return false; 352 + case 'NULL': 353 + return null; 354 + } 355 + } catch (e) { 356 + this.pos = p; 357 + } 358 + p = this.pos; 359 + try { 360 + return this.parseIdentifier(); 361 + } catch (e) { 362 + this.pos = p; 363 + } 364 + this.exception('Unknown value type.'); 365 + }; 366 + /*DefStream.prototype.parseValue = function (type) { 367 + console.log('[debug] parseValue type:', type); 368 + if (type.type == 'defined') { 369 + if (!(type.name in types)) 370 + this.exception("Missing type: " + type.name); 371 + type = types[type.name]; 372 + } 373 + switch (type.name) { 374 + case 'BOOLEAN': 375 + return this.parseValueBoolean(); 376 + case 'OBJECT IDENTIFIER': 377 + return this.parseValueOID(); 378 + default: 379 + console.log('[debug] parseValue unknown:', type); 380 + return 'TODO:value'; 381 + } 382 + };*/ 383 + Parser.prototype.parseElementType = function () { 384 + let x = { 385 + name: this.parseIdentifier(), 386 + type: this.parseType(), 387 + }; 388 + // console.log('[debug] parseElementType 1:', x); 389 + if (this.tryToken('OPTIONAL')) 390 + x.optional = true; 391 + if (this.tryToken('DEFAULT')) 392 + x.default = this.parseValue(x.type); 393 + // console.log('[debug] parseElementType 2:', x); 394 + return x; 395 + }; 396 + Parser.prototype.parseElementTypeList = function () { 397 + let v = []; 398 + this.expectToken('{'); 399 + do { 400 + v.push(this.parseElementType()); 401 + } while (this.tryToken(',')); 402 + this.expectToken('}'); 403 + return v; 404 + } 405 + Parser.prototype.parseAssignment = function () { 406 + let name = this.parseIdentifier(); 407 + if (this.tryToken('::=')) { // type assignment 408 + // console.log('type name', name); 409 + let type = this.parseType(); 410 + currentMod.types[name] = { name, type }; 411 + return currentMod.types[name]; 412 + } else { // value assignment 413 + // console.log('value name', name); 414 + let type = this.parseType(); 415 + // console.log('[debug] parseAssignment type:', type); 416 + this.expectToken('::='); 417 + let value = this.parseValue(type); 418 + currentMod.values[name] = { name, type, value }; 419 + return currentMod.values[name]; 420 + } 421 + }; 422 + Parser.prototype.parseModuleIdentifier = function () { 423 + return { 424 + name: this.parseIdentifier(), 425 + oid: this.parseValueOID(), 426 + }; 427 + }; 428 + Parser.prototype.parseSymbolsImported = function () { 429 + let imports = {}; 430 + do { 431 + let l = []; 432 + do { 433 + l.push(this.parseIdentifier()); 434 + } while (this.tryToken(',')); 435 + this.expectToken('FROM'); 436 + let mod = this.parseModuleIdentifier(); 437 + mod.types = l; 438 + imports[mod.oid] = mod; 439 + } while (this.peekChar() != ';'); 440 + return imports; 441 + }; 442 + const reTagDefault = /(AUTOMATIC|IMPLICIT|EXPLICIT) TAGS|/y; 443 + Parser.prototype.parseModuleDefinition = function (file) { 444 + let mod = this.parseModuleIdentifier(); 445 + currentMod = mod; // for deeply nested parsers 446 + mod.source = file; 447 + this.expectToken('DEFINITIONS'); 448 + mod.tagDefault = this.getRegEx('tag default', reTagDefault); 449 + this.expectToken('::='); 450 + this.expectToken('BEGIN'); 451 + //TODO this.tryToken('EXPORTS') 452 + if (this.tryToken('IMPORTS')) { 453 + mod.imports = this.parseSymbolsImported(); 454 + this.expectToken(';'); 455 + } 456 + mod.values = {}; 457 + mod.types = {}; 458 + while (!this.tryToken('END')) 459 + this.parseAssignment(); 460 + return mod; 461 + }; 462 + 463 + let s = fs.readFileSync(process.argv[2], 'utf8'); 464 + let num = /^Request for Comments: ([0-9]+)/m.exec(s)[1]; 465 + console.log('RFC:', num); 466 + for (let p of patches[0]) 467 + s = s.replace(p[0], p[1]); 468 + if (num in patches) 469 + for (let p of patches[num]) 470 + s = s.replace(p[0], p[1]); 471 + // fs.writeFileSync('rfc3161_patched.txt', s, 'utf8'); 472 + // console.log(s); 473 + asn1 = JSON.parse(fs.readFileSync('rfcasn1.json', 'utf8')); 474 + const reModuleDefinition = /\s[A-Z](?:[-]?[a-zA-Z0-9])*\s*\{[^\}]+\}\s*DEFINITIONS/gm; 475 + let m; 476 + while ((m = reModuleDefinition.exec(s))) { 477 + new Parser(s, m.index).parseModuleDefinition(process.argv[2]); 478 + console.log('Module:', currentMod.name); 479 + // fs.writeFileSync('rfc' + num + '.json', JSON.stringify(currentMod, null, 2) + '\n', 'utf8'); 480 + asn1[currentMod.oid] = currentMod; 481 + } 482 + /*asn1 = Object.keys(asn1).sort().reduce( 483 + (obj, key) => { 484 + obj[key] = asn1[key]; 485 + return obj; 486 + }, 487 + {} 488 + );*/ 489 + fs.writeFileSync('rfcasn1.json', JSON.stringify(asn1, null, 2) + '\n', 'utf8'); 490 + // console.log('Module:', mod); 491 + /*while ((idx = s.indexOf('::=', idx + 1)) >= 0) { 492 + let line = s.lastIndexOf('\n', idx) + 1; 493 + // console.log('[line] ' + s.slice(line, line+30)); 494 + try { 495 + let a = new DefStream(s, line).parseAssignment(); 496 + // console.log('[assignment]', util.inspect(a, {showHidden: false, depth: null, colors: true})); 497 + } catch (e) { 498 + console.log('Error:', e); 499 + } 500 + }*/ 501 + console.log('Done.');