JavaScript generic ASN.1 parser (mirror)

Compare changes

Choose any two refs to compare.

+137 -67
+3 -1
index.css
··· 1 html { 2 --main-bg-color: #C0C0C0; 3 --main-text-color: #000000; 4 --headline-text-color: #8be9fd; 5 --button-border-color: #767676; 6 --button-bg-color: #efefef; ··· 35 html[data-theme="dark"] { 36 --main-bg-color: #0d1116; 37 --main-text-color: #f8f8f2; 38 --headline-text-color: #8be9fd; 39 --button-border-color: #505050; 40 --button-bg-color: #303030; ··· 186 color: var(--preview-border-color); 187 } 188 .name.id { 189 - color: var(--main-text-color); 190 } 191 .value { 192 display: none;
··· 1 html { 2 --main-bg-color: #C0C0C0; 3 --main-text-color: #000000; 4 + --id-text-color: #7d5900; 5 --headline-text-color: #8be9fd; 6 --button-border-color: #767676; 7 --button-bg-color: #efefef; ··· 36 html[data-theme="dark"] { 37 --main-bg-color: #0d1116; 38 --main-text-color: #f8f8f2; 39 + --id-text-color: #9d7c2d; 40 --headline-text-color: #8be9fd; 41 --button-border-color: #505050; 42 --button-bg-color: #303030; ··· 188 color: var(--preview-border-color); 189 } 190 .name.id { 191 + color: var(--id-text-color); 192 } 193 .value { 194 display: none;
+13 -7
int10.js
··· 13 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 16 - let max = 10000000000000; // biggest 10^n integer that can still fit 2^53 when multiplied by 256 17 18 export class Int10 { 19 /** ··· 26 27 /** 28 * Multiply value by m and add c. 29 - * @param {number} m - multiplier, must be < =256 30 - * @param {number} c - value to add 31 */ 32 mulAdd(m, c) { 33 // assert(m <= 256) 34 let b = this.buf, 35 l = b.length, 36 i, t; ··· 71 72 /** 73 * Convert to decimal string representation. 74 - * @param {*} base - optional value, only value accepted is 10 75 */ 76 - toString(base) { 77 - if ((base || 10) != 10) 78 - throw 'only base 10 is supported'; 79 let b = this.buf, 80 s = b[b.length - 1].toString(); 81 for (let i = b.length - 2; i >= 0; --i) ··· 86 /** 87 * Convert to Number value representation. 88 * Will probably overflow 2^53 and thus become approximate. 89 */ 90 valueOf() { 91 let b = this.buf, ··· 97 98 /** 99 * Return value as a simple Number (if it is <= 10000000000000), or return this. 100 */ 101 simplify() { 102 let b = this.buf;
··· 13 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 16 + /** Biggest 10^n integer that can still fit 2^53 when multiplied by 256. */ 17 + const max = 10000000000000; 18 19 export class Int10 { 20 /** ··· 27 28 /** 29 * Multiply value by m and add c. 30 + * @param {number} m - multiplier, must be 0<m<=256 31 + * @param {number} c - value to add, must be c>=0 32 */ 33 mulAdd(m, c) { 34 + // assert(m > 0) 35 // assert(m <= 256) 36 + // assert(c >= 0) 37 let b = this.buf, 38 l = b.length, 39 i, t; ··· 74 75 /** 76 * Convert to decimal string representation. 77 + * @param {number} [base=10] - optional value, only value accepted is 10 78 + * @returns {string} The decimal string representation. 79 */ 80 + toString(base = 10) { 81 + if (base != 10) 82 + throw new Error('only base 10 is supported'); 83 let b = this.buf, 84 s = b[b.length - 1].toString(); 85 for (let i = b.length - 2; i >= 0; --i) ··· 90 /** 91 * Convert to Number value representation. 92 * Will probably overflow 2^53 and thus become approximate. 93 + * @returns {number} The numeric value. 94 */ 95 valueOf() { 96 let b = this.buf, ··· 102 103 /** 104 * Return value as a simple Number (if it is <= 10000000000000), or return this. 105 + * @returns {number | Int10} The simplified value. 106 */ 107 simplify() { 108 let b = this.buf;
+121 -59
test.js
··· 3 import { ASN1, Stream } from './asn1.js'; 4 import { Hex } from './hex.js'; 5 import { Base64 } from './base64.js'; 6 7 - const 8 - all = (process.argv[2] == 'all'); 9 10 - const tests = [ 11 // RSA Laboratories technical notes from https://luca.ntop.org/Teaching/Appunti/asn1.html 12 ['0304066E5DC0', '(18 bit)\n011011100101110111', 'ntop, bit string: DER encoding'], 13 ['0304066E5DE0', '(18 bit)\n011011100101110111', 'ntop, bit string: padded with "100000"'], ··· 89 ['181331393835313130363231303632372E332B3134', '1985-11-06 21:06:27.3 UTC+14:00', 'UTC offset +13 and +14'], // GitHub issue #54 90 ['032100171E83C1B251803F86DD01E9CFA886BE89A7316D8372649AC2231EC669F81A84', n => { if (n.sub != null) return 'Should not decode content: ' + n.sub[0].content(); }, 'Key that resembles an UTCTime'], // GitHub issue #79 91 ['171E83C1B251803F86DD01E9CFA886BE89A7316D8372649AC2231EC669F81A84', /^Exception:\nError: Unrecognized time: /, 'Invalid UTCTime'], // GitHub issue #79 92 - ]; 93 94 - const testsB64 = [ 95 'AA==', 96 'ABA=', 97 'ABCD', ··· 99 'ABCDEFE=', 100 'ABCDEFGH', 101 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQR\nSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456w==', 102 - ]; 103 104 - function check(result, expected, comment) { 105 - if (!result || result == expected) { 106 - if (all) console.log('\x1B[1m\x1B[32mOK \x1B[39m\x1B[22m ' + comment); 107 - return true; 108 - } else { 109 - console.log('\x1B[1m\x1B[31mERR\x1B[39m\x1B[22m ' + comment); 110 - console.log(' \x1B[1m\x1B[34mEXP\x1B[39m\x1B[22m ' + expected.toString().replace(/\n/g, '\n ')); 111 - console.log(' \x1B[1m\x1B[33mGOT\x1B[39m\x1B[22m ' + result.replace(/\n/g, '\n ')); 112 - return false; 113 - } 114 - } 115 116 - let 117 - run = 0, 118 - expErr = 0, 119 - error = 0; 120 - for (let t of tests) { 121 - const input = t[0], 122 - expected = t[1], 123 - comment = t[2]; 124 - let result; 125 - try { 126 - let node = ASN1.decode(Hex.decode(input)); 127 - if (typeof expected == 'function') 128 - result = expected(node); 129 - else 130 - result = node.content(); 131 - //TODO: check structure, not only first level content 132 - } catch (e) { 133 - result = 'Exception:\n' + e; 134 - } 135 - if (expected instanceof RegExp) 136 - result = expected.test(result) ? null : 'does not match'; 137 - ++run; 138 - if (!check(result, expected, comment)) 139 - ++error; 140 - } 141 - for (let t of testsB64) { 142 - let bin = Base64.decode(t); 143 - let url = new Stream(bin, 0).b64Dump(0, bin.length); 144 - ++run; 145 - if (!check(url, t.replace(/\n/g, '').replace(/=*$/g, ''), 'Base64url: ' + bin.length + ' bytes')) 146 - ++error; 147 - let pretty = Base64.pretty(url); 148 - ++run; 149 - if (!check(pretty, t, 'Base64pretty: ' + bin.length + ' bytes')) 150 - ++error; 151 - let std = new Stream(bin, 0).b64Dump(0, bin.length, 'std'); 152 - ++run; 153 - if (!check(std, t.replace(/\n/g, ''), 'Base64: ' + bin.length + ' bytes')) 154 - ++error; 155 - } 156 - console.log(run + ' tested, ' + expErr + ' expected, ' + error + ' errors.'); 157 - process.exit(error ? 1 : 0);
··· 3 import { ASN1, Stream } from './asn1.js'; 4 import { Hex } from './hex.js'; 5 import { Base64 } from './base64.js'; 6 + import { Int10 } from './int10.js'; 7 8 + const all = (process.argv[2] == 'all'); 9 + 10 + /** @type {Array<Tests>} */ 11 + const tests = []; 12 + 13 + const stats = { 14 + run: 0, 15 + error: 0, 16 + }; 17 + 18 + /** 19 + * A class for managing and executing tests. 20 + */ 21 + class Tests { 22 + /** 23 + * An array to store test data. 24 + * @type {Array<unknown>} 25 + */ 26 + data; 27 + 28 + /** 29 + * Checks a row of test data. 30 + * @param {Function} t - How to test a row of data. 31 + */ 32 + checkRow; 33 34 + /** 35 + * Constructs a new Tests instance. 36 + * @param {Function} checkRow - A function to check each row of data. 37 + * @param {Array<unknown>} data - The test data to be processed. 38 + */ 39 + constructor(checkRow, data) { 40 + this.checkRow = checkRow; 41 + this.data = data; 42 + } 43 + 44 + /** 45 + * Executes the tests and checks their results for all rows. 46 + */ 47 + checkAll() { 48 + for (const t of this.data) 49 + this.checkRow(t); 50 + } 51 + 52 + /** 53 + * Prints the result of a test, indicating if it passed or failed. 54 + * @param {unknown} result The actual result of the test. 55 + * @param {unknown} expected The expected result of the test. 56 + * @param {string} comment A comment describing the test. 57 + */ 58 + checkResult(result, expected, comment) { 59 + ++stats.run; 60 + if (!result || result == expected) { 61 + if (all) console.log('\x1B[1m\x1B[32mOK \x1B[39m\x1B[22m ' + comment); 62 + } else { 63 + ++stats.error; 64 + console.log('\x1B[1m\x1B[31mERR\x1B[39m\x1B[22m ' + comment); 65 + console.log(' \x1B[1m\x1B[34mEXP\x1B[39m\x1B[22m ' + expected.toString().replace(/\n/g, '\n ')); 66 + console.log(' \x1B[1m\x1B[33mGOT\x1B[39m\x1B[22m ' + result.replace(/\n/g, '\n ')); 67 + } 68 + } 69 + } 70 + 71 + tests.push(new Tests(function (t) { 72 + const input = t[0], 73 + expected = t[1], 74 + comment = t[2]; 75 + let result; 76 + try { 77 + let node = ASN1.decode(Hex.decode(input)); 78 + if (typeof expected == 'function') 79 + result = expected(node); 80 + else 81 + result = node.content(); 82 + //TODO: check structure, not only first level content 83 + } catch (e) { 84 + result = 'Exception:\n' + e; 85 + } 86 + if (expected instanceof RegExp) 87 + result = expected.test(result) ? null : 'does not match'; 88 + this.checkResult(result, expected, comment); 89 + }, [ 90 // RSA Laboratories technical notes from https://luca.ntop.org/Teaching/Appunti/asn1.html 91 ['0304066E5DC0', '(18 bit)\n011011100101110111', 'ntop, bit string: DER encoding'], 92 ['0304066E5DE0', '(18 bit)\n011011100101110111', 'ntop, bit string: padded with "100000"'], ··· 168 ['181331393835313130363231303632372E332B3134', '1985-11-06 21:06:27.3 UTC+14:00', 'UTC offset +13 and +14'], // GitHub issue #54 169 ['032100171E83C1B251803F86DD01E9CFA886BE89A7316D8372649AC2231EC669F81A84', n => { if (n.sub != null) return 'Should not decode content: ' + n.sub[0].content(); }, 'Key that resembles an UTCTime'], // GitHub issue #79 170 ['171E83C1B251803F86DD01E9CFA886BE89A7316D8372649AC2231EC669F81A84', /^Exception:\nError: Unrecognized time: /, 'Invalid UTCTime'], // GitHub issue #79 171 + ])); 172 173 + tests.push(new Tests(function (t) { 174 + let bin = Base64.decode(t); 175 + let url = new Stream(bin, 0).b64Dump(0, bin.length); 176 + // check base64url encoding 177 + this.checkResult(url, t.replace(/\n/g, '').replace(/=*$/g, ''), 'Base64url: ' + bin.length + ' bytes'); 178 + // check conversion from base64url to base64 179 + let pretty = Base64.pretty(url); 180 + this.checkResult(pretty, t, 'Base64pretty: ' + bin.length + ' bytes'); 181 + let std = new Stream(bin, 0).b64Dump(0, bin.length, 'std'); 182 + // check direct base64 encoding 183 + this.checkResult(std, t.replace(/\n/g, ''), 'Base64: ' + bin.length + ' bytes'); 184 + }, [ 185 'AA==', 186 'ABA=', 187 'ABCD', ··· 189 'ABCDEFE=', 190 'ABCDEFGH', 191 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQR\nSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456w==', 192 + ])); 193 194 + tests.push(new Tests(function (t) { 195 + this.row = (0|this.row) + 1; 196 + this.num = this.num || new Int10(); 197 + this.num.mulAdd(t[0], t[1]); 198 + this.checkResult(this.num.toString(), t[2], 'Int10 row ' + this.row); 199 + }, [ 200 + [0, 1000000000, '1000000000'], 201 + [256, 23, '256000000023'], 202 + [256, 23, '65536000005911'], 203 + [256, 23, '16777216001513239'], 204 + [256, 23, '4294967296387389207'], 205 + [256, 23, '1099511627875171637015'], 206 + [256, 23, '281474976736043939075863'], 207 + [253, 1, '71213169114219116586193340'], 208 + [253, 1, '18016931785897436496306915021'], 209 + [253, 1, '4558283741832051433565649500314'], 210 + [253, 1, '1153245786683509012692109323579443'], 211 + [253, 1, '291771184030927780211103658865599080'], 212 + [1, 0, '291771184030927780211103658865599080'], 213 + ])); 214 215 + for (const t of tests) 216 + t.checkAll(); 217 + 218 + console.log(stats.run + ' tested, ' + stats.error + ' errors.'); 219 + process.exit(stats.error ? 1 : 0);