Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ var options = {

An optional OAEP label may be supplied as `keyEncryptionOaepParams` (a Buffer or a base64 string); it is emitted as `<OAEPparams>` and honoured on decrypt.

Note: for the digest/MGF1 combinations Node's `crypto` cannot express, the OAEP padding is computed in JavaScript over the raw RSA primitive. That code path cannot offer the constant-time guarantees of OpenSSL's C implementation. It is used only when the MGF1 digest differs from the message digest; all other combinations go through `crypto.privateDecrypt` unchanged.
Note: for the digest/MGF1 combinations Node's `crypto` cannot express, the OAEP padding is computed in JavaScript over the raw RSA primitive. That code path is blocked when Node is using a FIPS compliant crypto provider and cannot offer the constant-time guarantees of OpenSSL's C implementation. It is used only when the MGF1 digest differs from the message digest; all other combinations go through `crypto.privateDecrypt` unchanged.

**Breaking change:** in versions 3.1.0 through 5.0.0, `rsa-oaep-mgf1p` with `keyEncryptionDigest: 'sha256'` or `'sha512'` produced ciphertext using MGF1-SHA256 or MGF1-SHA512, which was never compliant with the W3C specification. `rsa-oaep-mgf1p` now produces MGF1-SHA1 ciphertext regardless of `keyEncryptionDigest`. Documents encrypted with the earlier behaviour will not decrypt with the current version; they were never interoperable with Java xmlsec, .NET `System.Security.Cryptography.Xml`, or other spec-compliant peers. Callers who genuinely need MGF1-SHA256 or MGF1-SHA512 should use `http://www.w3.org/2009/xmlenc11#rsa-oaep` with the `keyEncryptionMgf` option.

Expand Down
16 changes: 16 additions & 0 deletions lib/oaep.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
var crypto = require('crypto');

// CRYPTO MODULE (non-FIPS-validated)
// This file hand-rolls the RSA-OAEP padding (EME-OAEP encode/decode, RFC 8017
// §7.1) in JavaScript. It exists only to express the OAEP-digest / MGF1-digest combinations
// that Node's `crypto` cannot. Because the padding is computed here rather than inside OpenSSL,
// this is not a FIPS valid code path.

function assertShimAllowed() {
if (crypto.getFips?.()) {
var err = new Error('unsupported cryptographic operation');
err.code = 'ERR_XMLENC_FIPS_UNSUPPORTED';
throw err;
}
}

// MGF1 mask generation function (RFC 8017 B.2.1).
function mgf1(seed, length, hash) {
var hLen = crypto.createHash(hash).digest().length;
Expand Down Expand Up @@ -30,6 +44,7 @@ function decodingError() {
// chosen independently. Node's privateDecrypt cannot express that combination:
// it only sets the OAEP digest, and OpenSSL then defaults MGF1 to match it.
function privateDecryptOaep(privateKey, ciphertext, options) {
assertShimAllowed();
var opts = options || {};
var oaepHash = opts.oaepHash || 'sha1';
var mgf1Hash = opts.mgf1Hash || oaepHash;
Expand Down Expand Up @@ -84,6 +99,7 @@ function privateDecryptOaep(privateKey, ciphertext, options) {

// EME-OAEP-ENCODE (RFC 8017 7.1.1) followed by the raw RSA public operation.
function publicEncryptOaep(publicKey, message, options) {
assertShimAllowed();
var opts = options || {};
var oaepHash = opts.oaepHash || 'sha1';
var mgf1Hash = opts.mgf1Hash || oaepHash;
Expand Down
34 changes: 34 additions & 0 deletions test/oaep.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var assert = require('assert');
var crypto = require('crypto');
var sinon = require('sinon');
var oaep = require('../lib/oaep');

// Throwaway 2048-bit key + ciphertext from the ESD-63620 repro. Produced by:
Expand Down Expand Up @@ -148,6 +149,39 @@ describe('oaep', function () {
});
});

describe('FIPS mode gate', function () {
var fs = require('fs');
var pub = fs.readFileSync(__dirname + '/test-auth0_rsa.pub');
var key = fs.readFileSync(__dirname + '/test-auth0.key');

afterEach(function () {
sinon.restore();
});

it('refuses publicEncryptOaep when FIPS mode is enabled', function () {
sinon.stub(crypto, 'getFips').returns(1);
assert.throws(function () {
oaep.publicEncryptOaep(pub, Buffer.from('test'), { oaepHash: 'sha256', mgf1Hash: 'sha1' });
}, function (e) { return e.code === 'ERR_XMLENC_FIPS_UNSUPPORTED'; });
});

it('refuses privateDecryptOaep when FIPS mode is enabled', function () {
// Encrypt outside FIPS, then assert decrypt is blocked under FIPS.
var ct = oaep.publicEncryptOaep(pub, Buffer.from('test'), { oaepHash: 'sha256', mgf1Hash: 'sha1' });
sinon.stub(crypto, 'getFips').returns(1);
assert.throws(function () {
oaep.privateDecryptOaep(key, ct, { oaepHash: 'sha256', mgf1Hash: 'sha1' });
}, function (e) { return e.code === 'ERR_XMLENC_FIPS_UNSUPPORTED'; });
});

it('runs normally when FIPS mode is disabled', function () {
sinon.stub(crypto, 'getFips').returns(0);
var ct = oaep.publicEncryptOaep(pub, Buffer.from('test'), { oaepHash: 'sha256', mgf1Hash: 'sha1' });
var pt = oaep.privateDecryptOaep(key, ct, { oaepHash: 'sha256', mgf1Hash: 'sha1' });
assert.equal(pt.toString(), 'test');
});
});

describe('mgf1', function () {
it('matches the RFC 8017 counter construction for one block', function () {
var seed = Buffer.from('abc');
Expand Down