/ dumpASN1.js
dumpASN1.js
 1  #!/usr/bin/env node
 2  
 3  // usage:
 4  // ./dumpASN1.js filename
 5  // ./dumpASN1.js data:base64,MDMCAQFjLgQACgEACgEAAgEAAgEAAQEAoA+jDQQFTnRWZXIEBAEAAAAwCgQITmV0bG9nb24===
 6  // cat file.der | ./dumpASN1.js -
 7  
 8  import * as fs from 'node:fs';
 9  import { text as streamAsString } from 'node:stream/consumers';
10  import { Base64 } from './base64.js';
11  import { ASN1 } from './asn1.js';
12  import { Defs } from './defs.js';
13  
14  const
15      colYellow = '\x1b[33m',
16      colBlue = '\x1b[34m',
17      colReset = '\x1b[0m',
18      reDataURI = /^data:(?:[a-z-]+[/][a-z.+-]+;)?base64,([A-Za-z0-9+/=\s]+)$/;
19  
20  function print(value, indent) {
21      if (indent === undefined) indent = '';
22      const def = value.def;
23      let name = '';
24      if (def?.type) {
25          if (def.id) name += colBlue + def.id + colReset;
26          if (typeof def.type == 'object' && def.name) name = (name ? name + ' ' : '') + def.name;
27          if (def.mismatch) name = (name ? name + ' ' : '') + '[?]';
28          if (name) name += ' ';
29      }
30      let s = indent + name + colYellow + value.typeName() + colReset + ' @' + value.stream.pos;
31      if (value.length >= 0)
32          s += '+';
33      s += value.length;
34      if (value.tag.tagConstructed)
35          s += ' (constructed)';
36      else if ((value.tag.isUniversal() && ((value.tag.tagNumber == 0x03) || (value.tag.tagNumber == 0x04))) && (value.sub !== null))
37          s += ' (encapsulates)';
38      let content = value.content();
39      if (content)
40          s += ': ' + content.replace(/\n/g, '|');
41      s += '\n';
42      if (value.sub !== null) {
43          indent += '  ';
44          for (const subval of value.sub)
45              s += print(subval, indent);
46      }
47      return s;
48  }
49  
50  const filename = process.argv[2];
51  let content;
52  const match = reDataURI.exec(filename);
53  if (match)
54      content = Buffer.from(match[1]);
55  else if (filename == '-') // stdin
56      content = await streamAsString(process.stdin);
57  else
58      content = fs.readFileSync(filename);
59  try { // try PEM first
60      content = Base64.unarmor(content);
61  } catch (ignore) { // try DER/BER then
62  }
63  let result = ASN1.decode(content);
64  content = null;
65  const t0 = performance.now();
66  if (process.argv.length == 5) {
67      Defs.match(result, Defs.moduleAndType(Defs.RFC[process.argv[3]], process.argv[4]));
68  } else {
69      const types = Defs.commonTypes
70          .map(type => {
71              const stats = Defs.match(result, type);
72              return { type, match: stats.recognized / stats.total };
73          })
74          .sort((a, b) => b.match - a.match);
75      const t1 = performance.now();
76      console.log('Parsed in ' + (t1 - t0).toFixed(2) + ' ms; possible types:');
77      for (const t of types)
78          console.log((t.match * 100).toFixed(2).padStart(6) + '% ' + t.type.description);
79      Defs.match(result, types[0].type);
80      // const stats = Defs.match(result, types[0].type);
81      // console.log('Stats:', stats);
82      console.log('Parsed as:', result.def);
83      // const type = searchType(process.argv[2]);
84      // const stats = applyDef(result, type);
85  }
86  console.log(print(result));
87  // console.log('Stats:', (stats.recognized * 100 / stats.total).toFixed(2) + '%');
88  // // print(result, searchType(process.argv[2]), stats);
89  // // console.log('Defs:', stats.defs);