• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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