diff --git a/asn1.js b/asn1.js index 87376c8..bbcc3c0 100644 --- a/asn1.js +++ b/asn1.js @@ -72,7 +72,7 @@ class Stream { if (pos === undefined) pos = this.pos++; if (pos >= this.enc.length) - throw 'Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length; + throw new Error('Requesting byte offset ' + pos + ' on a stream of length ' + this.enc.length); return (typeof this.enc == 'string') ? this.enc.charCodeAt(pos) : this.enc[pos]; } hexByte(b) { @@ -190,7 +190,7 @@ class Stream { let s = this.parseStringISO(start, end).str, m = (shortYear ? reTimeS : reTimeL).exec(s); if (!m) - return 'Unrecognized time: ' + s; + throw new Error('Unrecognized time: ' + s); if (shortYear) { // to avoid querying the timer, use the fixed range [1970, 2069] // it will conform with ITU X.400 [-10, +40] sliding window until 2030 @@ -245,7 +245,7 @@ class Stream { parseBitString(start, end, maxLength) { let unusedBits = this.get(start); if (unusedBits > 7) - throw 'Invalid BitString with unusedBits=' + unusedBits; + throw new Error('Invalid BitString with unusedBits=' + unusedBits); let lenBit = ((end - start - 1) << 3) - unusedBits, s = ''; for (let i = start + 1; i < end; ++i) { @@ -368,7 +368,7 @@ class ASN1Tag { export class ASN1 { constructor(stream, header, length, tag, tagLen, sub) { - if (!(tag instanceof ASN1Tag)) throw 'Invalid tag value.'; + if (!(tag instanceof ASN1Tag)) throw new Error('Invalid tag value.'); this.stream = stream; this.header = header; this.length = length; @@ -531,7 +531,7 @@ export class ASN1 { if (len === 0) // long form with length 0 is a special case return null; // undefined length if (len > 6) // no reason to use Int10, as it would be a huge buffer anyways - throw 'Length over 48 bits not supported at position ' + (stream.pos - 1); + throw new Error('Length over 48 bits not supported at position ' + (stream.pos - 1)); buf = 0; for (let i = 0; i < len; ++i) buf = (buf * 256) + stream.get(); @@ -539,7 +539,7 @@ export class ASN1 { } static decode(stream, offset, type = ASN1) { if (!(type == ASN1 || type.prototype instanceof ASN1)) - throw 'Must pass a class that extends ASN1'; + throw new Error('Must pass a class that extends ASN1'); if (!(stream instanceof Stream)) stream = new Stream(stream, offset || 0); let streamStart = new Stream(stream), @@ -555,11 +555,11 @@ export class ASN1 { // definite length let end = start + len; if (end > stream.enc.length) - throw 'Container at offset ' + start + ' has a length of ' + len + ', which is past the end of the stream'; + throw new Error('Container at offset ' + start + ' has a length of ' + len + ', which is past the end of the stream'); while (stream.pos < end) sub[sub.length] = type.decode(stream); if (stream.pos != end) - throw 'Content size is not correct for container at offset ' + start; + throw new Error('Content size is not correct for container at offset ' + start); } else { // undefined length try { @@ -571,7 +571,7 @@ export class ASN1 { } len = start - stream.pos; // undefined lengths are represented as negative values } catch (e) { - throw 'Exception while decoding undefined length content at offset ' + start + ': ' + e; + throw new Error('Exception while decoding undefined length content at offset ' + start + ': ' + e); } } }; @@ -583,11 +583,17 @@ export class ASN1 { try { if (tag.tagNumber == 0x03) if (stream.get() != 0) - throw 'BIT STRINGs with unused bits cannot encapsulate.'; + throw new Error('BIT STRINGs with unused bits cannot encapsulate.'); getSub(); - for (let i = 0; i < sub.length; ++i) - if (sub[i].tag.isEOC()) - throw 'EOC is not supposed to be actual content.'; + for (let s of sub) { + if (s.tag.isEOC()) + throw new Error('EOC is not supposed to be actual content.'); + try { + s.content(); + } catch (e) { + throw new Error('Unable to parse content: ' + e); + } + } } catch (e) { // but silently ignore when they don't sub = null; @@ -596,7 +602,7 @@ export class ASN1 { } if (sub === null) { if (len === null) - throw "We can't skip over an invalid tag with undefined length at offset " + start; + throw new Error("We can't skip over an invalid tag with undefined length at offset " + start); stream.pos = start + Math.abs(len); } return new type(streamStart, header, len, tag, tagLen, sub); diff --git a/dom.js b/dom.js index 26f3260..8ccf4ea 100644 --- a/dom.js +++ b/dom.js @@ -75,7 +75,12 @@ export class ASN1DOM extends ASN1 { } } head.appendChild(DOM.text(typeName)); - let content = this.content(contentLength); + let content; + try { + content = this.content(contentLength); + } catch (e) { + content = 'Cannot decode: ' + e; + } let oid; if (content !== null) { let preview = DOM.tag('span', 'preview'), diff --git a/test.js b/test.js index cd20b71..884125a 100755 --- a/test.js +++ b/test.js @@ -36,7 +36,7 @@ const tests = [ ['170D3931303530363233343534305A', '1991-05-06 23:45:40 UTC', 'ntop, utc time: UTC'], ['17113931303530363136343534302D30373030', '1991-05-06 16:45:40 UTC-07:00', 'ntop, utc time: PDT'], // inspired by http://luca.ntop.org/Teaching/Appunti/asn1.html - ['0304086E5DC0', 'Exception:\nInvalid BitString with unusedBits=8', 'bit string: invalid unusedBits'], + ['0304086E5DC0', 'Exception:\nError: Invalid BitString with unusedBits=8', 'bit string: invalid unusedBits'], // http://msdn.microsoft.com/en-us/library/windows/desktop/aa379076(v=vs.85).aspx ['30820319308202820201003023310F300D0603550403130654657374434E3110300E060355040A1307546573744F726730819F300D06092A864886F70D010101050003818D00308189028181008FE2412A08E851A88CB3E853E7D54950B3278A2BCBEAB54273EA0257CC6533EE882061A11756C12418E3A808D3BED931F3370B94B8CC43080B7024F79CB18D5DD66D82D0540984F89F970175059C89D4D5C91EC913D72A6B309119D6D442E0C49D7C9271E1B22F5C8DEEF0F1171ED25F315BB19CBC2055BF3A37424575DC90650203010001A08201B4301A060A2B0601040182370D0203310C160A362E302E353336312E323042060A2B0601040182370D0201313430321E260043006500720074006900660069006300610074006500540065006D0070006C0061007400651E080055007300650072305706092B0601040182371514314A30480201090C237669636833642E6A646F6D6373632E6E74746573742E6D6963726F736F66742E636F6D0C154A444F4D4353435C61646D696E6973747261746F720C07636572747265713074060A2B0601040182370D0202316630640201011E5C004D006900630072006F0073006F0066007400200045006E00680061006E006300650064002000430072007900700074006F0067007200610070006800690063002000500072006F00760069006400650072002000760031002E003003010030818206092A864886F70D01090E31753073301706092B0601040182371402040A1E08005500730065007230290603551D2504223020060A2B0601040182370A030406082B0601050507030406082B06010505070302300E0603551D0F0101FF0404030205A0301D0603551D0E041604143C0F73DAF8EF41D83AEABE922A5D2C966A7B9454300D06092A864886F70D01010505000381810047EB995ADF9E700DFBA73132C15F5C24C2E0BFC624AF15660EB86A2EAB2BC4971FE3CBDC63A525ECC7B428616636A1311BBFDDD0FCBF1794901DE55EC7115EC9559FEBA33E14C799A6CBBAA1460F39D444C4C84B760E205D6DA9349ED4D58742EB2426511490B40F065E5288327A9520A0FDF7E57D60DD72689BF57B058F6D1E', '(3 elem)', 'PKCS#10 request'], @@ -86,6 +86,8 @@ const tests = [ ['0420041EE4E3B7ED350CC24D034E436D9A1CB15BB1E328D37062FB82E84618AB0A3C', '(32 byte)\n041EE4E3B7ED350CC24D034E436D9A1CB15BB1E328D37062FB82E84618AB0A3C', 'Do not mix encapsulated and structured octet strings'], // GitHub issue #47 ['181531393835313130363231303632372E332D31323334', '1985-11-06 21:06:27.3 UTC-12:34', 'UTC offsets with minutes'], // GitHub issue #54 ['181331393835313130363231303632372E332B3134', '1985-11-06 21:06:27.3 UTC+14:00', 'UTC offset +13 and +14'], // GitHub issue #54 + ['032100171E83C1B251803F86DD01E9CFA886BE89A7316D8372649AC2231EC669F81A84', n => { if (n.sub != null) return 'Should not decode content: ' + n.sub[0].content(); }, 'Key that resembles an UTCTime'], // GitHub issue #79 + ['171E83C1B251803F86DD01E9CFA886BE89A7316D8372649AC2231EC669F81A84', /^Exception:\nError: Unrecognized time: /, 'Invalid UTCTime'], // GitHub issue #79 ]; let @@ -95,24 +97,28 @@ let tests.forEach(function (t) { const input = t[0], expected = t[1], - comment = t[2], - errorReason = t[3]; + comment = t[2]; let result; try { - result = ASN1.decode(Hex.decode(input)).content(); + let node = ASN1.decode(Hex.decode(input)); + if (typeof expected == 'function') + result = expected(node); + else + result = node.content(); //TODO: check structure, not only first level content } catch (e) { result = 'Exception:\n' + e; } + if (expected instanceof RegExp) + result = expected.test(result) ? null : 'does not match'; ++run; - if (result == expected) { + if (!result || result == expected) { if (all) console.log('\x1B[1m\x1B[32mOK \x1B[39m\x1B[22m ' + comment); - } else if (errorReason) { - ++expErr; - console.log('\x1B[1m\x1B[33mEXP\x1B[39m\x1B[22m ' + comment + ' (' + errorReason + ')' + '\n' + result); } else { ++error; - console.log('\x1B[1m\x1B[31mERR\x1B[39m\x1B[22m ' + comment + '\n' + result); + console.log('\x1B[1m\x1B[31mERR\x1B[39m\x1B[22m ' + comment); + console.log(' \x1B[1m\x1B[34mEXP\x1B[39m\x1B[22m ' + expected.toString().replace(/\n/g, '\n ')); + console.log(' \x1B[1m\x1B[33mGOT\x1B[39m\x1B[22m ' + result.replace(/\n/g, '\n ')); } }); console.log(run + ' tested, ' + expErr + ' expected, ' + error + ' errors.');