From 919372998c67dbdd4bb08a8fd75841069e3d29ae Mon Sep 17 00:00:00 2001 From: adamjmcgrath <1299658+adamjmcgrath@users.noreply.github.com> Date: Fri, 7 Aug 2026 15:26:29 +0000 Subject: [PATCH 1/3] chore(release): 6.0.0 --- CHANGELOG.md | 19 +++++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 028abd6..74e3046 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +## [6.0.0](https://github.com/auth0/node-xml-encryption/compare/v5.0.0...v6.0.0) (2026-08-07) + +### ⚠ BREAKING CHANGES + +* the rsa-oaep-mgf1p identifier fixes MGF1 to SHA-1 per +XML-Enc 1.1 section 5.5.2, and DigestMethod selects only the OAEP message +digest. Ciphertext produced by this library with keyEncryptionDigest sha256 +or sha512 (v3.1.0 through v5.0.0) used MGF1 matching the digest and no +longer decrypts; it was never interoperable with compliant peers. + +* fix!: emit MGF1-SHA1 ciphertext for rsa-oaep-mgf1p +* encrypting with keyEncryptionDigest sha256 or sha512 under +rsa-oaep-mgf1p now wraps the key with MGF1-SHA1, as the identifier requires. +Peers that adapted to the previous non-compliant output must switch to the + +### Features + +* mask generation should use sha1 for rsa-oaep-mgf1p ([#140](https://github.com/auth0/node-xml-encryption/issues/140)) ([48f0059](https://github.com/auth0/node-xml-encryption/commit/48f0059c149e3b3518c40b7e3209908ae4181bc5)), closes [xmlenc11#rsa-oaep](https://github.com/auth0/xmlenc11/issues/rsa-oaep) [xmlenc11#rsa-oaep](https://github.com/auth0/xmlenc11/issues/rsa-oaep) [xmlenc11#rsa-oaep](https://github.com/auth0/xmlenc11/issues/rsa-oaep) [xmlenc11#mgf1sha1](https://github.com/auth0/xmlenc11/issues/mgf1sha1) [xmlenc#MGF1withSHA1](https://github.com/auth0/xmlenc/issues/MGF1withSHA1) + ## [5.0.0](https://github.com/auth0/node-xml-encryption/compare/v4.0.1...v5.0.0) (2026-07-02) ### ⚠ BREAKING CHANGES diff --git a/package-lock.json b/package-lock.json index 9c32768..540f7fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "xml-encryption", - "version": "5.0.0", + "version": "6.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "xml-encryption", - "version": "5.0.0", + "version": "6.0.0", "license": "MIT", "dependencies": { "@xmldom/xmldom": "^0.8.13", diff --git a/package.json b/package.json index 0506a03..a989e9b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xml-encryption", - "version": "5.0.0", + "version": "6.0.0", "devDependencies": { "@commitlint/cli": "^20.3.1", "@commitlint/config-conventional": "^20.3.1", From d84f2dfc64f8c3ec316e7723c8c63c1780b880e8 Mon Sep 17 00:00:00 2001 From: Adam Mcgrath Date: Fri, 7 Aug 2026 16:43:08 +0100 Subject: [PATCH 2/3] chore: update CHANGELOG.md --- CHANGELOG.md | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74e3046..636519a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,20 +2,13 @@ ### ⚠ BREAKING CHANGES -* the rsa-oaep-mgf1p identifier fixes MGF1 to SHA-1 per -XML-Enc 1.1 section 5.5.2, and DigestMethod selects only the OAEP message -digest. Ciphertext produced by this library with keyEncryptionDigest sha256 -or sha512 (v3.1.0 through v5.0.0) used MGF1 matching the digest and no -longer decrypts; it was never interoperable with compliant peers. - -* fix!: emit MGF1-SHA1 ciphertext for rsa-oaep-mgf1p -* encrypting with keyEncryptionDigest sha256 or sha512 under -rsa-oaep-mgf1p now wraps the key with MGF1-SHA1, as the identifier requires. -Peers that adapted to the previous non-compliant output must switch to the +* **rsa-oaep-mgf1p now emits and expects MGF1-SHA1.** The `http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p` identifier fixes the mask generation function to MGF1-SHA1 per XML-Enc 1.1. Previously `keyEncryptionDigest: 'sha256'` or `'sha512'` also drove MGF1, producing MGF1-SHA256/SHA512 ciphertext that is not interoperable with spec-compliant peers (Java xmlsec, .NET `System.Security.Cryptography.Xml`). Such ciphertext will no longer decrypt with this version. Callers who genuinely need a non-SHA-1 MGF1 must switch to the `xmlenc11#rsa-oaep` identifier with the new `keyEncryptionMgf` option. ### Features -* mask generation should use sha1 for rsa-oaep-mgf1p ([#140](https://github.com/auth0/node-xml-encryption/issues/140)) ([48f0059](https://github.com/auth0/node-xml-encryption/commit/48f0059c149e3b3518c40b7e3209908ae4181bc5)), closes [xmlenc11#rsa-oaep](https://github.com/auth0/xmlenc11/issues/rsa-oaep) [xmlenc11#rsa-oaep](https://github.com/auth0/xmlenc11/issues/rsa-oaep) [xmlenc11#rsa-oaep](https://github.com/auth0/xmlenc11/issues/rsa-oaep) [xmlenc11#mgf1sha1](https://github.com/auth0/xmlenc11/issues/mgf1sha1) [xmlenc#MGF1withSHA1](https://github.com/auth0/xmlenc/issues/MGF1withSHA1) +* **Support `http://www.w3.org/2009/xmlenc11#rsa-oaep`** — The new `keyEncryptionMgf` option selects the MGF1 digest (`sha1`, `sha224`, `sha256`, `sha384`, `sha512`, or the full `xmlenc11#mgf1*` URI; default `sha1`). +* **OAEP label support** — supply `keyEncryptionOaepParams` (a Buffer or base64 string) to set the OAEP label; it is emitted as an `` element and honoured on decrypt. +* Digest/MGF1 combinations Node's `crypto` cannot express (MGF1 digest ≠ message digest) are computed via an EME-OAEP shim over the raw RSA primitive. ## [5.0.0](https://github.com/auth0/node-xml-encryption/compare/v4.0.1...v5.0.0) (2026-07-02) From e6ca2fbfebfad6d88d1d65f4cc85d5e8580efa53 Mon Sep 17 00:00:00 2001 From: Adam Mcgrath Date: Tue, 11 Aug 2026 09:52:33 +0100 Subject: [PATCH 3/3] chore: block js RSA-OAEP padding combination on FIPS (#145) * chore: block js RSA-OAEP padding combination on FIPS * chore: update fips check Co-authored-by: Filip Skokan * chore: add note about FIPs in readme --------- Co-authored-by: Filip Skokan --- README.md | 2 +- lib/oaep.js | 16 ++++++++++++++++ test/oaep.js | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 80d0959..b901536 100644 --- a/README.md +++ b/README.md @@ -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 `` 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. diff --git a/lib/oaep.js b/lib/oaep.js index 78d3659..093c7d2 100644 --- a/lib/oaep.js +++ b/lib/oaep.js @@ -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; @@ -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; @@ -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; diff --git a/test/oaep.js b/test/oaep.js index a259d4a..e7b96be 100644 --- a/test/oaep.js +++ b/test/oaep.js @@ -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: @@ -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');