Skip to content

Commit 0e4ce8f

Browse files
bnoordhuistargos
authored andcommitted
test: add known issues test for #31733
Authenticated decryption works for file streams up to 32768 bytes but not beyond. Other streams and direct decryption are not affected. Refs: #31733 PR-URL: #31734 Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent c095520 commit 0e4ce8f

File tree

1 file changed

+142
-0
lines changed

1 file changed

+142
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/* eslint-disable node-core/crypto-check */
2+
'use strict';
3+
// Refs: https://github.com/nodejs/node/issues/31733
4+
const common = require('../common');
5+
const assert = require('assert');
6+
const crypto = require('crypto');
7+
const fs = require('fs');
8+
const path = require('path');
9+
const stream = require('stream');
10+
const tmpdir = require('../common/tmpdir');
11+
12+
class 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+
24+
function 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+
40+
function 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+
71+
function 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+
}
114+
fstream.count = 0;
115+
116+
function test(config) {
117+
direct(config);
118+
mstream(config);
119+
fstream(config);
120+
}
121+
122+
tmpdir.refresh();
123+
124+
// OK
125+
test({
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.
135+
test({
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+
});

0 commit comments

Comments
 (0)