1#!/usr/bin/env node
2'use strict';
3
4const
5 fs = require('fs'),
6 Base64 = require('./base64.js'),
7 ASN1 = require('./asn1.js'),
8 rfc = require('./rfcasn1.json'),
9 colYellow = "\x1b[33m",
10 colBlue = "\x1b[34m",
11 colReset = "\x1b[0m";
12
13function searchType(name) {
14 for (const r of Object.values(rfc))
15 if (name in r.types) {
16 // console.log(name + ' found in ' + r.name);
17 return r.types[name];
18 }
19 throw 'Type not found: ' + name;
20}
21
22function translate(def, tn) {
23 const id = def?.id;
24 if (def?.type == 'tag' && !def.explicit)
25 // def.type = def.content[0].type;
26 def = def.content[0].type;
27 while (def?.type == 'defined' || def?.type?.type == 'defined') {
28 const name = def?.type?.type ? def.type.name : def.name;
29 def = Object.assign({}, def);
30 def.type = searchType(name).type;
31 }
32 if (def?.type?.name == 'CHOICE') {
33 for (let c of def.type.content) {
34 c = translate(c);
35 if (tn == c.type.name || tn == c.name) {
36 def = Object.assign({}, def);
37 def.type = c.type.name ? c.type : c;
38 break;
39 }
40 }
41 }
42 if (id)
43 def = Object.assign({}, def, { id });
44 return def ?? { type: {} };
45}
46
47function firstUpper(s) {
48 return s[0].toUpperCase() + s.slice(1);
49}
50
51function print(value, def, stats, indent) {
52 if (indent === undefined) indent = '';
53 stats ??= {};
54 stats.total ??= 0;
55 stats.recognized ??= 0;
56 stats.defs ??= {};
57 let tn = value.typeName();
58 def = translate(def, tn);
59 tn = tn.replaceAll('_', ' ');
60 if (stats) ++stats.total;
61 let name = '';
62 if (def?.type) {
63 if (def.id) name += colBlue + def.id + colReset;
64 if (typeof def.type == 'object' && def.name) name = (name ? name + ' ' : '') + def.name;
65 if (stats && name != '') ++stats.recognized;
66 if (name) name += ' ';
67 }
68 let s = indent + name + colYellow + value.typeName() + colReset + " @" + value.stream.pos;
69 if (value.length >= 0)
70 s += "+";
71 s += value.length;
72 if (value.tag.tagConstructed)
73 s += " (constructed)";
74 else if ((value.tag.isUniversal() && ((value.tag.tagNumber == 0x03) || (value.tag.tagNumber == 0x04))) && (value.sub !== null))
75 s += " (encapsulates)";
76 let content = value.content();
77 if (content)
78 s += ": " + content.replace(/\n/g, '|');
79 s += "\n";
80 if (value.sub !== null) {
81 indent += ' ';
82 if (def?.type?.type)
83 def = def.type;
84 let j = def?.content ? 0 : -1;
85 for (const subval of value.sub) {
86 let type;
87 if (j >= 0) {
88 if (def.typeOf)
89 type = def.content[0];
90 else {
91 let tn = subval.typeName(); //.replaceAll('_', ' ');
92 do {
93 type = def.content[j++];
94 // type = translate(type, tn);
95 if (type?.type?.type)
96 type = type.type;
97 } while (type && ('optional' in type || 'default' in type) && type.name != 'ANY' && type.name != tn);
98 if (type?.type == 'defined')
99 stats.defs[type.id] = subval.content().split(/\n/);
100 else if (type?.definedBy && stats.defs?.[type.definedBy]?.[1]) // hope current OIDs contain the type name (will need to parse from RFC itself)
101 type = searchType(firstUpper(stats.defs[type.definedBy][1]));
102 }
103 }
104 s += print(subval, type, stats, indent);
105 }
106 }
107 return s;
108}
109
110let content = fs.readFileSync(process.argv[3]);
111try { // try PEM first
112 content = Base64.unarmor(content);
113} catch (e) { // try DER/BER then
114}
115let result = ASN1.decode(content);
116content = null;
117let stats = {};
118console.log(print(result, searchType(process.argv[2]), stats));
119console.log('Stats:', (stats.recognized * 100 / stats.total).toFixed(2) + '%');
120// console.log('Defs:', stats.defs);