• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright Joyent, Inc. and other Node contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to permit
8// persons to whom the Software is furnished to do so, subject to the
9// following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22'use strict';
23const common = require('../common');
24if (!common.hasCrypto)
25  common.skip('missing crypto');
26
27const assert = require('assert');
28const crypto = require('crypto');
29const fixtures = require('../common/fixtures');
30
31crypto.DEFAULT_ENCODING = 'buffer';
32
33//
34// Test authenticated encryption modes.
35//
36// !NEVER USE STATIC IVs IN REAL LIFE!
37//
38
39const TEST_CASES = require(fixtures.path('aead-vectors.js'));
40
41const errMessages = {
42  auth: / auth/,
43  state: / state/,
44  FIPS: /not supported in FIPS mode/,
45  length: /Invalid IV length/,
46  authTagLength: /Invalid authentication tag length/
47};
48
49const ciphers = crypto.getCiphers();
50
51const expectedWarnings = common.hasFipsCrypto ?
52  [] : [
53    ['Use Cipheriv for counter mode of aes-192-gcm'],
54    ['Use Cipheriv for counter mode of aes-192-ccm'],
55    ['Use Cipheriv for counter mode of aes-192-ccm'],
56    ['Use Cipheriv for counter mode of aes-128-ccm'],
57    ['Use Cipheriv for counter mode of aes-128-ccm'],
58    ['Use Cipheriv for counter mode of aes-128-ccm'],
59    ['Use Cipheriv for counter mode of aes-256-ccm'],
60    ['Use Cipheriv for counter mode of aes-256-ccm'],
61    ['Use Cipheriv for counter mode of aes-256-ccm'],
62    ['Use Cipheriv for counter mode of aes-256-ccm'],
63    ['Use Cipheriv for counter mode of aes-256-ccm'],
64    ['Use Cipheriv for counter mode of aes-256-ccm'],
65    ['Use Cipheriv for counter mode of aes-256-ccm'],
66    ['Use Cipheriv for counter mode of aes-256-ccm'],
67    ['Use Cipheriv for counter mode of aes-256-ccm'],
68    ['Use Cipheriv for counter mode of aes-256-ccm'],
69    ['Use Cipheriv for counter mode of aes-256-ccm'],
70    ['Use Cipheriv for counter mode of aes-256-ccm'],
71    ['Use Cipheriv for counter mode of aes-256-ccm'],
72  ];
73
74const expectedDeprecationWarnings = [
75  ['crypto.DEFAULT_ENCODING is deprecated.', 'DEP0091'],
76  ['crypto.createCipher is deprecated.', 'DEP0106'],
77];
78
79common.expectWarning({
80  Warning: expectedWarnings,
81  DeprecationWarning: expectedDeprecationWarnings
82});
83
84for (const test of TEST_CASES) {
85  if (!ciphers.includes(test.algo)) {
86    common.printSkipMessage(`unsupported ${test.algo} test`);
87    continue;
88  }
89
90  if (common.hasFipsCrypto && test.iv.length < 24) {
91    common.printSkipMessage('IV len < 12 bytes unsupported in FIPS mode');
92    continue;
93  }
94
95  const isCCM = /^aes-(128|192|256)-ccm$/.test(test.algo);
96  const isOCB = /^aes-(128|192|256)-ocb$/.test(test.algo);
97  const isChacha20Poly1305 = test.algo === 'chacha20-poly1305';
98
99  let options;
100  if (isCCM || isOCB || isChacha20Poly1305)
101    options = { authTagLength: test.tag.length / 2 };
102
103  const inputEncoding = test.plainIsHex ? 'hex' : 'ascii';
104
105  let aadOptions;
106  if (isCCM) {
107    aadOptions = {
108      plaintextLength: Buffer.from(test.plain, inputEncoding).length
109    };
110  }
111
112  {
113    const encrypt = crypto.createCipheriv(test.algo,
114                                          Buffer.from(test.key, 'hex'),
115                                          Buffer.from(test.iv, 'hex'),
116                                          options);
117
118    if (test.aad)
119      encrypt.setAAD(Buffer.from(test.aad, 'hex'), aadOptions);
120
121    let hex = encrypt.update(test.plain, inputEncoding, 'hex');
122    hex += encrypt.final('hex');
123
124    const auth_tag = encrypt.getAuthTag();
125    // Only test basic encryption run if output is marked as tampered.
126    if (!test.tampered) {
127      assert.strictEqual(hex, test.ct);
128      assert.strictEqual(auth_tag.toString('hex'), test.tag);
129    }
130  }
131
132  {
133    if (isCCM && common.hasFipsCrypto) {
134      assert.throws(() => {
135        crypto.createDecipheriv(test.algo,
136                                Buffer.from(test.key, 'hex'),
137                                Buffer.from(test.iv, 'hex'),
138                                options);
139      }, errMessages.FIPS);
140    } else {
141      const decrypt = crypto.createDecipheriv(test.algo,
142                                              Buffer.from(test.key, 'hex'),
143                                              Buffer.from(test.iv, 'hex'),
144                                              options);
145      decrypt.setAuthTag(Buffer.from(test.tag, 'hex'));
146      if (test.aad)
147        decrypt.setAAD(Buffer.from(test.aad, 'hex'), aadOptions);
148
149      const outputEncoding = test.plainIsHex ? 'hex' : 'ascii';
150
151      let msg = decrypt.update(test.ct, 'hex', outputEncoding);
152      if (!test.tampered) {
153        msg += decrypt.final(outputEncoding);
154        assert.strictEqual(msg, test.plain);
155      } else {
156        // Assert that final throws if input data could not be verified!
157        assert.throws(function() { decrypt.final('hex'); }, errMessages.auth);
158      }
159    }
160  }
161
162  if (test.password) {
163    if (common.hasFipsCrypto) {
164      assert.throws(() => { crypto.createCipher(test.algo, test.password); },
165                    errMessages.FIPS);
166    } else {
167      const encrypt = crypto.createCipher(test.algo, test.password, options);
168      if (test.aad)
169        encrypt.setAAD(Buffer.from(test.aad, 'hex'), aadOptions);
170      let hex = encrypt.update(test.plain, 'ascii', 'hex');
171      hex += encrypt.final('hex');
172      const auth_tag = encrypt.getAuthTag();
173      // Only test basic encryption run if output is marked as tampered.
174      if (!test.tampered) {
175        assert.strictEqual(hex, test.ct);
176        assert.strictEqual(auth_tag.toString('hex'), test.tag);
177      }
178    }
179  }
180
181  if (test.password) {
182    if (common.hasFipsCrypto) {
183      assert.throws(() => { crypto.createDecipher(test.algo, test.password); },
184                    errMessages.FIPS);
185    } else {
186      const decrypt = crypto.createDecipher(test.algo, test.password, options);
187      decrypt.setAuthTag(Buffer.from(test.tag, 'hex'));
188      if (test.aad)
189        decrypt.setAAD(Buffer.from(test.aad, 'hex'), aadOptions);
190      let msg = decrypt.update(test.ct, 'hex', 'ascii');
191      if (!test.tampered) {
192        msg += decrypt.final('ascii');
193        assert.strictEqual(msg, test.plain);
194      } else {
195        // Assert that final throws if input data could not be verified!
196        assert.throws(function() { decrypt.final('ascii'); }, errMessages.auth);
197      }
198    }
199  }
200
201  {
202    // Trying to get tag before inputting all data:
203    const encrypt = crypto.createCipheriv(test.algo,
204                                          Buffer.from(test.key, 'hex'),
205                                          Buffer.from(test.iv, 'hex'),
206                                          options);
207    encrypt.update('blah', 'ascii');
208    assert.throws(function() { encrypt.getAuthTag(); }, errMessages.state);
209  }
210
211  {
212    // Trying to create cipher with incorrect IV length
213    assert.throws(function() {
214      crypto.createCipheriv(
215        test.algo,
216        Buffer.from(test.key, 'hex'),
217        Buffer.alloc(0)
218      );
219    }, errMessages.length);
220  }
221}
222
223// Non-authenticating mode:
224{
225  const encrypt =
226      crypto.createCipheriv('aes-128-cbc',
227                            'ipxp9a6i1Mb4USb4',
228                            '6fKjEjR3Vl30EUYC');
229  encrypt.update('blah', 'ascii');
230  encrypt.final();
231  assert.throws(() => encrypt.getAuthTag(), errMessages.state);
232  assert.throws(() => encrypt.setAAD(Buffer.from('123', 'ascii')),
233                errMessages.state);
234}
235
236// GCM only supports specific authentication tag lengths, invalid lengths should
237// throw.
238{
239  for (const length of [0, 1, 2, 6, 9, 10, 11, 17]) {
240    assert.throws(() => {
241      const decrypt = crypto.createDecipheriv('aes-128-gcm',
242                                              'FxLKsqdmv0E9xrQh',
243                                              'qkuZpJWCewa6Szih');
244      decrypt.setAuthTag(Buffer.from('1'.repeat(length)));
245    }, {
246      name: 'Error',
247      message: `Invalid authentication tag length: ${length}`
248    });
249
250    assert.throws(() => {
251      crypto.createCipheriv('aes-256-gcm',
252                            'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8',
253                            'qkuZpJWCewa6Szih',
254                            {
255                              authTagLength: length
256                            });
257    }, {
258      name: 'Error',
259      message: `Invalid authentication tag length: ${length}`
260    });
261
262    assert.throws(() => {
263      crypto.createDecipheriv('aes-256-gcm',
264                              'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8',
265                              'qkuZpJWCewa6Szih',
266                              {
267                                authTagLength: length
268                              });
269    }, {
270      name: 'Error',
271      message: `Invalid authentication tag length: ${length}`
272    });
273  }
274}
275
276// Test that GCM can produce shorter authentication tags than 16 bytes.
277{
278  const fullTag = '1debb47b2c91ba2cea16fad021703070';
279  for (const [authTagLength, e] of [[undefined, 16], [12, 12], [4, 4]]) {
280    const cipher = crypto.createCipheriv('aes-256-gcm',
281                                         'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8',
282                                         'qkuZpJWCewa6Szih', {
283                                           authTagLength
284                                         });
285    cipher.setAAD(Buffer.from('abcd'));
286    cipher.update('01234567', 'hex');
287    cipher.final();
288    const tag = cipher.getAuthTag();
289    assert.strictEqual(tag.toString('hex'), fullTag.substr(0, 2 * e));
290  }
291}
292
293// Test that users can manually restrict the GCM tag length to a single value.
294{
295  const decipher = crypto.createDecipheriv('aes-256-gcm',
296                                           'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8',
297                                           'qkuZpJWCewa6Szih', {
298                                             authTagLength: 8
299                                           });
300
301  assert.throws(() => {
302    // This tag would normally be allowed.
303    decipher.setAuthTag(Buffer.from('1'.repeat(12)));
304  }, {
305    name: 'Error',
306    message: 'Invalid authentication tag length: 12'
307  });
308
309  // The Decipher object should be left intact.
310  decipher.setAuthTag(Buffer.from('445352d3ff85cf94', 'hex'));
311  const text = Buffer.concat([
312    decipher.update('3a2a3647', 'hex'),
313    decipher.final(),
314  ]);
315  assert.strictEqual(text.toString('utf8'), 'node');
316}
317
318// Test that create(De|C)ipher(iv)? throws if the mode is CCM and an invalid
319// authentication tag length has been specified.
320{
321  for (const authTagLength of [-1, true, false, NaN, 5.5]) {
322    assert.throws(() => {
323      crypto.createCipheriv('aes-256-ccm',
324                            'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8',
325                            'qkuZpJWCewa6S',
326                            {
327                              authTagLength
328                            });
329    }, {
330      name: 'TypeError',
331      code: 'ERR_INVALID_OPT_VALUE',
332      message: `The value "${authTagLength}" is invalid for option ` +
333               '"authTagLength"'
334    });
335
336    assert.throws(() => {
337      crypto.createDecipheriv('aes-256-ccm',
338                              'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8',
339                              'qkuZpJWCewa6S',
340                              {
341                                authTagLength
342                              });
343    }, {
344      name: 'TypeError',
345      code: 'ERR_INVALID_OPT_VALUE',
346      message: `The value "${authTagLength}" is invalid for option ` +
347               '"authTagLength"'
348    });
349
350    if (!common.hasFipsCrypto) {
351      assert.throws(() => {
352        crypto.createCipher('aes-256-ccm', 'bad password', { authTagLength });
353      }, {
354        name: 'TypeError',
355        code: 'ERR_INVALID_OPT_VALUE',
356        message: `The value "${authTagLength}" is invalid for option ` +
357                 '"authTagLength"'
358      });
359
360      assert.throws(() => {
361        crypto.createDecipher('aes-256-ccm', 'bad password', { authTagLength });
362      }, {
363        name: 'TypeError',
364        code: 'ERR_INVALID_OPT_VALUE',
365        message: `The value "${authTagLength}" is invalid for option ` +
366                 '"authTagLength"'
367      });
368    }
369  }
370
371  // The following values will not be caught by the JS layer and thus will not
372  // use the default error codes.
373  for (const authTagLength of [0, 1, 2, 3, 5, 7, 9, 11, 13, 15, 17, 18]) {
374    assert.throws(() => {
375      crypto.createCipheriv('aes-256-ccm',
376                            'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8',
377                            'qkuZpJWCewa6S',
378                            {
379                              authTagLength
380                            });
381    }, errMessages.authTagLength);
382
383    if (!common.hasFipsCrypto) {
384      assert.throws(() => {
385        crypto.createDecipheriv('aes-256-ccm',
386                                'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8',
387                                'qkuZpJWCewa6S',
388                                {
389                                  authTagLength
390                                });
391      }, errMessages.authTagLength);
392
393      assert.throws(() => {
394        crypto.createCipher('aes-256-ccm', 'bad password', { authTagLength });
395      }, errMessages.authTagLength);
396
397      assert.throws(() => {
398        crypto.createDecipher('aes-256-ccm', 'bad password', { authTagLength });
399      }, errMessages.authTagLength);
400    }
401  }
402}
403
404// Test that create(De|C)ipher(iv)? throws if the mode is CCM or OCB and no
405// authentication tag has been specified.
406{
407  for (const mode of ['ccm', 'ocb']) {
408    assert.throws(() => {
409      crypto.createCipheriv(`aes-256-${mode}`,
410                            'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8',
411                            'qkuZpJWCewa6S');
412    }, {
413      message: `authTagLength required for aes-256-${mode}`
414    });
415
416    // CCM decryption and create(De|C)ipher are unsupported in FIPS mode.
417    if (!common.hasFipsCrypto) {
418      assert.throws(() => {
419        crypto.createDecipheriv(`aes-256-${mode}`,
420                                'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8',
421                                'qkuZpJWCewa6S');
422      }, {
423        message: `authTagLength required for aes-256-${mode}`
424      });
425
426      assert.throws(() => {
427        crypto.createCipher(`aes-256-${mode}`, 'very bad password');
428      }, {
429        message: `authTagLength required for aes-256-${mode}`
430      });
431
432      assert.throws(() => {
433        crypto.createDecipher(`aes-256-${mode}`, 'very bad password');
434      }, {
435        message: `authTagLength required for aes-256-${mode}`
436      });
437    }
438  }
439}
440
441// Test that setAAD throws if an invalid plaintext length has been specified.
442{
443  const cipher = crypto.createCipheriv('aes-256-ccm',
444                                       'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8',
445                                       'qkuZpJWCewa6S',
446                                       {
447                                         authTagLength: 10
448                                       });
449
450  for (const plaintextLength of [-1, true, false, NaN, 5.5]) {
451    assert.throws(() => {
452      cipher.setAAD(Buffer.from('0123456789', 'hex'), { plaintextLength });
453    }, {
454      name: 'TypeError',
455      code: 'ERR_INVALID_OPT_VALUE',
456      message: `The value "${plaintextLength}" is invalid for option ` +
457               '"plaintextLength"'
458    });
459  }
460}
461
462// Test that setAAD and update throw if the plaintext is too long.
463{
464  for (const ivLength of [13, 12]) {
465    const maxMessageSize = (1 << (8 * (15 - ivLength))) - 1;
466    const key = 'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8';
467    const cipher = () => crypto.createCipheriv('aes-256-ccm', key,
468                                               '0'.repeat(ivLength),
469                                               {
470                                                 authTagLength: 10
471                                               });
472
473    assert.throws(() => {
474      cipher().setAAD(Buffer.alloc(0), {
475        plaintextLength: maxMessageSize + 1
476      });
477    }, /^Error: Message exceeds maximum size$/);
478
479    const msg = Buffer.alloc(maxMessageSize + 1);
480    assert.throws(() => {
481      cipher().update(msg);
482    }, /^Error: Message exceeds maximum size$/);
483
484    const c = cipher();
485    c.setAAD(Buffer.alloc(0), {
486      plaintextLength: maxMessageSize
487    });
488    c.update(msg.slice(1));
489  }
490}
491
492// Test that setAAD throws if the mode is CCM and the plaintext length has not
493// been specified.
494{
495  assert.throws(() => {
496    const cipher = crypto.createCipheriv('aes-256-ccm',
497                                         'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8',
498                                         'qkuZpJWCewa6S',
499                                         {
500                                           authTagLength: 10
501                                         });
502    cipher.setAAD(Buffer.from('0123456789', 'hex'));
503  }, /^Error: plaintextLength required for CCM mode with AAD$/);
504
505  if (!common.hasFipsCrypto) {
506    assert.throws(() => {
507      const cipher = crypto.createDecipheriv('aes-256-ccm',
508                                             'FxLKsqdmv0E9xrQhp0b1ZgI0K7JFZJM8',
509                                             'qkuZpJWCewa6S',
510                                             {
511                                               authTagLength: 10
512                                             });
513      cipher.setAAD(Buffer.from('0123456789', 'hex'));
514    }, /^Error: plaintextLength required for CCM mode with AAD$/);
515  }
516}
517
518// Test that final() throws in CCM mode when no authentication tag is provided.
519{
520  if (!common.hasFipsCrypto) {
521    const key = Buffer.from('1ed2233fa2223ef5d7df08546049406c', 'hex');
522    const iv = Buffer.from('7305220bca40d4c90e1791e9', 'hex');
523    const ct = Buffer.from('8beba09d4d4d861f957d51c0794f4abf8030848e', 'hex');
524    const decrypt = crypto.createDecipheriv('aes-128-ccm', key, iv, {
525      authTagLength: 10
526    });
527    // Normally, we would do this:
528    // decrypt.setAuthTag(Buffer.from('0d9bcd142a94caf3d1dd', 'hex'));
529    assert.throws(() => {
530      decrypt.setAAD(Buffer.from('63616c76696e', 'hex'), {
531        plaintextLength: ct.length
532      });
533      decrypt.update(ct);
534      decrypt.final();
535    }, errMessages.state);
536  }
537}
538
539// Test that setAuthTag does not throw in GCM mode when called after setAAD.
540{
541  const key = Buffer.from('1ed2233fa2223ef5d7df08546049406c', 'hex');
542  const iv = Buffer.from('579d9dfde9cd93d743da1ceaeebb86e4', 'hex');
543  const decrypt = crypto.createDecipheriv('aes-128-gcm', key, iv);
544  decrypt.setAAD(Buffer.from('0123456789', 'hex'));
545  decrypt.setAuthTag(Buffer.from('1bb9253e250b8069cde97151d7ef32d9', 'hex'));
546  assert.strictEqual(decrypt.update('807022', 'hex', 'hex'), 'abcdef');
547  assert.strictEqual(decrypt.final('hex'), '');
548}
549
550// Test that an IV length of 11 does not overflow max_message_size_.
551{
552  const key = 'x'.repeat(16);
553  const iv = Buffer.from('112233445566778899aabb', 'hex');
554  const options = { authTagLength: 8 };
555  const encrypt = crypto.createCipheriv('aes-128-ccm', key, iv, options);
556  encrypt.update('boom');  // Should not throw 'Message exceeds maximum size'.
557  encrypt.final();
558}
559
560// Test that the authentication tag can be set at any point before calling
561// final() in GCM or OCB mode.
562{
563  const plain = Buffer.from('Hello world', 'utf8');
564  const key = Buffer.from('0123456789abcdef', 'utf8');
565  const iv = Buffer.from('0123456789ab', 'utf8');
566
567  for (const mode of ['gcm', 'ocb']) {
568    for (const authTagLength of mode === 'gcm' ? [undefined, 8] : [8]) {
569      const cipher = crypto.createCipheriv(`aes-128-${mode}`, key, iv, {
570        authTagLength
571      });
572      const ciphertext = Buffer.concat([cipher.update(plain), cipher.final()]);
573      const authTag = cipher.getAuthTag();
574
575      for (const authTagBeforeUpdate of [true, false]) {
576        const decipher = crypto.createDecipheriv(`aes-128-${mode}`, key, iv, {
577          authTagLength
578        });
579        if (authTagBeforeUpdate) {
580          decipher.setAuthTag(authTag);
581        }
582        const resultUpdate = decipher.update(ciphertext);
583        if (!authTagBeforeUpdate) {
584          decipher.setAuthTag(authTag);
585        }
586        const resultFinal = decipher.final();
587        const result = Buffer.concat([resultUpdate, resultFinal]);
588        assert(result.equals(plain));
589      }
590    }
591  }
592}
593
594// Test that setAuthTag can only be called once.
595{
596  const plain = Buffer.from('Hello world', 'utf8');
597  const key = Buffer.from('0123456789abcdef', 'utf8');
598  const iv = Buffer.from('0123456789ab', 'utf8');
599  const opts = { authTagLength: 8 };
600
601  for (const mode of ['gcm', 'ccm', 'ocb']) {
602    const cipher = crypto.createCipheriv(`aes-128-${mode}`, key, iv, opts);
603    const ciphertext = Buffer.concat([cipher.update(plain), cipher.final()]);
604    const tag = cipher.getAuthTag();
605
606    const decipher = crypto.createDecipheriv(`aes-128-${mode}`, key, iv, opts);
607    decipher.setAuthTag(tag);
608    assert.throws(() => {
609      decipher.setAuthTag(tag);
610    }, errMessages.state);
611    // Decryption should still work.
612    const plaintext = Buffer.concat([
613      decipher.update(ciphertext),
614      decipher.final(),
615    ]);
616    assert(plain.equals(plaintext));
617  }
618}
619
620
621// Test chacha20-poly1305 rejects invalid IV lengths of 13, 14, 15, and 16 (a
622// length of 17 or greater was already rejected).
623// - https://www.openssl.org/news/secadv/20190306.txt
624{
625  // Valid extracted from TEST_CASES, check that it detects IV tampering.
626  const valid = {
627    algo: 'chacha20-poly1305',
628    key: '808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f',
629    iv: '070000004041424344454647',
630    plain: '4c616469657320616e642047656e746c656d656e206f662074686520636c6173' +
631           '73206f66202739393a204966204920636f756c64206f6666657220796f75206f' +
632           '6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73' +
633           '637265656e20776f756c642062652069742e',
634    plainIsHex: true,
635    aad: '50515253c0c1c2c3c4c5c6c7',
636    ct: 'd31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5' +
637        'a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e06' +
638        '0b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fa' +
639        'b324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d265' +
640        '86cec64b6116',
641    tag: '1ae10b594f09e26a7e902ecbd0600691',
642    tampered: false,
643  };
644
645  // Invalid IV lengths should be detected:
646  // - 12 and below are valid.
647  // - 13-16 are not detected as invalid by some OpenSSL versions.
648  check(13);
649  check(14);
650  check(15);
651  check(16);
652  // - 17 and above were always detected as invalid by OpenSSL.
653  check(17);
654
655  function check(ivLength) {
656    const prefix = ivLength - valid.iv.length / 2;
657    assert.throws(() => crypto.createCipheriv(
658      valid.algo,
659      Buffer.from(valid.key, 'hex'),
660      Buffer.from(H(prefix) + valid.iv, 'hex'),
661      { authTagLength: valid.tag.length / 2 }
662    ), errMessages.length, `iv length ${ivLength} was not rejected`);
663
664    function H(length) { return '00'.repeat(length); }
665  }
666}
667