diff --git a/dom.js b/dom.js index 259e2cd..ba66d87 100644 --- a/dom.js +++ b/dom.js @@ -65,11 +65,19 @@ class ASN1DOM extends ASN1 { let node = DOM.tag('div', 'node'); node.asn1 = this; let head = DOM.tag('div', 'head'); - head.innerHTML = "" + spaces + '' + this.typeName().replace(/_/g, ' '); - if (this.def && this.def.id) { - let name = DOM.tag('span', 'name'); - name.innerText = this.def.id + ' '; - head.prepend(name); + const tn = this.typeName().replace(/_/g, ' '); + head.innerHTML = "" + spaces + '' + tn; + if (this.def) { + if (this.def.name && this.def.name != tn) { + let name = DOM.tag('span', 'name type'); + name.innerText = this.def.name + ' '; + head.prepend(name); + } + if (this.def.id) { + let name = DOM.tag('span', 'name id'); + name.innerText = this.def.id + ' '; + head.prepend(name); + } } let content = this.content(contentLength); let oid; -- 2.51.2 From b850adf50f1b08c37670486c35edef9ca1600e66 Mon Sep 17 00:00:00 2001 From: Lapo Luchini Date: Tue, 14 Mar 2023 19:16:39 +0000 Subject: [PATCH 02/14] Fix spaces in copy-paste content. Enhance `DOM.tag` text content. --- dom.js | 47 ++++++++++++++++++----------------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/dom.js b/dom.js index ba66d87..aef4c74 100644 --- a/dom.js +++ b/dom.js @@ -26,19 +26,17 @@ const contentLength = 8 * lineLength, DOM = { ellipsis: '\u2026', - tag: function (tagName, className) { + tag: function (tagName, className, text) { let t = document.createElement(tagName); t.className = className; + if (text) t.innerText = text; return t; }, text: function (str) { return document.createTextNode(str); }, space: function () { - let t = document.createElement('span'); - t.className = 'spaces'; - t.innerHTML = ' '; - return t; + return DOM.tag('span', 'spaces', ' '); }, breakLines: function (str, length) { let lines = str.split(/\r?\n/), @@ -65,20 +63,19 @@ class ASN1DOM extends ASN1 { let node = DOM.tag('div', 'node'); node.asn1 = this; let head = DOM.tag('div', 'head'); - const tn = this.typeName().replace(/_/g, ' '); - head.innerHTML = "" + spaces + '' + tn; + head.appendChild(DOM.tag('span', 'spaces', spaces)); + const typeName = this.typeName().replace(/_/g, ' '); if (this.def) { - if (this.def.name && this.def.name != tn) { - let name = DOM.tag('span', 'name type'); - name.innerText = this.def.name + ' '; - head.prepend(name); - } if (this.def.id) { - let name = DOM.tag('span', 'name id'); - name.innerText = this.def.id + ' '; - head.prepend(name); + head.appendChild(DOM.tag('span', 'name id', this.def.id)); + head.appendChild(DOM.space()); + } + if (this.def.name && this.def.name != typeName) { + head.appendChild(DOM.tag('span', 'name type', this.def.name)); + head.appendChild(DOM.space()); } } + head.appendChild(DOM.text(typeName)); let content = this.content(contentLength); let oid; if (content !== null) { @@ -94,14 +91,12 @@ class ASN1DOM extends ASN1 { if (oid) { if (oid.d) { preview.appendChild(DOM.space()); - let oidd = DOM.tag('span', 'oid description'); - oidd.appendChild(DOM.text(oid.d)); + let oidd = DOM.tag('span', 'oid description', oid.d); preview.appendChild(oidd); } if (oid.c) { preview.appendChild(DOM.space()); - let oidc = DOM.tag('span', 'oid comment'); - oidc.appendChild(DOM.text('(' + oid.c + ')')); + let oidc = DOM.tag('span', 'oid comment', '(' + oid.c + ')'); preview.appendChild(oidc); } } @@ -162,9 +157,7 @@ class ASN1DOM extends ASN1 { toHexDOM_sub(node, className, stream, start, end) { if (start >= end) return; - let sub = DOM.tag('span', className); - sub.appendChild(DOM.text( - stream.hexDump(start, end))); + let sub = DOM.tag('span', className, stream.hexDump(start, end)); node.appendChild(sub); } toHexDOM(root) { @@ -214,13 +207,9 @@ class ASN1DOM extends ASN1 { else { let end1 = start + 5 * 16 - (start & 0xF); let start2 = end - 16 - (end & 0xF); - node.appendChild(DOM.text( - this.stream.hexDump(start, end1))); - let sub = DOM.tag('span', 'skip'); - sub.appendChild(DOM.text('\u2026 skipping ' + (start2 - end1) + ' bytes \u2026\n')); - node.appendChild(sub); - node.appendChild(DOM.text( - this.stream.hexDump(start2, end))); + node.appendChild(DOM.text(this.stream.hexDump(start, end1))); + node.appendChild(DOM.tag('span', 'skip', '\u2026 skipping ' + (start2 - end1) + ' bytes \u2026\n')); + node.appendChild(DOM.text(this.stream.hexDump(start2, end))); } } else if (this.sub.length > 0) { let first = this.sub[0]; -- 2.51.2 From a8fd6a32a27de972120a2a0774302c7a764a7184 Mon Sep 17 00:00:00 2001 From: Lapo Luchini Date: Tue, 14 Mar 2023 19:17:40 +0000 Subject: [PATCH 03/14] Improve CSS for new definitions a bit. --- index.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/index.css b/index.css index c572b58..67231e6 100644 --- a/index.css +++ b/index.css @@ -143,8 +143,12 @@ header { display: block; } .name { + margin-right: 1em; color: var(--preview-border-color); } +.name.id { + color: var(--main-text-color); +} .value { display: none; position: absolute; -- 2.51.2 From 5734cc2f16b9b7b613df703a90a2f51b762f0bc1 Mon Sep 17 00:00:00 2001 From: Lapo Luchini Date: Tue, 14 Mar 2023 19:18:33 +0000 Subject: [PATCH 04/14] Add support for comments between identified and start of module definition. Refactor to ES6 class. --- parseRFC.js | 772 ++++++++++++++++++++++++++-------------------------- 1 file changed, 388 insertions(+), 384 deletions(-) diff --git a/parseRFC.js b/parseRFC.js index 23690d3..0225cb0 100755 --- a/parseRFC.js +++ b/parseRFC.js @@ -24,266 +24,19 @@ const ], }; -let asn1; -let currentMod; - -function Parser(enc, pos) { - this.enc = enc; - this.pos = pos; - this.start = pos; -} -Parser.prototype.getChar = function (pos) { - if (pos === undefined) - pos = this.pos++; - if (pos >= this.enc.length) - throw 'Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length; - return this.enc.charAt(pos); -}; -Parser.prototype.exception = function (s, pos) { - if (pos == undefined) pos = this.pos; - let from = Math.max(pos - 30, this.start); - let to = Math.min(pos + 30, this.enc.length); - let ctx = ''; - let arrow = ''; - let i = from; - for (; i < pos; ++i) { - ctx += this.getChar(i); - arrow += ' '; - } - ctx += this.getChar(i++); - arrow += '^'; - for (; i < to; ++i) - ctx += this.getChar(i); - throw new Error('[position ' + pos + '] ' + s + '\n' + ctx.replace(/\s/g, ' ') + '\n' + arrow); -}; -Parser.prototype.peek = function () { - return (typeof this.enc == 'string') ? this.enc.charCodeAt(this.pos) : this.enc[this.pos]; -}; -Parser.prototype.peekChar = function () { - return (typeof this.enc == 'string') ? this.enc.charAt(this.pos) : String.fromCharCode(this.enc[this.pos]); -}; -Parser.prototype.isWhitespace = function () { - let c = this.peekChar(); - return c == ' ' || c == '\n'; -}; -Parser.prototype.isDigit = function () { - let c = this.peekChar(); - return c >= '0' && c <= '9'; -}; // const reWhitespace = /(?:\s|--(?:[}-]?[^\n}-])*(?:\n|--))*/y; const reWhitespace = /(?:\s|--(?:-?[^\n-])*(?:\n|--))*/my; -Parser.prototype.skipWhitespace = function () { - reWhitespace.lastIndex = this.pos; - let s = reWhitespace.exec(this.enc); - if (s) - this.pos = reWhitespace.lastIndex; -}; -// DefStream.prototype.eat = function (str) { -// for (let i = 0; i < str.length; ++i) { -// let c = this.getChar(); -// if (c != str.charAt(i)) -// throw new Error("Found '" + c + "', was expecting '" + str.charAt(i) + "'"); -// } -// }; -Parser.prototype.getRegEx = function (type, re) { - this.skipWhitespace(); - re.lastIndex = this.pos; - let s = re.exec(this.enc); //TODO: does not work with typed arrays - if (!s) - this.exception("Found '" + this.peekChar() + "', was expecting a " + type); - s = s[0]; - // console.log('[debug] getRexEx@' + this.pos + ' = ' + s); - this.pos = re.lastIndex; - this.skipWhitespace(); - return s; -}; const reIdentifier = /[a-zA-Z](?:[-]?[a-zA-Z0-9])*/y; -Parser.prototype.parseIdentifier = function () { - let id = this.getRegEx('identifier', reIdentifier); - // console.log('[debug] parseIdentifier = ' + id); - return id; -}; const reNumber = /0|[1-9][0-9]*/y; -Parser.prototype.parseNumber = function () { - let id = this.getRegEx('number', reNumber); - // console.log('[debug] parseNumber = ' + id); - return id; -}; const reToken = /[(){},[\];]|::=|OPTIONAL|DEFAULT|NULL|TRUE|FALSE|\.\.|OF|SIZE|MIN|MAX|DEFINED BY|DEFINITIONS|TAGS|BEGIN|EXPORTS|IMPORTS|FROM|END/y; -Parser.prototype.parseToken = function () { - let tok = this.getRegEx('token', reToken); - return tok; -}; -Parser.prototype.tryToken = function (expect) { - let p = this.pos; - let t; - try { t = this.parseToken(); } catch (e) { /*ignore*/ } - // console.log('[debug] tryToken(' + expect + ') = ' + t); - if (t == expect) - return true; - else { - this.pos = p; - return false; - } -}; -Parser.prototype.expectToken = function (expect) { - let p = this.pos; - let t; - try { t = this.parseToken(); } - catch (e) { console.log('[debug] expectToken', e); } - // console.log('[debug] expectToken(' + expect + ') = ' + t); - if (t != expect) { - this.pos = p; - this.exception("Found '" + t + "', was expecting '" + expect + "'"); - } -}; -Parser.prototype.parseNumberOrValue = function () { - if (this.isDigit()) - return +this.parseNumber(); - return this.parseIdentifier(); -}; -Parser.prototype.parseRange = function () { - let min = this.tryToken('MIN') ? 'MIN' : this.parseNumberOrValue(); - if (this.tryToken('..')) { - let max = this.tryToken('MAX') ? 'MAX' : this.parseNumberOrValue(); - return [min, max]; - } - return min; -}; 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; -Parser.prototype.parseBuiltinType = function () { - let x = { - name: this.getRegEx('type', reType), - type: 'builtin', - }; - // console.log('[debug] parseType = ' + x.name); - try { - switch (x.name) { - case 'ANY': - if (this.tryToken('DEFINED BY')) - x.definedBy = this.parseIdentifier(); - break; - case 'BOOLEAN': - case 'OCTET STRING': - case 'OBJECT IDENTIFIER': - break; - case 'CHOICE': - x.content = this.parseElementTypeList(); - break; - case 'SEQUENCE': - case 'SET': - if (this.peekChar() == '{') { - x.content = this.parseElementTypeList(); - } else { - x.typeOf = 1; - if (this.tryToken('SIZE')) { - this.expectToken('('); - x.size = this.parseRange(); - this.expectToken(')'); - } - this.expectToken('OF'); - x.content = [this.parseType()]; - } - break; - case 'INTEGER': - if (this.tryToken('(')) { - x.range = this.parseRange(); - this.expectToken(')'); - } - // falls through - case 'ENUMERATED': - case 'BIT STRING': - if (this.tryToken('{')) { - x.content = {}; - do { - let id = this.parseIdentifier(); - this.expectToken('('); - let val = this.parseNumber(); //TODO: signed - this.expectToken(')'); - x.content[id] = +val; - } while (this.tryToken(',')); - this.expectToken('}'); - } - break; - case 'BMPString': - case 'GeneralString': - case 'GraphicString': - case 'IA5String': - case 'ISO646String': - case 'NumericString': - case 'PrintableString': - case 'TeletexString': - case 'T61String': - case 'UniversalString': - case 'UTF8String': - case 'VideotexString': - case 'VisibleString': - if (this.tryToken('(')) { - if (this.tryToken('SIZE')) { - this.expectToken('('); - x.size = this.parseRange(); - this.expectToken(')'); - } - this.expectToken(')'); - } - break; - default: - x.content = 'TODO:unknown'; - } - } catch (e) { - console.log('[debug] parseBuiltinType content', e); - x.content = 'TODO:exception'; - } - return x; -}; const reTagClass = /UNIVERSAL|APPLICATION|PRIVATE|/y; const reTagType = /IMPLICIT|EXPLICIT|/y; -Parser.prototype.parseTaggedType = function () { - this.expectToken('['); - let tagClass = this.getRegEx('class', reTagClass) || 'CONTEXT'; //TODO: use module defaults - let t = this.parseNumber(); - this.expectToken(']'); - let plicit = this.getRegEx('explicit/implicit', reTagType); - if (plicit == '') plicit = currentMod.tagDefault; - let x = this.parseType(); - return { - name: '[' + t + ']', - type: 'tag', - 'class': tagClass, - explicit: (plicit == 'EXPLICIT'), - content: [{ name: '', type: x }], - }; -}; -Parser.prototype.parseType = function () { - if (this.peekChar() == '[') - return this.parseTaggedType(); - let p = this.pos; - try { - return this.parseBuiltinType(); - } catch (e) { - // console.log('[debug] parseAssignment failed on parseType', e); - this.pos = p; - let x = { - name: this.parseIdentifier(), - type: 'defined', - }; - // let from = searchImportedType(x.name); - // if (from) - // x.module = from; - return x; - //TODO "restricted string type" - } -}; -Parser.prototype.parseValueBoolean = function () { - let p = this.pos; - let t = this.parseToken(); - if (t == 'TRUE') - return true; - if (t == 'FALSE') - return false; - this.pos = p; - this.exception("Found '" + t + "', was expecting a boolean"); -}; +const reTagDefault = /(AUTOMATIC|IMPLICIT|EXPLICIT) TAGS|/y; + +let asn1; +let currentMod; + function searchImportedValue(id) { for (let imp of Object.values(currentMod.imports)) for (let name of imp.types) @@ -296,154 +49,405 @@ function searchImportedValue(id) { } throw new Error('Cannot find imported value in any module: ' + id); } -Parser.prototype.parseValueOID = function () { - this.expectToken('{'); - let v = ''; - while (!this.tryToken('}')) { + +class Parser { + constructor(enc, pos) { + this.enc = enc; + this.pos = pos; + this.start = pos; + } + getChar(pos) { + if (pos === undefined) + pos = this.pos++; + if (pos >= this.enc.length) + throw 'Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length; + return this.enc.charAt(pos); + } + exception(s, pos) { + if (pos == undefined) pos = this.pos; + let from = Math.max(pos - 30, this.start); + let to = Math.min(pos + 30, this.enc.length); + let ctx = ''; + let arrow = ''; + let i = from; + for (; i < pos; ++i) { + ctx += this.getChar(i); + arrow += ' '; + } + ctx += this.getChar(i++); + arrow += '^'; + for (; i < to; ++i) + ctx += this.getChar(i); + throw new Error('[position ' + pos + '] ' + s + '\n' + ctx.replace(/\s/g, ' ') + '\n' + arrow); + } + peek() { + return (typeof this.enc == 'string') ? this.enc.charCodeAt(this.pos) : this.enc[this.pos]; + } + peekChar() { + return (typeof this.enc == 'string') ? this.enc.charAt(this.pos) : String.fromCharCode(this.enc[this.pos]); + } + isWhitespace() { + let c = this.peekChar(); + return c == ' ' || c == '\n'; + } + isDigit() { + let c = this.peekChar(); + return c >= '0' && c <= '9'; + } + skipWhitespace() { + reWhitespace.lastIndex = this.pos; + let s = reWhitespace.exec(this.enc); + if (s) + this.pos = reWhitespace.lastIndex; + } + // DefStream.prototype.eat = function (str) { + // for (let i = 0; i < str.length; ++i) { + // let c = this.getChar(); + // if (c != str.charAt(i)) + // throw new Error("Found '" + c + "', was expecting '" + str.charAt(i) + "'"); + // } + // }; + getRegEx(type, re) { + this.skipWhitespace(); + re.lastIndex = this.pos; + let s = re.exec(this.enc); //TODO: does not work with typed arrays + if (!s) + this.exception("Found '" + this.peekChar() + "', was expecting a " + type); + s = s[0]; + // console.log('[debug] getRexEx@' + this.pos + ' = ' + s); + this.pos = re.lastIndex; + this.skipWhitespace(); + return s; + } + parseIdentifier() { + let id = this.getRegEx('identifier', reIdentifier); + // console.log('[debug] parseIdentifier = ' + id); + return id; + } + parseNumber() { + let id = this.getRegEx('number', reNumber); + // console.log('[debug] parseNumber = ' + id); + return id; + } + parseToken() { + let tok = this.getRegEx('token', reToken); + return tok; + } + tryToken(expect) { let p = this.pos; - let val; - if (this.isDigit()) - val = this.parseNumber(); + let t; + try { t = this.parseToken(); } catch (e) { /*ignore*/ } + // console.log('[debug] tryToken(' + expect + ') = ' + t); + if (t == expect) + return true; else { this.pos = p; - let id = this.parseIdentifier(); - if (this.tryToken('(')) { - val = this.parseNumber(); - this.expectToken(')'); - } else { - if (id in currentMod.values) // defined in local module - val = currentMod.values[id].value; - else - val = searchImportedValue(id); - } + return false; } - if (v.length) v += '.'; - v += val; } - return v; -}; -Parser.prototype.parseValue = function () { - let c = this.peekChar(); - if (c == '{') - return this.parseValueOID(); - if (c >= '0' && c <= '9') - return +this.parseNumber(); - if (c == '-') - return -this.parseNumber(); - let p = this.pos; - try { - switch (this.parseToken()) { - case 'TRUE': - return true; - case 'FALSE': - return false; - case 'NULL': - return null; + expectToken(expect) { + let p = this.pos; + let t; + try { t = this.parseToken(); } + catch (e) { console.log('[debug] expectToken', e); } + // console.log('[debug] expectToken(' + expect + ') = ' + t); + if (t != expect) { + this.pos = p; + this.exception("Found '" + t + "', was expecting '" + expect + "'"); } - } catch (e) { - this.pos = p; } - p = this.pos; - try { + parseNumberOrValue() { + if (this.isDigit()) + return +this.parseNumber(); return this.parseIdentifier(); - } catch (e) { + } + parseRange() { + let min = this.tryToken('MIN') ? 'MIN' : this.parseNumberOrValue(); + if (this.tryToken('..')) { + let max = this.tryToken('MAX') ? 'MAX' : this.parseNumberOrValue(); + return [min, max]; + } + return min; + } + parseBuiltinType() { + let x = { + name: this.getRegEx('type', reType), + type: 'builtin', + }; + // console.log('[debug] parseType = ' + x.name); + try { + switch (x.name) { + case 'ANY': + if (this.tryToken('DEFINED BY')) + x.definedBy = this.parseIdentifier(); + break; + case 'BOOLEAN': + case 'OCTET STRING': + case 'OBJECT IDENTIFIER': + break; + case 'CHOICE': + x.content = this.parseElementTypeList(); + break; + case 'SEQUENCE': + case 'SET': + if (this.peekChar() == '{') { + x.content = this.parseElementTypeList(); + } else { + x.typeOf = 1; + if (this.tryToken('SIZE')) { + this.expectToken('('); + x.size = this.parseRange(); + this.expectToken(')'); + } + this.expectToken('OF'); + x.content = [this.parseType()]; + } + break; + case 'INTEGER': + if (this.tryToken('(')) { + x.range = this.parseRange(); + this.expectToken(')'); + } + // falls through + case 'ENUMERATED': + case 'BIT STRING': + if (this.tryToken('{')) { + x.content = {}; + do { + let id = this.parseIdentifier(); + this.expectToken('('); + let val = this.parseNumber(); //TODO: signed + this.expectToken(')'); + x.content[id] = +val; + } while (this.tryToken(',')); + this.expectToken('}'); + } + break; + case 'BMPString': + case 'GeneralString': + case 'GraphicString': + case 'IA5String': + case 'ISO646String': + case 'NumericString': + case 'PrintableString': + case 'TeletexString': + case 'T61String': + case 'UniversalString': + case 'UTF8String': + case 'VideotexString': + case 'VisibleString': + if (this.tryToken('(')) { + if (this.tryToken('SIZE')) { + this.expectToken('('); + x.size = this.parseRange(); + this.expectToken(')'); + } + this.expectToken(')'); + } + break; + default: + x.content = 'TODO:unknown'; + } + } catch (e) { + console.log('[debug] parseBuiltinType content', e); + x.content = 'TODO:exception'; + } + return x; + } + parseTaggedType() { + this.expectToken('['); + let tagClass = this.getRegEx('class', reTagClass) || 'CONTEXT'; //TODO: use module defaults + let t = this.parseNumber(); + this.expectToken(']'); + let plicit = this.getRegEx('explicit/implicit', reTagType); + if (plicit == '') plicit = currentMod.tagDefault; + let x = this.parseType(); + return { + name: '[' + t + ']', + type: 'tag', + 'class': tagClass, + explicit: (plicit == 'EXPLICIT'), + content: [{ name: '', type: x }], + }; + } + parseType() { + if (this.peekChar() == '[') + return this.parseTaggedType(); + let p = this.pos; + try { + return this.parseBuiltinType(); + } catch (e) { + // console.log('[debug] parseAssignment failed on parseType', e); + this.pos = p; + let x = { + name: this.parseIdentifier(), + type: 'defined', + }; + // let from = searchImportedType(x.name); + // if (from) + // x.module = from; + return x; + //TODO "restricted string type" + } + } + parseValueBoolean() { + let p = this.pos; + let t = this.parseToken(); + if (t == 'TRUE') + return true; + if (t == 'FALSE') + return false; this.pos = p; + this.exception("Found '" + t + "', was expecting a boolean"); } - this.exception('Unknown value type.'); -}; -/*DefStream.prototype.parseValue = function (type) { - console.log('[debug] parseValue type:', type); - if (type.type == 'defined') { - if (!(type.name in types)) - this.exception("Missing type: " + type.name); - type = types[type.name]; + parseValueOID() { + this.expectToken('{'); + let v = ''; + while (!this.tryToken('}')) { + let p = this.pos; + let val; + if (this.isDigit()) + val = this.parseNumber(); + else { + this.pos = p; + let id = this.parseIdentifier(); + if (this.tryToken('(')) { + val = this.parseNumber(); + this.expectToken(')'); + } else { + if (id in currentMod.values) // defined in local module + val = currentMod.values[id].value; + else + val = searchImportedValue(id); + } + } + if (v.length) v += '.'; + v += val; + } + return v; } - switch (type.name) { - case 'BOOLEAN': - return this.parseValueBoolean(); - case 'OBJECT IDENTIFIER': + parseValue() { + let c = this.peekChar(); + if (c == '{') return this.parseValueOID(); - default: - console.log('[debug] parseValue unknown:', type); - return 'TODO:value'; + if (c >= '0' && c <= '9') + return +this.parseNumber(); + if (c == '-') + return -this.parseNumber(); + let p = this.pos; + try { + switch (this.parseToken()) { + case 'TRUE': + return true; + case 'FALSE': + return false; + case 'NULL': + return null; + } + } catch (e) { + this.pos = p; + } + p = this.pos; + try { + return this.parseIdentifier(); + } catch (e) { + this.pos = p; + } + this.exception('Unknown value type.'); } -};*/ -Parser.prototype.parseElementType = function () { - let x = Object.assign({ id: this.parseIdentifier() }, this.parseType()); - // console.log('[debug] parseElementType 1:', x); - if (this.tryToken('OPTIONAL')) - x.optional = true; - if (this.tryToken('DEFAULT')) - x.default = this.parseValue(x.type); - // console.log('[debug] parseElementType 2:', x); - return x; -}; -Parser.prototype.parseElementTypeList = function () { - let v = []; - this.expectToken('{'); - do { - v.push(this.parseElementType()); - } while (this.tryToken(',')); - this.expectToken('}'); - return v; -}; -Parser.prototype.parseAssignment = function () { - let name = this.parseIdentifier(); - if (this.tryToken('::=')) { // type assignment - // console.log('type name', name); - let type = this.parseType(); - currentMod.types[name] = { name, type }; - return currentMod.types[name]; - } else { // value assignment - // console.log('value name', name); - let type = this.parseType(); - // console.log('[debug] parseAssignment type:', type); - this.expectToken('::='); - let value = this.parseValue(type); - currentMod.values[name] = { name, type, value }; - return currentMod.values[name]; + /*DefStream.prototype.parseValue = function (type) { + console.log('[debug] parseValue type:', type); + if (type.type == 'defined') { + if (!(type.name in types)) + this.exception("Missing type: " + type.name); + type = types[type.name]; + } + switch (type.name) { + case 'BOOLEAN': + return this.parseValueBoolean(); + case 'OBJECT IDENTIFIER': + return this.parseValueOID(); + default: + console.log('[debug] parseValue unknown:', type); + return 'TODO:value'; + } + }*/ + parseElementType() { + let x = Object.assign({ id: this.parseIdentifier() }, this.parseType()); + // console.log('[debug] parseElementType 1:', x); + if (this.tryToken('OPTIONAL')) + x.optional = true; + if (this.tryToken('DEFAULT')) + x.default = this.parseValue(x.type); + // console.log('[debug] parseElementType 2:', x); + return x; } -}; -Parser.prototype.parseModuleIdentifier = function () { - return { - name: this.parseIdentifier(), - oid: this.parseValueOID(), - }; -}; -Parser.prototype.parseSymbolsImported = function () { - let imports = {}; - do { - let l = []; + parseElementTypeList() { + let v = []; + this.expectToken('{'); do { - l.push(this.parseIdentifier()); + v.push(this.parseElementType()); } while (this.tryToken(',')); - this.expectToken('FROM'); + this.expectToken('}'); + return v; + } + parseAssignment() { + let name = this.parseIdentifier(); + if (this.tryToken('::=')) { // type assignment + // console.log('type name', name); + let type = this.parseType(); + currentMod.types[name] = { name, type }; + return currentMod.types[name]; + } else { // value assignment + // console.log('value name', name); + let type = this.parseType(); + // console.log('[debug] parseAssignment type:', type); + this.expectToken('::='); + let value = this.parseValue(type); + currentMod.values[name] = { name, type, value }; + return currentMod.values[name]; + } + } + parseModuleIdentifier() { + return { + name: this.parseIdentifier(), + oid: this.parseValueOID(), + }; + } + parseSymbolsImported() { + let imports = {}; + do { + let l = []; + do { + l.push(this.parseIdentifier()); + } while (this.tryToken(',')); + this.expectToken('FROM'); + let mod = this.parseModuleIdentifier(); + mod.types = l; + imports[mod.oid] = mod; + } while (this.peekChar() != ';'); + return imports; + } + parseModuleDefinition(file) { let mod = this.parseModuleIdentifier(); - mod.types = l; - imports[mod.oid] = mod; - } while (this.peekChar() != ';'); - return imports; -}; -const reTagDefault = /(AUTOMATIC|IMPLICIT|EXPLICIT) TAGS|/y; -Parser.prototype.parseModuleDefinition = function (file) { - let mod = this.parseModuleIdentifier(); - currentMod = mod; // for deeply nested parsers - mod.source = file; - this.expectToken('DEFINITIONS'); - mod.tagDefault = this.getRegEx('tag default', reTagDefault).split(' ')[0]; - this.expectToken('::='); - this.expectToken('BEGIN'); - //TODO this.tryToken('EXPORTS') - if (this.tryToken('IMPORTS')) { - mod.imports = this.parseSymbolsImported(); - this.expectToken(';'); + currentMod = mod; // for deeply nested parsers + mod.source = file; + this.expectToken('DEFINITIONS'); + mod.tagDefault = this.getRegEx('tag default', reTagDefault).split(' ')[0]; + this.expectToken('::='); + this.expectToken('BEGIN'); + //TODO this.tryToken('EXPORTS') + if (this.tryToken('IMPORTS')) { + mod.imports = this.parseSymbolsImported(); + this.expectToken(';'); + } + mod.values = {}; + mod.types = {}; + while (!this.tryToken('END')) + this.parseAssignment(); + return mod; } - mod.values = {}; - mod.types = {}; - while (!this.tryToken('END')) - this.parseAssignment(); - return mod; -}; +} let s = fs.readFileSync(process.argv[2], 'utf8'); let num = /^Request for Comments: ([0-9]+)/m.exec(s)[1]; @@ -453,10 +457,10 @@ for (let p of patches[0]) if (num in patches) for (let p of patches[num]) s = s.replace(p[0], p[1]); -// fs.writeFileSync('rfc3161_patched.txt', s, 'utf8'); +fs.writeFileSync(process.argv[2].replace(/[.]txt$/, '_patched.txt'), s, 'utf8'); // console.log(s); asn1 = JSON.parse(fs.readFileSync(process.argv[3], 'utf8')); -const reModuleDefinition = /\s[A-Z](?:[-]?[a-zA-Z0-9])*\s*\{[^}]+\}\s*DEFINITIONS/gm; +const reModuleDefinition = /\s[A-Z](?:[-]?[a-zA-Z0-9])*\s*\{[^}]+\}\s*(^--.*|\s*)*DEFINITIONS/gm; let m; while ((m = reModuleDefinition.exec(s))) { new Parser(s, m.index).parseModuleDefinition(process.argv[2]); -- 2.51.2 From 4ddc3cf58df25107aa608a8f139902c00836504c Mon Sep 17 00:00:00 2001 From: Lapo Luchini Date: Tue, 14 Mar 2023 20:42:33 +0000 Subject: [PATCH 05/14] Add PKCS#8 support. --- defs.js | 3 ++- parseRFC.js | 9 ++++++++- updateRFC.sh | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/defs.js b/defs.js index 65ab398..3c3b1cf 100644 --- a/defs.js +++ b/defs.js @@ -109,8 +109,9 @@ class Defs { Defs.RFC = rfc; Defs.commonTypes = [ - ['X.509 certificate', '1.3.6.1.5.5.7.0.18', 'Certificate' ], + [ 'X.509 certificate', '1.3.6.1.5.5.7.0.18', 'Certificate' ], [ 'CMS / PKCS#7 envelope', '1.2.840.113549.1.9.16.0.14', 'ContentInfo' ], + [ 'PKCS#8 encrypted private key', '1.2.840.113549.1.8.1.1', 'EncryptedPrivateKeyInfo' ], ].map(arr => ({ description: arr[0], ...Defs.moduleAndType(rfc[arr[1]], arr[2]) })); return Defs; diff --git a/parseRFC.js b/parseRFC.js index 0225cb0..686458c 100755 --- a/parseRFC.js +++ b/parseRFC.js @@ -18,6 +18,13 @@ const [ /^( +)--.*\n(?:\1 .*\n)+/mg, '' ], [ /addInfoNotAvailable \(17\)/g, '$&,' ], ], + 5208: [ // currently unsupported + [ 'FROM InformationFramework informationFramework', 'FROM InformationFramework {joint-iso-itu-t(2) ds(5) module(1) usefulDefinitions(0) 3}' ], + [ ' {{PrivateKeyAlgorithms}}', '' ], + [ 'Version ::= INTEGER {v1(0)} (v1,...)', 'Version ::= INTEGER {v1(0)}' ], + [ ' {{KeyEncryptionAlgorithms}}', '' ], + [ /\.\.\. -- For local profiles/g, '' ], + ], 5280: [ // currently unsupported [ 'videotex (8) } (0..ub-integer-options)', 'videotex (8) }' ], [ /OBJECT IDENTIFIER \( id-qt-cps \| id-qt-unotice \)/g, 'OBJECT IDENTIFIER' ], @@ -460,7 +467,7 @@ if (num in patches) fs.writeFileSync(process.argv[2].replace(/[.]txt$/, '_patched.txt'), s, 'utf8'); // console.log(s); asn1 = JSON.parse(fs.readFileSync(process.argv[3], 'utf8')); -const reModuleDefinition = /\s[A-Z](?:[-]?[a-zA-Z0-9])*\s*\{[^}]+\}\s*(^--.*|\s*)*DEFINITIONS/gm; +const reModuleDefinition = /\s[A-Z](?:[-]?[a-zA-Z0-9])*\s*\{[^}]+\}\s*(^--.*|\n)*DEFINITIONS/gm; let m; while ((m = reModuleDefinition.exec(s))) { new Parser(s, m.index).parseModuleDefinition(process.argv[2]); diff --git a/updateRFC.sh b/updateRFC.sh index 30305f8..cb9f812 100755 --- a/updateRFC.sh +++ b/updateRFC.sh @@ -1,5 +1,5 @@ #/bin/sh -RFCs="5280 3369 3161" +RFCs="5280 5208 3369 3161" downloadRFC() { URL="https://www.ietf.org/rfc/rfc$1.txt" if [ -x /usr/bin/fetch ]; then -- 2.51.2 From e495fbc88c4f651939667a24baccd032dd247128 Mon Sep 17 00:00:00 2001 From: Lapo Luchini Date: Fri, 24 Mar 2023 18:01:05 +0000 Subject: [PATCH 06/14] Improve support for type recognition. Add `[?]` marker for mismatched types. --- defs.js | 22 +++++++++++++++------- dom.js | 4 ++++ dumpASN1.js | 4 +++- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/defs.js b/defs.js index 3c3b1cf..2a85ef1 100644 --- a/defs.js +++ b/defs.js @@ -22,7 +22,6 @@ const rfc = require('./rfcdef'); function translate(def, tn) { - const id = def?.id; if (def?.type == 'tag' && !def.explicit) // def.type = def.content[0].type; def = def.content[0].type; @@ -41,6 +40,7 @@ function translate(def, tn) { } } } + const id = def?.id; if (id) def = Object.assign({}, def, { id }); return def ?? { type: {} }; @@ -68,12 +68,16 @@ class Defs { static match(value, def, stats = { total: 0, recognized: 0, defs: {} }) { value.def = {}; - let tn = value.typeName(); + let tn = value.typeName().replaceAll('_', ' '); def = translate(def, tn); ++stats.total; if (def?.type) { + // if (def.id || def.name) ++stats.recognized; + if (tn == def.type.name || tn == def.name || def.name == 'ANY') + ++stats.recognized; + else if (def.name) + def = Object.assign({ mismatch: 1 }, def); value.def = def; - if (def.id || def.name) ++stats.recognized; } if (value.sub !== null) { if (def?.type?.type) @@ -85,16 +89,19 @@ class Defs { if (def.typeOf) type = def.content[0]; else { - let tn = subval.typeName(); //.replaceAll('_', ' '); + let tn = subval.typeName().replaceAll('_', ' '); do { type = def.content[j++]; // type = translate(type, tn); if (type?.type?.type) type = type.type; } while (type && ('optional' in type || 'default' in type) && type.name != 'ANY' && type.name != tn); - if (type?.type == 'defined') - stats.defs[type.id] = subval.content().split(/\n/); - else if (type?.definedBy && stats.defs?.[type.definedBy]?.[1]) // hope current OIDs contain the type name (will need to parse from RFC itself) + if (type?.type == 'builtin' || type?.type == 'defined') { + let v = subval.content(); + if (typeof v == 'string') + v = v.split(/\n/); + stats.defs[type.id] = v; + } else if (type?.definedBy && stats.defs?.[type.definedBy]?.[1]) // hope current OIDs contain the type name (will need to parse from RFC itself) type = Defs.searchType(firstUpper(stats.defs[type.definedBy][1])); } } @@ -112,6 +119,7 @@ Defs.commonTypes = [ [ 'X.509 certificate', '1.3.6.1.5.5.7.0.18', 'Certificate' ], [ 'CMS / PKCS#7 envelope', '1.2.840.113549.1.9.16.0.14', 'ContentInfo' ], [ 'PKCS#8 encrypted private key', '1.2.840.113549.1.8.1.1', 'EncryptedPrivateKeyInfo' ], + [ 'PKCS#8 private key', '1.2.840.113549.1.8.1.1', 'PrivateKeyInfo' ], ].map(arr => ({ description: arr[0], ...Defs.moduleAndType(rfc[arr[1]], arr[2]) })); return Defs; diff --git a/dom.js b/dom.js index aef4c74..f065571 100644 --- a/dom.js +++ b/dom.js @@ -74,6 +74,10 @@ class ASN1DOM extends ASN1 { head.appendChild(DOM.tag('span', 'name type', this.def.name)); head.appendChild(DOM.space()); } + if (this.def.mismatch) { + head.appendChild(DOM.tag('span', 'name type', '[?]')); + head.appendChild(DOM.space()); + } } head.appendChild(DOM.text(typeName)); let content = this.content(contentLength); diff --git a/dumpASN1.js b/dumpASN1.js index c67531d..d43872f 100755 --- a/dumpASN1.js +++ b/dumpASN1.js @@ -17,6 +17,7 @@ function print(value, indent) { if (def?.type) { if (def.id) name += colBlue + def.id + colReset; if (typeof def.type == 'object' && def.name) name = (name ? name + ' ' : '') + def.name; + if (def.mismatch) name = (name ? name + ' ' : '') + '[?]'; if (name) name += ' '; } let s = indent + name + colYellow + value.typeName() + colReset + ' @' + value.stream.pos; @@ -57,7 +58,8 @@ const t1 = performance.now(); console.log('Parsed in ' + (t1 - t0).toFixed(2) + ' ms; possible types:'); for (const t of types) console.log((t.match * 100).toFixed(2).padStart(6) + '% ' + t.type.description); -Defs.match(result, types[0].match >= 0.1 ? types[0].type : null); +const stats = Defs.match(result, types[0].match >= 0.1 ? types[0].type : null); +// console.log('Stats:', stats); console.log('Parsed as:', result.def); // const type = searchType(process.argv[2]); // const stats = applyDef(result, type); -- 2.51.2 From 06d8aeb468c405d5d7a1bee99c75815effdfac84 Mon Sep 17 00:00:00 2001 From: Lapo Luchini Date: Fri, 24 Mar 2023 18:15:14 +0000 Subject: [PATCH 07/14] Recover lost matching in some cases.. --- defs.js | 12 ++++++++---- dumpASN1.js | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/defs.js b/defs.js index 2a85ef1..2e485c3 100644 --- a/defs.js +++ b/defs.js @@ -21,10 +21,15 @@ const rfc = require('./rfcdef'); -function translate(def, tn) { +function translate(def, tn, stats) { if (def?.type == 'tag' && !def.explicit) // def.type = def.content[0].type; def = def.content[0].type; + if (def?.definedBy) + try { + // hope current OIDs contain the type name (will need to parse from RFC itself) + def = Defs.searchType(firstUpper(stats.defs[def.definedBy][1])); + } catch (e) {} while (def?.type == 'defined' || def?.type?.type == 'defined') { const name = def?.type?.type ? def.type.name : def.name; def = Object.assign({}, def); @@ -69,7 +74,7 @@ class Defs { static match(value, def, stats = { total: 0, recognized: 0, defs: {} }) { value.def = {}; let tn = value.typeName().replaceAll('_', ' '); - def = translate(def, tn); + def = translate(def, tn, stats); ++stats.total; if (def?.type) { // if (def.id || def.name) ++stats.recognized; @@ -101,8 +106,7 @@ class Defs { if (typeof v == 'string') v = v.split(/\n/); stats.defs[type.id] = v; - } else if (type?.definedBy && stats.defs?.[type.definedBy]?.[1]) // hope current OIDs contain the type name (will need to parse from RFC itself) - type = Defs.searchType(firstUpper(stats.defs[type.definedBy][1])); + } } } Defs.match(subval, type, stats); diff --git a/dumpASN1.js b/dumpASN1.js index d43872f..c937469 100755 --- a/dumpASN1.js +++ b/dumpASN1.js @@ -58,7 +58,7 @@ const t1 = performance.now(); console.log('Parsed in ' + (t1 - t0).toFixed(2) + ' ms; possible types:'); for (const t of types) console.log((t.match * 100).toFixed(2).padStart(6) + '% ' + t.type.description); -const stats = Defs.match(result, types[0].match >= 0.1 ? types[0].type : null); +const stats = Defs.match(result, types[0].type); // console.log('Stats:', stats); console.log('Parsed as:', result.def); // const type = searchType(process.argv[2]); -- 2.51.2 From ce3dc3d2bde4145203489cb67d1bebc6ca334999 Mon Sep 17 00:00:00 2001 From: Lapo Luchini Date: Fri, 4 Aug 2023 06:49:14 +0000 Subject: [PATCH 08/14] Check that `type` is an object before accessing to members. Closes #69 on GitHub. --- defs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/defs.js b/defs.js index 3c3b1cf..96bda2c 100644 --- a/defs.js +++ b/defs.js @@ -91,7 +91,7 @@ class Defs { // type = translate(type, tn); if (type?.type?.type) type = type.type; - } while (type && ('optional' in type || 'default' in type) && type.name != 'ANY' && type.name != tn); + } while (type && typeof type == 'object' && ('optional' in type || 'default' in type) && type.name != 'ANY' && type.name != tn); if (type?.type == 'defined') stats.defs[type.id] = subval.content().split(/\n/); else if (type?.definedBy && stats.defs?.[type.definedBy]?.[1]) // hope current OIDs contain the type name (will need to parse from RFC itself) -- 2.51.2 From 9948fb74b9c5baafd379e367ac9bbb3b3276f4d3 Mon Sep 17 00:00:00 2001 From: Lapo Luchini Date: Fri, 4 Aug 2023 07:02:56 +0000 Subject: [PATCH 09/14] Fix GitHub action. --- .github/workflows/node.js.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index dc44dfe..0bb55ff 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -22,4 +22,4 @@ jobs: node-version: ${{ matrix.node-version }} - run: npm test all - run: npm run lint - if: ${{ matrix.node-version == latest }} + if: matrix.node-version == 'latest' -- 2.51.2 From 53a2cf9c7d0532a692e507e8d8903bcbe65d788e Mon Sep 17 00:00:00 2001 From: Lapo Luchini Date: Tue, 29 Aug 2023 20:49:37 +0000 Subject: [PATCH 10/14] Sync with latest changes from Peter Gutmann's website. Last-Modified: Thu, 29 Jun 2023 10:25:59 GMT --- oids.js | 52 ++++++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/oids.js b/oids.js index 2310c5c..06fb616 100644 --- a/oids.js +++ b/oids.js @@ -1352,10 +1352,10 @@ return { "1.3.6.1.4.1.8301.3.5.1": { "d": "validityModelChain", "c": "TU Darmstadt ValidityModel" }, "1.3.6.1.4.1.8301.3.5.2": { "d": "validityModelShell", "c": "ValidityModel" }, "1.3.6.1.4.1.8231.1": { "d": "rolUnicoNacional", "c": "Chilean Government national unique roll number" }, -"1.3.6.1.4.1.11129.2.4.2": { "d": "googleEV", "c": "Google" }, -"1.3.6.1.4.1.11129.2.4.3": { "d": "googleCertificateTransparency", "c": "Google" }, -"1.3.6.1.4.1.11129.2.4.4": { "d": "googlePrecertificate", "c": "Google" }, -"1.3.6.1.4.1.11129.2.4.5": { "d": "googleOcspStapling", "c": "Google" }, +"1.3.6.1.4.1.11129.2.4.2": { "d": "googleSignedCertificateTimestamp", "c": "Google Certificate Transparency" }, +"1.3.6.1.4.1.11129.2.4.3": { "d": "googlePrecertificatePoison", "c": "Google Certificate Transparency" }, +"1.3.6.1.4.1.11129.2.4.4": { "d": "googlePrecertificateCA", "c": "Google Certificate Transparency" }, +"1.3.6.1.4.1.11129.2.4.5": { "d": "googleOcspSignedCertificateTimestamp", "c": "Google Certificate Transparency" }, "1.3.6.1.4.1.11591": { "d": "gnu", "c": "GNU Project (see https://www.gnupg.org/oids.html)" }, "1.3.6.1.4.1.11591.1": { "d": "gnuRadius", "c": "GNU Radius" }, "1.3.6.1.4.1.11591.2.2.1": { "d": "gpgX509StandaloneCert", "c": "Cert is intentionally self-signed." }, @@ -2657,26 +2657,30 @@ return { "2.23.43.1.4.6": { "d": "wTLS-ECC-curve6", "c": "WAP WTLS" }, "2.23.43.1.4.8": { "d": "wTLS-ECC-curve8", "c": "WAP WTLS" }, "2.23.43.1.4.9": { "d": "wTLS-ECC-curve9", "c": "WAP WTLS" }, -"2.23.133": { "d": "tCPA", "c": "TCPA" }, -"2.23.133.1": { "d": "tcpaSpecVersion", "c": "TCPA" }, -"2.23.133.2": { "d": "tcpaAttribute", "c": "TCPA" }, -"2.23.133.2.1": { "d": "tcpaTpmManufacturer", "c": "TCPA Attribute" }, -"2.23.133.2.2": { "d": "tcpaTpmModel", "c": "TCPA Attribute" }, -"2.23.133.2.3": { "d": "tcpaTpmVersion", "c": "TCPA Attribute" }, -"2.23.133.2.4": { "d": "tcpaPlatformManufacturer", "c": "TCPA Attribute" }, -"2.23.133.2.5": { "d": "tcpaPlatformModel", "c": "TCPA Attribute" }, -"2.23.133.2.6": { "d": "tcpaPlatformVersion", "c": "TCPA Attribute" }, -"2.23.133.2.7": { "d": "tcpaComponentManufacturer", "c": "TCPA Attribute" }, -"2.23.133.2.8": { "d": "tcpaComponentModel", "c": "TCPA Attribute" }, -"2.23.133.2.9": { "d": "tcpaComponentVersion", "c": "TCPA Attribute" }, -"2.23.133.2.10": { "d": "tcpaSecurityQualities", "c": "TCPA Attribute" }, -"2.23.133.2.11": { "d": "tcpaTpmProtectionProfile", "c": "TCPA Attribute" }, -"2.23.133.2.12": { "d": "tcpaTpmSecurityTarget", "c": "TCPA Attribute" }, -"2.23.133.2.13": { "d": "tcpaFoundationProtectionProfile", "c": "TCPA Attribute" }, -"2.23.133.2.14": { "d": "tcpaFoundationSecurityTarget", "c": "TCPA Attribute" }, -"2.23.133.2.15": { "d": "tcpaTpmIdLabel", "c": "TCPA Attribute" }, -"2.23.133.3": { "d": "tcpaProtocol", "c": "TCPA" }, -"2.23.133.3.1": { "d": "tcpaPrttTpmIdProtocol", "c": "TCPA Protocol" }, +"2.23.133": { "d": "tCPA", "c": "TCPA/TCG" }, +"2.23.133.1": { "d": "tcgSpecVersion", "c": "TCPA/TCG" }, +"2.23.133.2": { "d": "tcgAttribute", "c": "TCPA/TCG" }, +"2.23.133.2.1": { "d": "tcgTpmManufacturer", "c": "TCPA/TCG Attribute" }, +"2.23.133.2.2": { "d": "tcgTpmModel", "c": "TCPA/TCG Attribute" }, +"2.23.133.2.3": { "d": "tcgTpmVersion", "c": "TCPA/TCG Attribute" }, +"2.23.133.2.4": { "d": "tcgPlatformManufacturer", "c": "TCPA/TCG Attribute" }, +"2.23.133.2.5": { "d": "tcgPlatformModel", "c": "TCPA/TCG Attribute" }, +"2.23.133.2.6": { "d": "tcgPlatformVersion", "c": "TCPA/TCG Attribute" }, +"2.23.133.2.7": { "d": "tcgComponentManufacturer", "c": "TCPA/TCG Attribute" }, +"2.23.133.2.8": { "d": "tcgComponentModel", "c": "TCPA/TCG Attribute" }, +"2.23.133.2.9": { "d": "tcgComponentVersion", "c": "TCPA/TCG Attribute" }, +"2.23.133.2.10": { "d": "tcgSecurityQualities", "c": "TCPA/TCG Attribute" }, +"2.23.133.2.11": { "d": "tcgTpmProtectionProfile", "c": "TCPA/TCG Attribute" }, +"2.23.133.2.12": { "d": "tcgTpmSecurityTarget", "c": "TCPA/TCG Attribute" }, +"2.23.133.2.13": { "d": "tcgFoundationProtectionProfile", "c": "TCPA/TCG Attribute" }, +"2.23.133.2.14": { "d": "tcgFoundationSecurityTarget", "c": "TCPA/TCG Attribute" }, +"2.23.133.2.15": { "d": "tcgTpmIdLabel", "c": "TCPA/TCG Attribute" }, +"2.23.133.2.16": { "d": "tcgTpmSpecification", "c": "TCPA/TCG Attribute" }, +"2.23.133.2.18": { "d": "tcgTpmSecurityAssertions", "c": "TCPA/TCG Attribute" }, +"2.23.133.3": { "d": "tcgProtocol", "c": "TCPA/TCG" }, +"2.23.133.3.1": { "d": "tcgPrttTpmIdProtocol", "c": "TCPA/TCG Protocol" }, +"2.23.133.8.1": { "d": "tcgEKCertificate", "c": "TCPA/TCG Key Usage" }, +"2.23.133.10.1.1.1": { "d": "tcgObject", "c": "TCPA/TCG Object" }, "2.23.134.1.4.2.1": { "d": "postSignumRootQCA", "c": "PostSignum CA" }, "2.23.134.1.2.2.3": { "d": "postSignumPublicCA", "c": "PostSignum CA" }, "2.23.134.1.2.1.8.210": { "d": "postSignumCommercialServerPolicy", "c": "PostSignum CA" }, -- 2.51.2 From 3a2ce63610cf4cf40ce93c55db6fc105af64502b Mon Sep 17 00:00:00 2001 From: Lapo Luchini Date: Tue, 29 Aug 2023 20:55:10 +0000 Subject: [PATCH 11/14] Do not fail on missing types. (un-break example `letsencrypt-x3.cer`) --- defs.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/defs.js b/defs.js index 96bda2c..07f8500 100644 --- a/defs.js +++ b/defs.js @@ -95,7 +95,9 @@ class Defs { if (type?.type == 'defined') stats.defs[type.id] = subval.content().split(/\n/); else if (type?.definedBy && stats.defs?.[type.definedBy]?.[1]) // hope current OIDs contain the type name (will need to parse from RFC itself) - type = Defs.searchType(firstUpper(stats.defs[type.definedBy][1])); + try { + type = Defs.searchType(firstUpper(stats.defs[type.definedBy][1])); + } catch (e) {} } } Defs.match(subval, type, stats); -- 2.51.2 From c9324b2c17c29d7018e77bb867f02bcddb28becf Mon Sep 17 00:00:00 2001 From: Lapo Luchini Date: Tue, 29 Aug 2023 20:55:57 +0000 Subject: [PATCH 12/14] Add type support for PKCS#10 certification requests. --- README.md | 2 +- defs.js | 1 + package.json | 2 +- parseRFC.js | 13 +++++++++++++ updateRFC.sh | 2 +- 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 34a6c44..de091f8 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ require([ ISC license ----------- -ASN.1 JavaScript decoder Copyright (c) 2008-2022 Lapo Luchini +ASN.1 JavaScript decoder Copyright (c) 2008-2023 Lapo Luchini 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. diff --git a/defs.js b/defs.js index 07f8500..bc5d5d6 100644 --- a/defs.js +++ b/defs.js @@ -114,6 +114,7 @@ Defs.commonTypes = [ [ 'X.509 certificate', '1.3.6.1.5.5.7.0.18', 'Certificate' ], [ 'CMS / PKCS#7 envelope', '1.2.840.113549.1.9.16.0.14', 'ContentInfo' ], [ 'PKCS#8 encrypted private key', '1.2.840.113549.1.8.1.1', 'EncryptedPrivateKeyInfo' ], + [ 'PKCS#10 certification request', '1.2.840.113549.1.10.1.1', 'CertificationRequest' ], ].map(arr => ({ description: arr[0], ...Defs.moduleAndType(rfc[arr[1]], arr[2]) })); return Defs; diff --git a/package.json b/package.json index 1e8db15..4a0bb0a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lapo/asn1js", - "version": "1.2.4", + "version": "1.3.0", "description": "Generic ASN.1 parser/decoder that can decode any valid ASN.1 DER or BER structures.", "main": "asn1.js", "repository": { diff --git a/parseRFC.js b/parseRFC.js index 686458c..e39e392 100755 --- a/parseRFC.js +++ b/parseRFC.js @@ -13,6 +13,19 @@ const [ /SIGNED \{ (SEQUENCE \{[^}]+\})\s*\}/g, 'SEQUENCE { toBeSigned $1, algorithm AlgorithmIdentifier, signature BIT STRING }' ], [ /EXTENSION\.&[^,]+/g, 'OBJECT IDENTIFIER'], ], + 2986: [ // currently unsupported + [ /FROM (InformationFramework|AuthenticationFramework) [a-zA-Z]+/g, 'FROM $1 {joint-iso-itu-t(2) ds(5) module(1) usefulDefinitions(0) 3}' ], + [ /[(]v1,[^)]+[)]/g, '' ], + [ /[{][{][^}]+[}][}]/g, '' ], + [ 'SubjectPublicKeyInfo {ALGORITHM: IOSet}', 'SubjectPublicKeyInfo' ], + [ /PKInfoAlgorithms ALGORITHM ::=[^}]+[}]/g, '' ], + [ /(Attributes?) [{] ATTRIBUTE:IOSet [}]/g, '$1' ], + [ /CRIAttributes +ATTRIBUTE +::=[^}]+[}]/g, '' ], + [ /[A-Z]+[.]&id[(][{]IOSet[}][)]/g, 'OBJECT IDENTIFIER' ], + [ /[A-Z]+[.]&Type[(][{]IOSet[}][{]@[a-z]+[}][)]/g, 'ANY' ], + [ /(AlgorithmIdentifier) [{]ALGORITHM:IOSet [}]/g, '$1' ], + [ /SignatureAlgorithms ALGORITHM ::=[^}]+[}]/g, '' ], + ], 3161: [ // actual syntax errors [ /--.*}/g, '}' ], [ /^( +)--.*\n(?:\1 .*\n)+/mg, '' ], diff --git a/updateRFC.sh b/updateRFC.sh index cb9f812..bf3f8f7 100755 --- a/updateRFC.sh +++ b/updateRFC.sh @@ -1,5 +1,5 @@ #/bin/sh -RFCs="5280 5208 3369 3161" +RFCs="5280 5208 3369 3161 2986" downloadRFC() { URL="https://www.ietf.org/rfc/rfc$1.txt" if [ -x /usr/bin/fetch ]; then -- 2.51.2 From b7d379bb54800bfa42cc121b8cdff2ab94acacda Mon Sep 17 00:00:00 2001 From: Lapo Luchini Date: Tue, 29 Aug 2023 21:34:46 +0000 Subject: [PATCH 13/14] Add PKCS#10 example. --- examples/pkcs10.pem | 9 +++++++++ index.html | 1 + 2 files changed, 10 insertions(+) create mode 100644 examples/pkcs10.pem diff --git a/examples/pkcs10.pem b/examples/pkcs10.pem new file mode 100644 index 0000000..c51b61b --- /dev/null +++ b/examples/pkcs10.pem @@ -0,0 +1,9 @@ +PKCS#10 certification request based on Daniel J. Bernstein’s Curve25519. +$ openssl req -nodes -newkey ed25519 -keyout /dev/null -out pkcs10.pem -days 36500 -subj '/CN=test' -addext 'subjectAltName=otherName:msUPN;UTF8:address@domain.test' +-----BEGIN CERTIFICATE REQUEST----- +MIHQMIGDAgEAMA8xDTALBgNVBAMMBHRlc3QwKjAFBgMrZXADIQD7Fua9ZF+wPXVd +DCBwQr+Aqny6OFvs25wZ/P4LyVsYmKBBMD8GCSqGSIb3DQEJDjEyMDAwLgYDVR0R +BCcwJaAjBgorBgEEAYI3FAIDoBUME2FkZHJlc3NAZG9tYWluLnRlc3QwBQYDK2Vw +A0EAUp5FenHF1rZzRGU+7wiF+/D1bfyDRF0dzWz2sl44nltu8iLjHO3aIfOTYWpq +ZlaDg1Bq3L7Fcb7If4yZAsE5Cw== +-----END CERTIFICATE REQUEST----- diff --git a/index.html b/index.html index 524f19a..588a911 100644 --- a/index.html +++ b/index.html @@ -44,6 +44,7 @@ +
-- 2.51.2 From e66a38d2f9d06179c9e1eeaeb52fc0606d44bff5 Mon Sep 17 00:00:00 2001 From: Lapo Luchini Date: Wed, 30 Aug 2023 12:16:46 +0000 Subject: [PATCH 14/14] Fix lint error. --- dumpASN1.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dumpASN1.js b/dumpASN1.js index c937469..dd02546 100755 --- a/dumpASN1.js +++ b/dumpASN1.js @@ -58,7 +58,8 @@ const t1 = performance.now(); console.log('Parsed in ' + (t1 - t0).toFixed(2) + ' ms; possible types:'); for (const t of types) console.log((t.match * 100).toFixed(2).padStart(6) + '% ' + t.type.description); -const stats = Defs.match(result, types[0].type); +Defs.match(result, types[0].type); +// const stats = Defs.match(result, types[0].type); // console.log('Stats:', stats); console.log('Parsed as:', result.def); // const type = searchType(process.argv[2]);