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