-
-
Notifications
You must be signed in to change notification settings - Fork 647
Description
(Since I haven't found a security policy that would ask for filing security issues over email, I'm making a regular bug report)
I've tested jsrsasign 10.8.6 on nodejs 21.1.0 and I have found it vulnerable to the Marvin Attack.
Looking at the results, both the bit size of the raw RSA decryption is leaking (so all padding modes will be vulnerable, both PKCS#1 v1.5 and OAEP), and in case of PKCS#1 v1.5 the size of the decrypted message is leaking. As such, it provides timing oracles useful in mounting a timing variant of the Bleichenbacher attack.
I've collected 10000 measurements per sample on an isolated core of an AMD Ryzen 5 5600X.
The test returned statistically significant results even with 100 measurements per sample, I've executed with with 10000 to look for side channels other then the bit size of the raw RSA operation. That means that the returned p-values are 0, as they are smaller in reality than a double precision floating point numbers can represent.
For 100k measurements the summary looks as follows:
Sign test mean p-value: 0.08346, median p-value: 6.623e-14, min p-value: 0.0
Friedman test (chisquare approximation) for all samples
p-value: 0.0
Worst pair: 2(no_padding_48), 11(zero_byte_in_padding_48_4)
Mean of differences: 2.67060e-05s, 95% CI: 2.39585e-05s, 2.993167e-05s (±2.987e-06s)
Median of differences: 2.61110e-05s, 95% CI: 2.55910e-05s, 2.649050e-05s (±4.498e-07s)
Trimmed mean (5%) of differences: 2.59561e-05s, 95% CI: 2.51313e-05s, 2.690085e-05s (±8.848e-07s)
Trimmed mean (25%) of differences: 2.59850e-05s, 95% CI: 2.56003e-05s, 2.642811e-05s (±4.139e-07s)
Trimmed mean (45%) of differences: 2.60091e-05s, 95% CI: 2.56006e-05s, 2.643911e-05s (±4.193e-07s)
Trimean of differences: 2.60815e-05s, 95% CI: 2.56166e-05s, 2.649325e-05s (±4.383e-07s)
and the confidence interval graph for the individual probes:
Legend to the graph:
ID,Name
0,header_only
1,no_header_with_payload_48
2,no_padding_48
3,no_structure
4,signature_padding_8
5,valid_0
6,valid_48
7,valid_192
8,valid_246
9,valid_repeated_byte_payload_246_1
10,valid_repeated_byte_payload_246_255
11,zero_byte_in_padding_48_4
Explanation for the ciphertexts is in the step2.py file.
Side note: the valid_246
probe is actually invalid, it has padding string of 7 bytes, which is less than the mandatory 8.
The reproducer I used for the test:
var program = require('commander');
var rs = require('jsrsasign');
var rsu = require('jsrsasign-util');
var path = require('path');
var fs = require('fs');
program
.version('1.0.0 (2016-Nov-05)')
.usage('[options] <encrypted data file> <output time file or "-"> <PEM RSA private key file> [RSA|RSAOEAP*>]')
.description('encrypt data')
.parse(process.argv);
if (program.args.length < 3)
throw "wrong number of arguments";
var keyObj, inHex, encHex;
var algName = "RSA";
var keyStr = "";
var inFileOrHex = program.args[0];
var outFile = program.args[1];
var keyFileOrStr = program.args[2];
if (program.args.length > 3) algName = program.args[3];
try {
keyStr = rsu.readFile(keyFileOrStr);
} catch(ex) {
keyStr = keyFileOrStr;
}
try {
keyObj = rs.KEYUTIL.getKey(keyStr);
} catch(ex) {};
const fileDescriptor = fs.openSync(inFileOrHex, 'r');
const outFD = fs.openSync(outFile, 'w');
const buffer = Buffer.alloc(256);
let bytesRead;
do {
bytesRead = fs.readSync(fileDescriptor, buffer, 0, buffer.length);
if (bytesRead > 0) {
inHex = buffer.toString('hex');
var startTime = process.hrtime();
var plainStr = rs.KJUR.crypto.Cipher.decrypt(inHex, keyObj, algName);
var endTime = process.hrtime();
var diff = (endTime[0] - startTime[0]) * 1000000000 + endTime[1] - startTime[1];
var outBuffer = Buffer.alloc(4);
outBuffer.writeInt32LE(diff, 0);
fs.writeSync(outFD, outBuffer);
}
} while (bytesRead === buffer.length);
fs.closeSync(fileDescriptor);
fs.closeSync(outFD);
It can be used in similar way as the python reproducer but in the extract step you need to additionally specify --binary 4
.