-
Notifications
You must be signed in to change notification settings - Fork 166
Expand file tree
/
Copy pathtest.js
More file actions
executable file
·289 lines (272 loc) · 20.2 KB
/
test.js
File metadata and controls
executable file
·289 lines (272 loc) · 20.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
#!/usr/bin/env node
import * as fs from 'fs'; // 'node:fs' doesn't work on NodeJS 14.5.0
import { ASN1, Stream } from './asn1.js';
import { Defs } from './defs.js';
import { Hex } from './hex.js';
import { Base64 } from './base64.js';
import { createPatch } from 'diff';
const all = (process.argv[2] == 'all');
/** @type {Array<Tests>} */
const tests = [];
const stats = {
run: 0,
error: 0,
};
function diff(str1, str2) {
let s = createPatch('test', str1, str2, null, null, { context: 2 });
s = s.slice(s.indexOf('@@'), -1);
s = s.replace(/^@@.*/mg, '\x1B[34m$&\x1B[39m');
s = s.replace(/^-.*/mg, '\x1B[31m$&\x1B[39m');
s = s.replace(/^\+.*/mg, '\x1B[32m$&\x1B[39m');
return s;
}
/**
* A class for managing and executing tests.
*/
class Tests {
/**
* The title of the test suite.
* @type {string}
*/
title;
/**
* An array to store test data.
* @type {Array<unknown>}
*/
data;
/**
* Checks a row of test data.
* @param {Function} t - How to test a row of data.
*/
checkRow;
/**
* Constructs a new Tests instance.
* @param {string} title - The title of the test suite.
* @param {Function} checkRow - A function to check each row of data.
* @param {Array<unknown>} data - The test data to be processed.
*/
constructor(title, checkRow, data) {
this.title = title;
this.checkRow = checkRow;
this.data = data;
}
/**
* Executes the tests and checks their results for all rows.
*/
checkAll() {
if (all) console.log('\x1B[1m\x1B[34m' + this.title + '\x1B[39m\x1B[22m');
for (const t of this.data)
this.checkRow(t);
}
/**
* Prints the result of a test, indicating if it passed or failed.
* @param {unknown} result The actual result of the test.
* @param {unknown} expected The expected result of the test.
* @param {string} comment A comment describing the test.
*/
checkResult(result, expected, comment) {
++stats.run;
if ((typeof expected != 'string' && !result) || result == expected) {
if (all) console.log('\x1B[1m\x1B[32mOK \x1B[39m\x1B[22m ' + comment);
} else {
++stats.error;
console.log('\x1B[1m\x1B[31mERR\x1B[39m\x1B[22m ' + comment);
if (!result) result = '(empty)';
if (typeof expected != 'string') {
console.log(' ' + result);
} else if (result.length > 100) {
console.log(' \x1B[1m\x1B[34mDIF\x1B[39m\x1B[22m ' + diff(result, expected.toString()).replace(/\n/g, '\n '));
} else {
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 '));
}
}
}
}
tests.push(new Tests('ASN.1', function (t) {
const input = t[0],
expected = t[1],
comment = t[2];
let result;
try {
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';
this.checkResult(result, expected, comment);
}, [
// RSA Laboratories technical notes from https://luca.ntop.org/Teaching/Appunti/asn1.html
['0304066E5DC0', '(18 bit)\n011011100101110111', 'ntop, bit string: DER encoding'],
['0304066E5DE0', '(18 bit)\n011011100101110111', 'ntop, bit string: padded with "100000"'],
['038104066E5DC0', '(18 bit)\n011011100101110111', 'ntop, bit string: long form of length octets'],
['23090303006E5D030206C0', '(18 bit)\n011011100101110111', 'ntop, bit string (constructed encoding): "0110111001011101" + "11"'],
['160D7465737431407273612E636F6D', 'test1@rsa.com', 'ntop, ia5string: DER encoding'],
['16810D7465737431407273612E636F6D', 'test1@rsa.com', 'ntop, ia5string: long form of length octets'],
['36131605746573743116014016077273612E636F6D', 'test1@rsa.com', 'ntop, ia5string: constructed encoding: "test1" + "@" + "rsa.com"'],
['020100', '0', 'ntop, integer: 0'],
['02017F', '127', 'ntop, integer: 127'],
['02020080', '128', 'ntop, integer: 128'],
['02020100', '256', 'ntop, integer: 256'],
['020180', '-128', 'ntop, integer: -128'],
['0202FF7F', '-129', 'ntop, integer: -129'],
['0500', null, 'ntop, null: DER'],
['058100', null, 'ntop, null: long form of length octets'],
['06062A864886F70D', '1.2.840.113549', 'ntop, object identifier'],
['04080123456789ABCDEF', '(8 byte)\n0123456789ABCDEF', 'ntop, octet string: DER encoding'],
['0481080123456789ABCDEF', '(8 byte)\n0123456789ABCDEF', 'ntop, octet string: long form of length octets'],
['240C040401234567040489ABCDEF', '(8 byte)\n0123456789ABCDEF', 'ntop, octet string (constructed encoding): 01…67 + 89…ef'],
['130B5465737420557365722031', 'Test User 1', 'ntop, printable string: DER encoding'],
['13810B5465737420557365722031', 'Test User 1', 'ntop, printable string: long form of length octets'],
['330F130554657374201306557365722031', 'Test User 1', 'ntop, printable string: constructed encoding: "Test " + "User 1"'],
['140F636CC26573207075626C6971756573', 'clés publiques', 'ntop, t61string: DER encoding'],
['14810F636CC26573207075626C6971756573', 'clés publiques', 'ntop, t61string: long form of length octets'],
['34151405636CC2657314012014097075626C6971756573', 'clés publiques', 'ntop, t61string: constructed encoding: "clés" + " " + "publiques"'],
['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:\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'],
// Int10
['02102FA176B36EE9F049F444B40099661945', '(126 bit)\n63312083136615639753586560173617846597', 'Big integer (126 bit)'],
['028181008953097086EE6147C5F4D5FFAF1B498A3D11EC5518E964DC52126B2614F743883F64CA51377ABB530DFD20464A48BD67CD27E7B29AEC685C5D10825E605C056E4AB8EEA460FA27E55AA62C498B02D7247A249838A12ECDF37C6011CF4F0EDEA9CEE687C1CB4A51C6AE62B2EFDB000723A01C99D6C23F834880BA8B42D5414E6F',
'(1024 bit)\n96432446964907009840023644401994013457468837455140331578268642517697945390319089463541388080569398374873228752921897678940332050406994011437231634303608704223145390228074087922901239478374991949372306413157758278029522534299413919735715864599284769202556071242381348472464716517735026291259010477833523908207',
'Big integer (1024 bit)'],
['02820201009BA9ABBF614A97AF2F97669A745FD0D996FDCFE2E466EF1F1F4733C244A3DF9ADE1FB554DD157C6935116FBBC80C8E6A181ED88FD916BC1048365CF063B3905A5C2437D7A3D6CB0971B9F1017284B07DDB4D80CDFCD36FC9F8DAB60E82D24585A81B68A83DE8F4446CBDA1C2CB03BE8C3E130084DF4A48C0E3220AE8E937A7184CB1090D23567F044DD9178418A5C8DA409473EBCE0E573C03813A9D0AA1574369AC576D799078E5B5B43BD8BC4C8D28A1A7A3A7BA024E25D12AAEEDAE0322B86B200F302854957FE0EECE0A669DD1402D6E22AF9D1AC10519D26FC0F29FF87BB30242FB50A91D2D930F23ABC6C10F92FFD0A215F55309711CFF451384E6265EF8E0881C0AFC16B6A87306B8F0638402A0C65AECE774DF70AEA38325EAD6C7978793A7C68A8A33976037103E973E6E2915D6A10FD1882C129F6FAAA4C642EB41A2E39543D301856D8EBB3BF32336C7FE3BE0A1250748ABC98974FF088F80BFC09665F3EEEC4B68BD9D88C331B340F1E8CFF638BB9CE4D17FD4E5589B7CFAD4F30E9B7591E4BA522E197ED1F5CD5A19FCBA06F6FB52A84B9904DDF8F9B48B50A34E6289F08724FA8342C187FAD52D292A5A717A646AD72760630DDBCE49F58D1F90893217F87343B8D25A938661D6E1750AEA796676884F71EB0425D60A5A7A93E5B94B17400FB1B6B9F5DE4FDCE0B3AC3B117060844A436E9920C029710AC065',
'(4096 bit)\n635048724432704421127930570668665246853305382538324205739741643121831497295424070220821366244137115920753022123888627038218427491681054376713237422498116573180444839575827154645186734602336866679804832661256616738257119870932328599495025506292424741581222812593482590762754785441060866630543522468678295806775919446210955958208696766307578451905771148370694894591388931494246786732600822191966681852303750991082312180670980233382216493445574638820565887523903118457551295241540793541306271618874366356869335229283992012581380459991160410157116077111142487644315609688092841697874946124416376553323373756926990721842430477982958383467149331238064901788481621489266725616974850293388387825359434286033332681714766010619113405542747712973535303497912234899589502990216128180823653963322406352206636893824027962569732222882297210841939793442415179615290739900774082858983244011679281115202763730964934473392333219986431517733237277686866318351054204026883453068392486990840498271719737813876367239342153571643327128417739316281558041652406500713712661305061745568036561978158652008943224271511931676512028205883718704503533046383542018858616087454820845906934069146870330990447993387221061968484774662499598623702280426558025111180066917',
'Big integer (4096 bit)'],
['0202007F', '127', 'Padded 127'],
['0202FF7F', '-129', 'Negative 129'],
['0202FC18', '-1000', 'Negative 1000 (2)'],
['0204FFFFFC18', '-1000', 'Negative 1000 (4)'],
['0208FFFFFFFFFFFFFC18', '-1000', 'Negative 1000 (8)'],
['0210FFFFFFFFFFFFFFFFFFFFFFFFFFFFFC18', '-1000', 'Negative 1000 (16)'],
['0203800001', '-8388607', 'Negative 8388607'],
['02020000', '0', 'Zero (2)'],
['0204FFFFFFFF', '-1', 'Negative 1 (4)'],
// OID
['060C69C7C79AB78084C289F9870D', '2.25.84478768945400492475277', 'Big OID arc'],
['06146982968D8D889BCCA8C7B3BDD4C080AAAED78A1B', '2.25.184830721219540099336690027854602552603', 'Bigger OID arc'],
['060488378952', '2.999.1234', 'OID arc > 2.47'],
['060782A384F3CAC00A', '2.9999999999930', 'OID with Int10 corner case (1)'],
['060881E3AFEAA69A800A', '2.999999999999930', 'OID with Int10 corner case (2)'],
['06092A864886F70D010105', '1.2.840.113549.1.1.5\nsha1WithRSAEncryption\nPKCS #1', 'known OID from Peter Gutmann list'],
// OID corner case from https://misc.daniel-marschall.de/asn.1/oid-sizecheck/oid_size_test.txt
['060A81FFFFFFFFFFFFFFFF7F', '2.18446744073709551535', 'OID root 64 bit - 1'],
['060A82808080808080808000', '2.18446744073709551536', 'OID root 64 bit'],
['060A82808080808080808001', '2.18446744073709551537', 'OID root 64 bit + 1'],
['0620FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F', '2.26959946667150639794667015087019630673637144422540572481103610249135', 'OID derLen20c'],
['0621818080808080808080808080808080808080808080808080808080808080808000', '2.26959946667150639794667015087019630673637144422540572481103610249136', 'OID derLen21c'],
// relative OID
['0D0A0102030405060708090A','1.2.3.4.5.6.7.8.9.10', 'Relative OID from GitHub PR 56'],
['0D04C27B0302','8571.3.2', 'Relative OID from ISO/IEC 8825-1:2002 8.20.5'],
// UTF-8
['0C0E4C61706FE280997320F09F9A972E', 'Lapo’s 🚗.', 'UTF-8 4-byte sequence'],
// T-REC-X.690-202102
['0307040A3B5F291CD0', '(44 bit)\n00001010001110110101111100101001000111001101', 'Example 8.6.4.2: bit string (primitive encoding)'],
['23800303000A3B0305045F291CD00000', '(44 bit)\n00001010001110110101111100101001000111001101', 'Example 8.6.4.2: bit string (constructed encoding)'],
['0603883703', '2.999.3', 'Example 8.19.5: object identifier'],
// X.690 section 8.2.1 “The contents octets shall consist of a single octet.”
['010100', 'false', 'Boolean with correct length (false)'],
['010101', 'true', 'Boolean with correct length (true)'],
['0100', 'invalid length 0', 'Boolean with zero length'],
['01020000', 'invalid length 2', 'Boolean with excessive length'],
// X.690 section 8.3.1 “The contents octets shall consist of one or more octets.”
['020100', '0', 'Integer with correct length'],
['0200', 'invalid length 0', 'Integer with zero length'],
// X.690 section 8.19.4 kinda imples that the minimum number of components is 2
['0600', 'invalid length 0', 'Object identifier with zero length'],
['060100', '0.0', 'Object identifier with minimal (?) length'],
// avoid past bugs
['23800303000A3B230A0302005F030404291CD00000', '(44 bit)\n00001010001110110101111100101001000111001101', 'Bit string (recursive constructed)'],
['0348003045022100DE601E573DAFB59BC551D58E3E7B9EDA0612DD0112805A2217B734759B884417022067C3FDE60780D41C1D7A3B90291F3D39C4DC2F206DCCBA2F982C06B67C09B232', '(568 bit)\n0011000001000101000000100010000100000000110111100110000000011110010101110011110110101111101101011001101111000101010100011101010110001110001111100111101110011110110110100000011000010010110111010000000100010010100000000101101000100010000101111011011100110100011101011001101110001000010001000001011100000010001000000110011111000011111111011110011000000111100000001101010000011100000111010111101000111011100100000010100100011111001111010011100111000100110111000010111100100000011011011100110010111010001011111001100000101100000001101011011001111100000010011011001000110010', 'not constructed, but contains structures'],
['040731323334353637', '(7 byte)\n1234567', 'Octet string with ASCII content'],
['0407312E3233E282AC', '(7 byte)\n1.23€', 'Octet string with UTF-8 content'],
['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
['180E3230303031323135313233343536', '2000-12-15 12:34:56', 'Generalized time with seconds'], // GitHub issue #107
['181232303030313231353132333435362E313233', '2000-12-15 12:34:56.123', 'Generalized time with milliseconds'], // GitHub issue #107
['181832303030313231353132333435362E313233343536373839', '2000-12-15 12:34:56.123456789', 'Generalized time with nanoseconds'], // GitHub issue #107
['0484FFFFFFFF222222', 'Exception:\nError: Element at offset 6 has a length of 4294967295, which is past the end of the stream', 'Excessive length'], // GitHub issue #108
['3081EC3081E93081E63081E33081E03081DD3081DA3081D73081D43081D13081CE3081CB3081C83081C53081C23081BF3081BC3081B93081B63081B33081B03081AD3081AA3081A73081A43081A130819E30819B30819830819530819230818F30818C308189308186308183308180307E307C307A30783076307430723070306E306C306A30683066306430623060305E305C305A30583056305430523050304E304C304A30483046304430423040303E303C303A30383036303430323030302E302C302A30283026302430223020301E301C301A30183016301430123010300E300C300A30083006300430023000', '(1 elem)', '100 nested structures'],
['3081EF3081EC3081E93081E63081E33081E03081DD3081DA3081D73081D43081D13081CE3081CB3081C83081C53081C23081BF3081BC3081B93081B63081B33081B03081AD3081AA3081A73081A43081A130819E30819B30819830819530819230818F30818C308189308186308183308180307E307C307A30783076307430723070306E306C306A30683066306430623060305E305C305A30583056305430523050304E304C304A30483046304430423040303E303C303A30383036303430323030302E302C302A30283026302430223020301E301C301A30183016301430123010300E300C300A30083006300430023000', 'Exception:\nError: ASN.1 structure nesting exceeds maximum depth of 100', '101 nested structures'],
]));
tests.push(new Tests('Length', function (t) {
const input = t[0],
expected = '' + t[1],
comment = t[2];
let result;
try {
result = '' + ASN1.decodeLength(new Stream(Hex.decode(input), 0));
} catch (e) {
result = 'Exception:\n' + e;
}
this.checkResult(result, expected, comment);
}, [
['00', 0, 'Short form length 0'],
['01', 1, 'Short form length 1'],
['7F', 127, 'Short form length 127'],
['80', null, 'Undefined length'],
['8103', 3, 'Long form length 3'],
['82FFFF', 0xFFFF, 'Long form length 65535'],
['83123456', 0x123456, 'Long form length 1193046'],
['847FFFFFFF', 0x7FFFFFFF, 'Long form length 2^31-1'],
['84FFFFFFFF', 0xFFFFFFFF, 'Long form length 2^32-1'],
['87FFFFFFFFFFFFFF', 'Exception:\nError: Length over 48 bits not supported at position 0', 'Long form length > 2^48'],
]));
tests.push(new Tests('Dump of examples', function () {
const examples = fs.readdirSync('examples/').filter(f => f.endsWith('.dump'));
for (const example of examples) {
const filename = example.slice(0, -5); // Remove '.dump' suffix
const expected = fs.readFileSync('examples/' + example, 'utf8');
let data = fs.readFileSync('examples/' + filename);
data = Base64.unarmor(data);
let node = ASN1.decode(data);
const types = Defs.commonTypes
.map(type => {
const stats = Defs.match(node, type);
return { type, match: stats.recognized / stats.total };
})
.sort((a, b) => b.match - a.match);
Defs.match(node, types[0].type);
let result = node.toPrettyString();
this.checkResult(result, expected, 'Dump of examples/' + filename);
}
}, [
[0],
]));
tests.push(new Tests('Base64', function (t) {
let bin = Base64.decode(t);
let url = new Stream(bin, 0).b64Dump(0, bin.length);
// check base64url encoding
this.checkResult(url, t.replace(/\n/g, '').replace(/=*$/g, ''), 'Base64url: ' + bin.length + ' bytes');
// check conversion from base64url to base64
let pretty = Base64.pretty(url);
this.checkResult(pretty, t, 'Base64pretty: ' + bin.length + ' bytes');
let std = new Stream(bin, 0).b64Dump(0, bin.length, 'std');
// check direct base64 encoding
this.checkResult(std, t.replace(/\n/g, ''), 'Base64: ' + bin.length + ' bytes');
}, [
'AA==',
'ABA=',
'ABCD',
'ABCDEA==',
'ABCDEFE=',
'ABCDEFGH',
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQR\nSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456w==',
]));
for (const t of tests)
t.checkAll();
console.log(stats.run + ' tested, ' + stats.error + ' errors.');
process.exit(stats.error ? 1 : 0);