1'use strict'; 2// Refs: https://github.com/nodejs/node/issues/31733 3const common = require('../common'); 4if (!common.hasCrypto) 5 common.skip('missing crypto'); 6 7const assert = require('assert'); 8const crypto = require('crypto'); 9const fs = require('fs'); 10const path = require('path'); 11const stream = require('stream'); 12const tmpdir = require('../common/tmpdir'); 13 14class Sink extends stream.Writable { 15 constructor() { 16 super(); 17 this.chunks = []; 18 } 19 20 _write(chunk, encoding, cb) { 21 this.chunks.push(chunk); 22 cb(); 23 } 24} 25 26function direct(config) { 27 const { cipher, key, iv, aad, authTagLength, plaintextLength } = config; 28 const expected = Buffer.alloc(plaintextLength); 29 30 const c = crypto.createCipheriv(cipher, key, iv, { authTagLength }); 31 c.setAAD(aad, { plaintextLength }); 32 const ciphertext = Buffer.concat([c.update(expected), c.final()]); 33 34 const d = crypto.createDecipheriv(cipher, key, iv, { authTagLength }); 35 d.setAAD(aad, { plaintextLength }); 36 d.setAuthTag(c.getAuthTag()); 37 const actual = Buffer.concat([d.update(ciphertext), d.final()]); 38 39 assert.deepStrictEqual(expected, actual); 40} 41 42function mstream(config) { 43 const { cipher, key, iv, aad, authTagLength, plaintextLength } = config; 44 const expected = Buffer.alloc(plaintextLength); 45 46 const c = crypto.createCipheriv(cipher, key, iv, { authTagLength }); 47 c.setAAD(aad, { plaintextLength }); 48 49 const plain = new stream.PassThrough(); 50 const crypt = new Sink(); 51 const chunks = crypt.chunks; 52 plain.pipe(c).pipe(crypt); 53 plain.end(expected); 54 55 crypt.on('close', common.mustCall(() => { 56 const d = crypto.createDecipheriv(cipher, key, iv, { authTagLength }); 57 d.setAAD(aad, { plaintextLength }); 58 d.setAuthTag(c.getAuthTag()); 59 60 const crypt = new stream.PassThrough(); 61 const plain = new Sink(); 62 crypt.pipe(d).pipe(plain); 63 for (const chunk of chunks) crypt.write(chunk); 64 crypt.end(); 65 66 plain.on('close', common.mustCall(() => { 67 const actual = Buffer.concat(plain.chunks); 68 assert.deepStrictEqual(expected, actual); 69 })); 70 })); 71} 72 73function fstream(config) { 74 const count = fstream.count++; 75 const filename = (name) => path.join(tmpdir.path, `${name}${count}`); 76 77 const { cipher, key, iv, aad, authTagLength, plaintextLength } = config; 78 const expected = Buffer.alloc(plaintextLength); 79 fs.writeFileSync(filename('a'), expected); 80 81 const c = crypto.createCipheriv(cipher, key, iv, { authTagLength }); 82 c.setAAD(aad, { plaintextLength }); 83 84 const plain = fs.createReadStream(filename('a')); 85 const crypt = fs.createWriteStream(filename('b')); 86 plain.pipe(c).pipe(crypt); 87 88 // Observation: 'close' comes before 'end' on |c|, which definitely feels 89 // wrong. Switching to `c.on('end', ...)` doesn't fix the test though. 90 crypt.on('close', common.mustCall(() => { 91 // Just to drive home the point that decryption does actually work: 92 // reading the file synchronously, then decrypting it, works. 93 { 94 const ciphertext = fs.readFileSync(filename('b')); 95 const d = crypto.createDecipheriv(cipher, key, iv, { authTagLength }); 96 d.setAAD(aad, { plaintextLength }); 97 d.setAuthTag(c.getAuthTag()); 98 const actual = Buffer.concat([d.update(ciphertext), d.final()]); 99 assert.deepStrictEqual(expected, actual); 100 } 101 102 const d = crypto.createDecipheriv(cipher, key, iv, { authTagLength }); 103 d.setAAD(aad, { plaintextLength }); 104 d.setAuthTag(c.getAuthTag()); 105 106 const crypt = fs.createReadStream(filename('b')); 107 const plain = fs.createWriteStream(filename('c')); 108 crypt.pipe(d).pipe(plain); 109 110 plain.on('close', common.mustCall(() => { 111 const actual = fs.readFileSync(filename('c')); 112 assert.deepStrictEqual(expected, actual); 113 })); 114 })); 115} 116fstream.count = 0; 117 118function test(config) { 119 direct(config); 120 mstream(config); 121 fstream(config); 122} 123 124tmpdir.refresh(); 125 126test({ 127 cipher: 'aes-128-ccm', 128 aad: Buffer.alloc(1), 129 iv: Buffer.alloc(8), 130 key: Buffer.alloc(16), 131 authTagLength: 16, 132 plaintextLength: 32768, 133}); 134 135test({ 136 cipher: 'aes-128-ccm', 137 aad: Buffer.alloc(1), 138 iv: Buffer.alloc(8), 139 key: Buffer.alloc(16), 140 authTagLength: 16, 141 plaintextLength: 32769, 142}); 143