Closed
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
10 changes: 7 additions & 3 deletions deps/ncrypto/ncrypto.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -5650,9 +5650,11 @@ DataPointer RSA_Cipher(const EVPKeyPointer& key,
if (!key) return {};
EVPKeyCtxPointer ctx = key.newCtx();

const Digest& mgf1_digest =
params.mgf1_digest != nullptr ? params.mgf1_digest : params.digest;
if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) ||
(params.digest != nullptr && (!ctx.setRsaOaepMd(params.digest) ||
!ctx.setRsaMgf1Md(params.digest)))) {
(params.digest != nullptr &&
(!ctx.setRsaOaepMd(params.digest) || !ctx.setRsaMgf1Md(mgf1_digest)))) {
return {};
}

Expand DownExpand Up@@ -5691,7 +5693,9 @@ DataPointer CipherImpl(const EVPKeyPointer& key,
if (!key) return {};
EVPKeyCtxPointer ctx = key.newCtx();
if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) ||
(params.digest != nullptr && !ctx.setRsaOaepMd(params.digest))) {
(params.digest != nullptr && !ctx.setRsaOaepMd(params.digest)) ||
(params.mgf1_digest != nullptr &&
!ctx.setRsaMgf1Md(params.mgf1_digest))) {
return {};
}

Expand Down
1 change: 1 addition & 0 deletions deps/ncrypto/ncrypto.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -508,6 +508,7 @@ class Cipher final {
struct CipherParams {
int padding;
Digest digest;
Digest mgf1_digest;
const Buffer<const void> label;
};

Expand Down
20 changes: 16 additions & 4 deletions doc/api/crypto.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -5340,6 +5340,9 @@ An array of supported digest functions can be retrieved using
<!-- YAML
added: v0.11.14
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65073
description: The `mgf1Hash` option was added.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63188
description: Passing a CryptoKey as `privateKey` is no longer supported.
Expand DownExpand Up@@ -5370,8 +5373,11 @@ changes:
<!--lint disable maximum-line-length remark-lint-->

* `privateKey` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject|URL}
* `oaepHash` {string} The hash function to use for OAEP padding and MGF1.
**Default:** `'sha1'`
* `oaepHash` {string} The hash function to use for OAEP padding and, unless
`mgf1Hash` is set, MGF1. **Default:** `'sha1'`
* `mgf1Hash` {string} The hash function to use for the MGF1 mask generation
function of OAEP padding. If not specified, the value of `oaepHash` is used.
This allows the OAEP digest and the MGF1 digest to differ.
* `oaepLabel` {string|ArrayBuffer|Buffer|TypedArray|DataView} The label to
use for OAEP padding. If not specified, no label is used.
* `padding` {crypto.constants} An optional padding value defined in
Expand DownExpand Up@@ -5491,6 +5497,9 @@ be passed instead of a public key.
<!-- YAML
added: v0.11.14
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65073
description: The `mgf1Hash` option was added.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63188
description: Passing a CryptoKey as `key` is no longer supported.
Expand All@@ -5516,8 +5525,11 @@ changes:
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
* `key` {string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
A PEM encoded public or private key, or {KeyObject}.
* `oaepHash` {string} The hash function to use for OAEP padding and MGF1.
**Default:** `'sha1'`
* `oaepHash` {string} The hash function to use for OAEP padding and, unless
`mgf1Hash` is set, MGF1. **Default:** `'sha1'`
* `mgf1Hash` {string} The hash function to use for the MGF1 mask generation
function of OAEP padding. If not specified, the value of `oaepHash` is used.
This allows the OAEP digest and the MGF1 digest to differ.
* `oaepLabel` {string|ArrayBuffer|Buffer|TypedArray|DataView} The label to
use for OAEP padding. If not specified, no label is used.
* `passphrase` {string|ArrayBuffer|Buffer|TypedArray|DataView} An optional
Expand Down
6 changes: 4 additions & 2 deletions lib/internal/crypto/cipher.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -68,15 +68,17 @@ function rsaFunctionFor(method, defaultPadding, keyType) {
preparePrivateKey(key, keyName) :
preparePublicOrPrivateKey(key, keyName);
const padding = key.padding || defaultPadding;
const { oaepHash, encoding } = key;
const { oaepHash, mgf1Hash, encoding } = key;
let { oaepLabel } = key;
if (oaepHash !== undefined)
validateString(oaepHash, 'key.oaepHash');
if (mgf1Hash !== undefined)
validateString(mgf1Hash, 'key.mgf1Hash');
if (oaepLabel !== undefined)
oaepLabel = getArrayBufferOrView(oaepLabel, 'key.oaepLabel', encoding);
buffer = getArrayBufferOrView(buffer, 'buffer', encoding);
return method(data, format, type, passphrase, namedCurve, buffer,
padding, oaepHash, oaepLabel);
padding, oaepHash, oaepLabel, mgf1Hash);
};
}

Expand Down
13 changes: 12 additions & 1 deletion src/crypto/crypto_cipher.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -801,6 +801,7 @@ bool PublicKeyCipher::Cipher(
const EVPKeyPointer& pkey,
int padding,
const Digest& digest,
const Digest& mgf1_digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
std::unique_ptr<BackingStore>* out) {
Expand All@@ -810,6 +811,7 @@ bool PublicKeyCipher::Cipher(
const ncrypto::Cipher::CipherParams params{
.padding = padding,
.digest = digest,
.mgf1_digest = mgf1_digest,
.label = label,
};

Expand DownExpand Up@@ -882,8 +884,17 @@ void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& args) {
if (!oaep_label.CheckSizeInt32()) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "oaepLabel is too big");
}

Digest mgf1_digest;
if (args[offset + 4]->IsString()) {
Utf8Value mgf1_str(env->isolate(), args[offset + 4]);
mgf1_digest = Digest::FromName(*mgf1_str);
if (!mgf1_digest) return THROW_ERR_OSSL_EVP_INVALID_DIGEST(env);
}

std::unique_ptr<BackingStore> out;
if (!Cipher<cipher>(env, pkey, padding, digest, oaep_label, buf, &out)) {
if (!Cipher<cipher>(
env, pkey, padding, digest, mgf1_digest, oaep_label, buf, &out)) {
return ThrowCryptoError(env, ERR_get_error());
}

Expand Down
1 change: 1 addition & 0 deletions src/crypto/crypto_cipher.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,6 +106,7 @@ class PublicKeyCipher {
const ncrypto::EVPKeyPointer& pkey,
int padding,
const ncrypto::Digest& digest,
const ncrypto::Digest& mgf1_digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
std::unique_ptr<v8::BackingStore>* out);
Expand Down
1 change: 1 addition & 0 deletions src/crypto/crypto_rsa.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,7 @@ WebCryptoCipherStatus RSA_Cipher(Environment* env,
const ncrypto::Rsa::CipherParams nparams{
.padding = params.padding,
.digest = params.digest,
.mgf1_digest = params.digest,
.label = params.label,
};

Expand Down
149 changes: 149 additions & 0 deletions test/parallel/test-crypto-rsa-oaep-mgf1.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

// Tests the `mgf1Hash` option of crypto.publicEncrypt() and
// crypto.privateDecrypt(), which allows the MGF1 digest of RSA-OAEP padding to
// differ from the OAEP message digest (`oaepHash`). This is required for
// interoperability with profiles such as XML Encryption's `rsa-oaep-mgf1p`,
// where the OAEP digest may be changed but MGF1 is fixed to SHA-1.

const assert = require('assert');
const crypto = require('crypto');
const fixtures = require('../common/fixtures');
const { hasFIPS } = require('../common/crypto');

const constants = crypto.constants;

const publicKey = fixtures.readKey('rsa_public.pem', 'ascii');
const privateKey = fixtures.readKey('rsa_private.pem', 'ascii');

const input = Buffer.from('the quick brown fox jumps over the lazy dog');

// A round-trip with mismatched OAEP and MGF1 digests must succeed when both
// sides agree on the digests.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// mgf1Hash actually affects the padding: a ciphertext produced with
// oaepHash=sha256 and mgf1Hash=sha1 must NOT decrypt when MGF1 defaults to the
// OAEP digest (sha256), which is the pre-existing behavior.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, input);

assert.throws(() => {
crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
// No mgf1Hash: MGF1 follows oaepHash (sha256) and must fail to unpad.
}, encrypted);
}, {
code: hasFIPS(3, 5) ? 'ERR_OSSL_EVP_PROVIDER_ASYM_CIPHER_FAILURE' :
'ERR_OSSL_RSA_OAEP_DECODING_ERROR'
});
}

// Backward compatibility: omitting mgf1Hash on both sides keeps MGF1 == oaepHash
// (the historical behavior), so this round-trips, and setting mgf1Hash equal to
// oaepHash is equivalent to omitting it.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha256',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// The default oaepHash is sha1, so mgf1Hash defaults to sha1 as well. A
// ciphertext encrypted with all defaults must decrypt with an explicit
// mgf1Hash: 'sha1'.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
mgf1Hash: 'sha1',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// A few other digest combinations round-trip.
for (const [oaepHash, mgf1Hash] of [
['sha512', 'sha1'],
['sha384', 'sha256'],
['sha1', 'sha256'],
]) {
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash,
mgf1Hash,
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash,
mgf1Hash,
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// mgf1Hash must be a string.
for (const mgf1Hash of [1, true, {}, [], null]) {
assert.throws(() => {
crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash,
}, input);
}, { code: 'ERR_INVALID_ARG_TYPE' });
}

// An unknown mgf1Hash digest name is rejected.
assert.throws(() => {
crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'not-a-real-digest',
}, input);
}, { code: 'ERR_OSSL_EVP_INVALID_DIGEST' });
1 change: 1 addition & 0 deletions typings/internalBinding/crypto.d.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -767,6 +767,7 @@ declare namespace InternalCryptoBinding {
padding: number,
oaepHash: string | undefined,
oaepLabel: OptionalByteSource,
mgf1Hash: string | undefined,
]
) => Buffer;
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Closed
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
10 changes: 7 additions & 3 deletions deps/ncrypto/ncrypto.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -5650,9 +5650,11 @@ DataPointer RSA_Cipher(const EVPKeyPointer& key,
if (!key) return {};
EVPKeyCtxPointer ctx = key.newCtx();

const Digest& mgf1_digest =
params.mgf1_digest != nullptr ? params.mgf1_digest : params.digest;
if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) ||
(params.digest != nullptr && (!ctx.setRsaOaepMd(params.digest) ||
!ctx.setRsaMgf1Md(params.digest)))) {
(params.digest != nullptr &&
(!ctx.setRsaOaepMd(params.digest) || !ctx.setRsaMgf1Md(mgf1_digest)))) {
return {};
}

Expand DownExpand Up@@ -5691,7 +5693,9 @@ DataPointer CipherImpl(const EVPKeyPointer& key,
if (!key) return {};
EVPKeyCtxPointer ctx = key.newCtx();
if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) ||
(params.digest != nullptr && !ctx.setRsaOaepMd(params.digest))) {
(params.digest != nullptr && !ctx.setRsaOaepMd(params.digest)) ||
(params.mgf1_digest != nullptr &&
!ctx.setRsaMgf1Md(params.mgf1_digest))) {
return {};
}

Expand Down
1 change: 1 addition & 0 deletions deps/ncrypto/ncrypto.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -508,6 +508,7 @@ class Cipher final {
struct CipherParams {
int padding;
Digest digest;
Digest mgf1_digest;
const Buffer<const void> label;
};

Expand Down
20 changes: 16 additions & 4 deletions doc/api/crypto.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -5340,6 +5340,9 @@ An array of supported digest functions can be retrieved using
<!-- YAML
added: v0.11.14
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65073
description: The `mgf1Hash` option was added.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63188
description: Passing a CryptoKey as `privateKey` is no longer supported.
Expand DownExpand Up@@ -5370,8 +5373,11 @@ changes:
<!--lint disable maximum-line-length remark-lint-->

* `privateKey` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject|URL}
* `oaepHash` {string} The hash function to use for OAEP padding and MGF1.
**Default:** `'sha1'`
* `oaepHash` {string} The hash function to use for OAEP padding and, unless
`mgf1Hash` is set, MGF1. **Default:** `'sha1'`
* `mgf1Hash` {string} The hash function to use for the MGF1 mask generation
function of OAEP padding. If not specified, the value of `oaepHash` is used.
This allows the OAEP digest and the MGF1 digest to differ.
* `oaepLabel` {string|ArrayBuffer|Buffer|TypedArray|DataView} The label to
use for OAEP padding. If not specified, no label is used.
* `padding` {crypto.constants} An optional padding value defined in
Expand DownExpand Up@@ -5491,6 +5497,9 @@ be passed instead of a public key.
<!-- YAML
added: v0.11.14
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65073
description: The `mgf1Hash` option was added.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63188
description: Passing a CryptoKey as `key` is no longer supported.
Expand All@@ -5516,8 +5525,11 @@ changes:
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
* `key` {string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
A PEM encoded public or private key, or {KeyObject}.
* `oaepHash` {string} The hash function to use for OAEP padding and MGF1.
**Default:** `'sha1'`
* `oaepHash` {string} The hash function to use for OAEP padding and, unless
`mgf1Hash` is set, MGF1. **Default:** `'sha1'`
* `mgf1Hash` {string} The hash function to use for the MGF1 mask generation
function of OAEP padding. If not specified, the value of `oaepHash` is used.
This allows the OAEP digest and the MGF1 digest to differ.
* `oaepLabel` {string|ArrayBuffer|Buffer|TypedArray|DataView} The label to
use for OAEP padding. If not specified, no label is used.
* `passphrase` {string|ArrayBuffer|Buffer|TypedArray|DataView} An optional
Expand Down
6 changes: 4 additions & 2 deletions lib/internal/crypto/cipher.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -68,15 +68,17 @@ function rsaFunctionFor(method, defaultPadding, keyType) {
preparePrivateKey(key, keyName) :
preparePublicOrPrivateKey(key, keyName);
const padding = key.padding || defaultPadding;
const { oaepHash, encoding } = key;
const { oaepHash, mgf1Hash, encoding } = key;
let { oaepLabel } = key;
if (oaepHash !== undefined)
validateString(oaepHash, 'key.oaepHash');
if (mgf1Hash !== undefined)
validateString(mgf1Hash, 'key.mgf1Hash');
if (oaepLabel !== undefined)
oaepLabel = getArrayBufferOrView(oaepLabel, 'key.oaepLabel', encoding);
buffer = getArrayBufferOrView(buffer, 'buffer', encoding);
return method(data, format, type, passphrase, namedCurve, buffer,
padding, oaepHash, oaepLabel);
padding, oaepHash, oaepLabel, mgf1Hash);
};
}

Expand Down
13 changes: 12 additions & 1 deletion src/crypto/crypto_cipher.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -801,6 +801,7 @@ bool PublicKeyCipher::Cipher(
const EVPKeyPointer& pkey,
int padding,
const Digest& digest,
const Digest& mgf1_digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
std::unique_ptr<BackingStore>* out) {
Expand All@@ -810,6 +811,7 @@ bool PublicKeyCipher::Cipher(
const ncrypto::Cipher::CipherParams params{
.padding = padding,
.digest = digest,
.mgf1_digest = mgf1_digest,
.label = label,
};

Expand DownExpand Up@@ -882,8 +884,17 @@ void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& args) {
if (!oaep_label.CheckSizeInt32()) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "oaepLabel is too big");
}

Digest mgf1_digest;
if (args[offset + 4]->IsString()) {
Utf8Value mgf1_str(env->isolate(), args[offset + 4]);
mgf1_digest = Digest::FromName(*mgf1_str);
if (!mgf1_digest) return THROW_ERR_OSSL_EVP_INVALID_DIGEST(env);
}

std::unique_ptr<BackingStore> out;
if (!Cipher<cipher>(env, pkey, padding, digest, oaep_label, buf, &out)) {
if (!Cipher<cipher>(
env, pkey, padding, digest, mgf1_digest, oaep_label, buf, &out)) {
return ThrowCryptoError(env, ERR_get_error());
}

Expand Down
1 change: 1 addition & 0 deletions src/crypto/crypto_cipher.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,6 +106,7 @@ class PublicKeyCipher {
const ncrypto::EVPKeyPointer& pkey,
int padding,
const ncrypto::Digest& digest,
const ncrypto::Digest& mgf1_digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
std::unique_ptr<v8::BackingStore>* out);
Expand Down
1 change: 1 addition & 0 deletions src/crypto/crypto_rsa.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,7 @@ WebCryptoCipherStatus RSA_Cipher(Environment* env,
const ncrypto::Rsa::CipherParams nparams{
.padding = params.padding,
.digest = params.digest,
.mgf1_digest = params.digest,
.label = params.label,
};

Expand Down
149 changes: 149 additions & 0 deletions test/parallel/test-crypto-rsa-oaep-mgf1.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

// Tests the `mgf1Hash` option of crypto.publicEncrypt() and
// crypto.privateDecrypt(), which allows the MGF1 digest of RSA-OAEP padding to
// differ from the OAEP message digest (`oaepHash`). This is required for
// interoperability with profiles such as XML Encryption's `rsa-oaep-mgf1p`,
// where the OAEP digest may be changed but MGF1 is fixed to SHA-1.

const assert = require('assert');
const crypto = require('crypto');
const fixtures = require('../common/fixtures');
const { hasFIPS } = require('../common/crypto');

const constants = crypto.constants;

const publicKey = fixtures.readKey('rsa_public.pem', 'ascii');
const privateKey = fixtures.readKey('rsa_private.pem', 'ascii');

const input = Buffer.from('the quick brown fox jumps over the lazy dog');

// A round-trip with mismatched OAEP and MGF1 digests must succeed when both
// sides agree on the digests.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// mgf1Hash actually affects the padding: a ciphertext produced with
// oaepHash=sha256 and mgf1Hash=sha1 must NOT decrypt when MGF1 defaults to the
// OAEP digest (sha256), which is the pre-existing behavior.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, input);

assert.throws(() => {
crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
// No mgf1Hash: MGF1 follows oaepHash (sha256) and must fail to unpad.
}, encrypted);
}, {
code: hasFIPS(3, 5) ? 'ERR_OSSL_EVP_PROVIDER_ASYM_CIPHER_FAILURE' :
'ERR_OSSL_RSA_OAEP_DECODING_ERROR'
});
}

// Backward compatibility: omitting mgf1Hash on both sides keeps MGF1 == oaepHash
// (the historical behavior), so this round-trips, and setting mgf1Hash equal to
// oaepHash is equivalent to omitting it.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha256',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// The default oaepHash is sha1, so mgf1Hash defaults to sha1 as well. A
// ciphertext encrypted with all defaults must decrypt with an explicit
// mgf1Hash: 'sha1'.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
mgf1Hash: 'sha1',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// A few other digest combinations round-trip.
for (const [oaepHash, mgf1Hash] of [
['sha512', 'sha1'],
['sha384', 'sha256'],
['sha1', 'sha256'],
]) {
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash,
mgf1Hash,
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash,
mgf1Hash,
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// mgf1Hash must be a string.
for (const mgf1Hash of [1, true, {}, [], null]) {
assert.throws(() => {
crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash,
}, input);
}, { code: 'ERR_INVALID_ARG_TYPE' });
}

// An unknown mgf1Hash digest name is rejected.
assert.throws(() => {
crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'not-a-real-digest',
}, input);
}, { code: 'ERR_OSSL_EVP_INVALID_DIGEST' });
1 change: 1 addition & 0 deletions typings/internalBinding/crypto.d.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -767,6 +767,7 @@ declare namespace InternalCryptoBinding {
padding: number,
oaepHash: string | undefined,
oaepLabel: OptionalByteSource,
mgf1Hash: string | undefined,
]
) => Buffer;
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
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
10 changes: 7 additions & 3 deletions deps/ncrypto/ncrypto.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -5650,9 +5650,11 @@ DataPointer RSA_Cipher(const EVPKeyPointer& key,
if (!key) return {};
EVPKeyCtxPointer ctx = key.newCtx();

const Digest& mgf1_digest =
params.mgf1_digest != nullptr ? params.mgf1_digest : params.digest;
if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) ||
(params.digest != nullptr && (!ctx.setRsaOaepMd(params.digest) ||
!ctx.setRsaMgf1Md(params.digest)))) {
(params.digest != nullptr &&
(!ctx.setRsaOaepMd(params.digest) || !ctx.setRsaMgf1Md(mgf1_digest)))) {
return {};
}

Expand DownExpand Up@@ -5691,7 +5693,9 @@ DataPointer CipherImpl(const EVPKeyPointer& key,
if (!key) return {};
EVPKeyCtxPointer ctx = key.newCtx();
if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) ||
(params.digest != nullptr && !ctx.setRsaOaepMd(params.digest))) {
(params.digest != nullptr && !ctx.setRsaOaepMd(params.digest)) ||
(params.mgf1_digest != nullptr &&
!ctx.setRsaMgf1Md(params.mgf1_digest))) {
return {};
}

Expand Down
1 change: 1 addition & 0 deletions deps/ncrypto/ncrypto.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -508,6 +508,7 @@ class Cipher final {
struct CipherParams {
int padding;
Digest digest;
Digest mgf1_digest;
const Buffer<const void> label;
};

Expand Down
20 changes: 16 additions & 4 deletions doc/api/crypto.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -5340,6 +5340,9 @@ An array of supported digest functions can be retrieved using
<!-- YAML
added: v0.11.14
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65073
description: The `mgf1Hash` option was added.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63188
description: Passing a CryptoKey as `privateKey` is no longer supported.
Expand DownExpand Up@@ -5370,8 +5373,11 @@ changes:
<!--lint disable maximum-line-length remark-lint-->

* `privateKey` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject|URL}
* `oaepHash` {string} The hash function to use for OAEP padding and MGF1.
**Default:** `'sha1'`
* `oaepHash` {string} The hash function to use for OAEP padding and, unless
`mgf1Hash` is set, MGF1. **Default:** `'sha1'`
* `mgf1Hash` {string} The hash function to use for the MGF1 mask generation
function of OAEP padding. If not specified, the value of `oaepHash` is used.
This allows the OAEP digest and the MGF1 digest to differ.
* `oaepLabel` {string|ArrayBuffer|Buffer|TypedArray|DataView} The label to
use for OAEP padding. If not specified, no label is used.
* `padding` {crypto.constants} An optional padding value defined in
Expand DownExpand Up@@ -5491,6 +5497,9 @@ be passed instead of a public key.
<!-- YAML
added: v0.11.14
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65073
description: The `mgf1Hash` option was added.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63188
description: Passing a CryptoKey as `key` is no longer supported.
Expand All@@ -5516,8 +5525,11 @@ changes:
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
* `key` {string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
A PEM encoded public or private key, or {KeyObject}.
* `oaepHash` {string} The hash function to use for OAEP padding and MGF1.
**Default:** `'sha1'`
* `oaepHash` {string} The hash function to use for OAEP padding and, unless
`mgf1Hash` is set, MGF1. **Default:** `'sha1'`
* `mgf1Hash` {string} The hash function to use for the MGF1 mask generation
function of OAEP padding. If not specified, the value of `oaepHash` is used.
This allows the OAEP digest and the MGF1 digest to differ.
* `oaepLabel` {string|ArrayBuffer|Buffer|TypedArray|DataView} The label to
use for OAEP padding. If not specified, no label is used.
* `passphrase` {string|ArrayBuffer|Buffer|TypedArray|DataView} An optional
Expand Down
6 changes: 4 additions & 2 deletions lib/internal/crypto/cipher.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -68,15 +68,17 @@ function rsaFunctionFor(method, defaultPadding, keyType) {
preparePrivateKey(key, keyName) :
preparePublicOrPrivateKey(key, keyName);
const padding = key.padding || defaultPadding;
const { oaepHash, encoding } = key;
const { oaepHash, mgf1Hash, encoding } = key;
let { oaepLabel } = key;
if (oaepHash !== undefined)
validateString(oaepHash, 'key.oaepHash');
if (mgf1Hash !== undefined)
validateString(mgf1Hash, 'key.mgf1Hash');
if (oaepLabel !== undefined)
oaepLabel = getArrayBufferOrView(oaepLabel, 'key.oaepLabel', encoding);
buffer = getArrayBufferOrView(buffer, 'buffer', encoding);
return method(data, format, type, passphrase, namedCurve, buffer,
padding, oaepHash, oaepLabel);
padding, oaepHash, oaepLabel, mgf1Hash);
};
}

Expand Down
13 changes: 12 additions & 1 deletion src/crypto/crypto_cipher.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -801,6 +801,7 @@ bool PublicKeyCipher::Cipher(
const EVPKeyPointer& pkey,
int padding,
const Digest& digest,
const Digest& mgf1_digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
std::unique_ptr<BackingStore>* out) {
Expand All@@ -810,6 +811,7 @@ bool PublicKeyCipher::Cipher(
const ncrypto::Cipher::CipherParams params{
.padding = padding,
.digest = digest,
.mgf1_digest = mgf1_digest,
.label = label,
};

Expand DownExpand Up@@ -882,8 +884,17 @@ void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& args) {
if (!oaep_label.CheckSizeInt32()) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "oaepLabel is too big");
}

Digest mgf1_digest;
if (args[offset + 4]->IsString()) {
Utf8Value mgf1_str(env->isolate(), args[offset + 4]);
mgf1_digest = Digest::FromName(*mgf1_str);
if (!mgf1_digest) return THROW_ERR_OSSL_EVP_INVALID_DIGEST(env);
}

std::unique_ptr<BackingStore> out;
if (!Cipher<cipher>(env, pkey, padding, digest, oaep_label, buf, &out)) {
if (!Cipher<cipher>(
env, pkey, padding, digest, mgf1_digest, oaep_label, buf, &out)) {
return ThrowCryptoError(env, ERR_get_error());
}

Expand Down
1 change: 1 addition & 0 deletions src/crypto/crypto_cipher.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,6 +106,7 @@ class PublicKeyCipher {
const ncrypto::EVPKeyPointer& pkey,
int padding,
const ncrypto::Digest& digest,
const ncrypto::Digest& mgf1_digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
std::unique_ptr<v8::BackingStore>* out);
Expand Down
1 change: 1 addition & 0 deletions src/crypto/crypto_rsa.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,7 @@ WebCryptoCipherStatus RSA_Cipher(Environment* env,
const ncrypto::Rsa::CipherParams nparams{
.padding = params.padding,
.digest = params.digest,
.mgf1_digest = params.digest,
.label = params.label,
};

Expand Down
149 changes: 149 additions & 0 deletions test/parallel/test-crypto-rsa-oaep-mgf1.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

// Tests the `mgf1Hash` option of crypto.publicEncrypt() and
// crypto.privateDecrypt(), which allows the MGF1 digest of RSA-OAEP padding to
// differ from the OAEP message digest (`oaepHash`). This is required for
// interoperability with profiles such as XML Encryption's `rsa-oaep-mgf1p`,
// where the OAEP digest may be changed but MGF1 is fixed to SHA-1.

const assert = require('assert');
const crypto = require('crypto');
const fixtures = require('../common/fixtures');
const { hasFIPS } = require('../common/crypto');

const constants = crypto.constants;

const publicKey = fixtures.readKey('rsa_public.pem', 'ascii');
const privateKey = fixtures.readKey('rsa_private.pem', 'ascii');

const input = Buffer.from('the quick brown fox jumps over the lazy dog');

// A round-trip with mismatched OAEP and MGF1 digests must succeed when both
// sides agree on the digests.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// mgf1Hash actually affects the padding: a ciphertext produced with
// oaepHash=sha256 and mgf1Hash=sha1 must NOT decrypt when MGF1 defaults to the
// OAEP digest (sha256), which is the pre-existing behavior.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, input);

assert.throws(() => {
crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
// No mgf1Hash: MGF1 follows oaepHash (sha256) and must fail to unpad.
}, encrypted);
}, {
code: hasFIPS(3, 5) ? 'ERR_OSSL_EVP_PROVIDER_ASYM_CIPHER_FAILURE' :
'ERR_OSSL_RSA_OAEP_DECODING_ERROR'
});
}

// Backward compatibility: omitting mgf1Hash on both sides keeps MGF1 == oaepHash
// (the historical behavior), so this round-trips, and setting mgf1Hash equal to
// oaepHash is equivalent to omitting it.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha256',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// The default oaepHash is sha1, so mgf1Hash defaults to sha1 as well. A
// ciphertext encrypted with all defaults must decrypt with an explicit
// mgf1Hash: 'sha1'.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
mgf1Hash: 'sha1',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// A few other digest combinations round-trip.
for (const [oaepHash, mgf1Hash] of [
['sha512', 'sha1'],
['sha384', 'sha256'],
['sha1', 'sha256'],
]) {
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash,
mgf1Hash,
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash,
mgf1Hash,
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// mgf1Hash must be a string.
for (const mgf1Hash of [1, true, {}, [], null]) {
assert.throws(() => {
crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash,
}, input);
}, { code: 'ERR_INVALID_ARG_TYPE' });
}

// An unknown mgf1Hash digest name is rejected.
assert.throws(() => {
crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'not-a-real-digest',
}, input);
}, { code: 'ERR_OSSL_EVP_INVALID_DIGEST' });
1 change: 1 addition & 0 deletions typings/internalBinding/crypto.d.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -767,6 +767,7 @@ declare namespace InternalCryptoBinding {
padding: number,
oaepHash: string | undefined,
oaepLabel: OptionalByteSource,
mgf1Hash: string | undefined,
]
) => Buffer;
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
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
10 changes: 7 additions & 3 deletions deps/ncrypto/ncrypto.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -5650,9 +5650,11 @@ DataPointer RSA_Cipher(const EVPKeyPointer& key,
if (!key) return {};
EVPKeyCtxPointer ctx = key.newCtx();

const Digest& mgf1_digest =
params.mgf1_digest != nullptr ? params.mgf1_digest : params.digest;
if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) ||
(params.digest != nullptr && (!ctx.setRsaOaepMd(params.digest) ||
!ctx.setRsaMgf1Md(params.digest)))) {
(params.digest != nullptr &&
(!ctx.setRsaOaepMd(params.digest) || !ctx.setRsaMgf1Md(mgf1_digest)))) {
return {};
}

Expand DownExpand Up@@ -5691,7 +5693,9 @@ DataPointer CipherImpl(const EVPKeyPointer& key,
if (!key) return {};
EVPKeyCtxPointer ctx = key.newCtx();
if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) ||
(params.digest != nullptr && !ctx.setRsaOaepMd(params.digest))) {
(params.digest != nullptr && !ctx.setRsaOaepMd(params.digest)) ||
(params.mgf1_digest != nullptr &&
!ctx.setRsaMgf1Md(params.mgf1_digest))) {
return {};
}

Expand Down
1 change: 1 addition & 0 deletions deps/ncrypto/ncrypto.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -508,6 +508,7 @@ class Cipher final {
struct CipherParams {
int padding;
Digest digest;
Digest mgf1_digest;
const Buffer<const void> label;
};

Expand Down
20 changes: 16 additions & 4 deletions doc/api/crypto.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -5340,6 +5340,9 @@ An array of supported digest functions can be retrieved using
<!-- YAML
added: v0.11.14
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65073
description: The `mgf1Hash` option was added.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63188
description: Passing a CryptoKey as `privateKey` is no longer supported.
Expand DownExpand Up@@ -5370,8 +5373,11 @@ changes:
<!--lint disable maximum-line-length remark-lint-->

* `privateKey` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject|URL}
* `oaepHash` {string} The hash function to use for OAEP padding and MGF1.
**Default:** `'sha1'`
* `oaepHash` {string} The hash function to use for OAEP padding and, unless
`mgf1Hash` is set, MGF1. **Default:** `'sha1'`
* `mgf1Hash` {string} The hash function to use for the MGF1 mask generation
function of OAEP padding. If not specified, the value of `oaepHash` is used.
This allows the OAEP digest and the MGF1 digest to differ.
* `oaepLabel` {string|ArrayBuffer|Buffer|TypedArray|DataView} The label to
use for OAEP padding. If not specified, no label is used.
* `padding` {crypto.constants} An optional padding value defined in
Expand DownExpand Up@@ -5491,6 +5497,9 @@ be passed instead of a public key.
<!-- YAML
added: v0.11.14
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65073
description: The `mgf1Hash` option was added.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63188
description: Passing a CryptoKey as `key` is no longer supported.
Expand All@@ -5516,8 +5525,11 @@ changes:
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
* `key` {string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
A PEM encoded public or private key, or {KeyObject}.
* `oaepHash` {string} The hash function to use for OAEP padding and MGF1.
**Default:** `'sha1'`
* `oaepHash` {string} The hash function to use for OAEP padding and, unless
`mgf1Hash` is set, MGF1. **Default:** `'sha1'`
* `mgf1Hash` {string} The hash function to use for the MGF1 mask generation
function of OAEP padding. If not specified, the value of `oaepHash` is used.
This allows the OAEP digest and the MGF1 digest to differ.
* `oaepLabel` {string|ArrayBuffer|Buffer|TypedArray|DataView} The label to
use for OAEP padding. If not specified, no label is used.
* `passphrase` {string|ArrayBuffer|Buffer|TypedArray|DataView} An optional
Expand Down
6 changes: 4 additions & 2 deletions lib/internal/crypto/cipher.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -68,15 +68,17 @@ function rsaFunctionFor(method, defaultPadding, keyType) {
preparePrivateKey(key, keyName) :
preparePublicOrPrivateKey(key, keyName);
const padding = key.padding || defaultPadding;
const { oaepHash, encoding } = key;
const { oaepHash, mgf1Hash, encoding } = key;
let { oaepLabel } = key;
if (oaepHash !== undefined)
validateString(oaepHash, 'key.oaepHash');
if (mgf1Hash !== undefined)
validateString(mgf1Hash, 'key.mgf1Hash');
if (oaepLabel !== undefined)
oaepLabel = getArrayBufferOrView(oaepLabel, 'key.oaepLabel', encoding);
buffer = getArrayBufferOrView(buffer, 'buffer', encoding);
return method(data, format, type, passphrase, namedCurve, buffer,
padding, oaepHash, oaepLabel);
padding, oaepHash, oaepLabel, mgf1Hash);
};
}

Expand Down
13 changes: 12 additions & 1 deletion src/crypto/crypto_cipher.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -801,6 +801,7 @@ bool PublicKeyCipher::Cipher(
const EVPKeyPointer& pkey,
int padding,
const Digest& digest,
const Digest& mgf1_digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
std::unique_ptr<BackingStore>* out) {
Expand All@@ -810,6 +811,7 @@ bool PublicKeyCipher::Cipher(
const ncrypto::Cipher::CipherParams params{
.padding = padding,
.digest = digest,
.mgf1_digest = mgf1_digest,
.label = label,
};

Expand DownExpand Up@@ -882,8 +884,17 @@ void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& args) {
if (!oaep_label.CheckSizeInt32()) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "oaepLabel is too big");
}

Digest mgf1_digest;
if (args[offset + 4]->IsString()) {
Utf8Value mgf1_str(env->isolate(), args[offset + 4]);
mgf1_digest = Digest::FromName(*mgf1_str);
if (!mgf1_digest) return THROW_ERR_OSSL_EVP_INVALID_DIGEST(env);
}

std::unique_ptr<BackingStore> out;
if (!Cipher<cipher>(env, pkey, padding, digest, oaep_label, buf, &out)) {
if (!Cipher<cipher>(
env, pkey, padding, digest, mgf1_digest, oaep_label, buf, &out)) {
return ThrowCryptoError(env, ERR_get_error());
}

Expand Down
1 change: 1 addition & 0 deletions src/crypto/crypto_cipher.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,6 +106,7 @@ class PublicKeyCipher {
const ncrypto::EVPKeyPointer& pkey,
int padding,
const ncrypto::Digest& digest,
const ncrypto::Digest& mgf1_digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
std::unique_ptr<v8::BackingStore>* out);
Expand Down
1 change: 1 addition & 0 deletions src/crypto/crypto_rsa.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,7 @@ WebCryptoCipherStatus RSA_Cipher(Environment* env,
const ncrypto::Rsa::CipherParams nparams{
.padding = params.padding,
.digest = params.digest,
.mgf1_digest = params.digest,
.label = params.label,
};

Expand Down
149 changes: 149 additions & 0 deletions test/parallel/test-crypto-rsa-oaep-mgf1.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

// Tests the `mgf1Hash` option of crypto.publicEncrypt() and
// crypto.privateDecrypt(), which allows the MGF1 digest of RSA-OAEP padding to
// differ from the OAEP message digest (`oaepHash`). This is required for
// interoperability with profiles such as XML Encryption's `rsa-oaep-mgf1p`,
// where the OAEP digest may be changed but MGF1 is fixed to SHA-1.

const assert = require('assert');
const crypto = require('crypto');
const fixtures = require('../common/fixtures');
const { hasFIPS } = require('../common/crypto');

const constants = crypto.constants;

const publicKey = fixtures.readKey('rsa_public.pem', 'ascii');
const privateKey = fixtures.readKey('rsa_private.pem', 'ascii');

const input = Buffer.from('the quick brown fox jumps over the lazy dog');

// A round-trip with mismatched OAEP and MGF1 digests must succeed when both
// sides agree on the digests.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// mgf1Hash actually affects the padding: a ciphertext produced with
// oaepHash=sha256 and mgf1Hash=sha1 must NOT decrypt when MGF1 defaults to the
// OAEP digest (sha256), which is the pre-existing behavior.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, input);

assert.throws(() => {
crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
// No mgf1Hash: MGF1 follows oaepHash (sha256) and must fail to unpad.
}, encrypted);
}, {
code: hasFIPS(3, 5) ? 'ERR_OSSL_EVP_PROVIDER_ASYM_CIPHER_FAILURE' :
'ERR_OSSL_RSA_OAEP_DECODING_ERROR'
});
}

// Backward compatibility: omitting mgf1Hash on both sides keeps MGF1 == oaepHash
// (the historical behavior), so this round-trips, and setting mgf1Hash equal to
// oaepHash is equivalent to omitting it.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha256',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// The default oaepHash is sha1, so mgf1Hash defaults to sha1 as well. A
// ciphertext encrypted with all defaults must decrypt with an explicit
// mgf1Hash: 'sha1'.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
mgf1Hash: 'sha1',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// A few other digest combinations round-trip.
for (const [oaepHash, mgf1Hash] of [
['sha512', 'sha1'],
['sha384', 'sha256'],
['sha1', 'sha256'],
]) {
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash,
mgf1Hash,
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash,
mgf1Hash,
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// mgf1Hash must be a string.
for (const mgf1Hash of [1, true, {}, [], null]) {
assert.throws(() => {
crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash,
}, input);
}, { code: 'ERR_INVALID_ARG_TYPE' });
}

// An unknown mgf1Hash digest name is rejected.
assert.throws(() => {
crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'not-a-real-digest',
}, input);
}, { code: 'ERR_OSSL_EVP_INVALID_DIGEST' });
1 change: 1 addition & 0 deletions typings/internalBinding/crypto.d.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -767,6 +767,7 @@ declare namespace InternalCryptoBinding {
padding: number,
oaepHash: string | undefined,
oaepLabel: OptionalByteSource,
mgf1Hash: string | undefined,
]
) => Buffer;
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Closed
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
10 changes: 7 additions & 3 deletions deps/ncrypto/ncrypto.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -5650,9 +5650,11 @@ DataPointer RSA_Cipher(const EVPKeyPointer& key,
if (!key) return {};
EVPKeyCtxPointer ctx = key.newCtx();

const Digest& mgf1_digest =
params.mgf1_digest != nullptr ? params.mgf1_digest : params.digest;
if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) ||
(params.digest != nullptr && (!ctx.setRsaOaepMd(params.digest) ||
!ctx.setRsaMgf1Md(params.digest)))) {
(params.digest != nullptr &&
(!ctx.setRsaOaepMd(params.digest) || !ctx.setRsaMgf1Md(mgf1_digest)))) {
return {};
}

Expand DownExpand Up@@ -5691,7 +5693,9 @@ DataPointer CipherImpl(const EVPKeyPointer& key,
if (!key) return {};
EVPKeyCtxPointer ctx = key.newCtx();
if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) ||
(params.digest != nullptr && !ctx.setRsaOaepMd(params.digest))) {
(params.digest != nullptr && !ctx.setRsaOaepMd(params.digest)) ||
(params.mgf1_digest != nullptr &&
!ctx.setRsaMgf1Md(params.mgf1_digest))) {
return {};
}

Expand Down
1 change: 1 addition & 0 deletions deps/ncrypto/ncrypto.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -508,6 +508,7 @@ class Cipher final {
struct CipherParams {
int padding;
Digest digest;
Digest mgf1_digest;
const Buffer<const void> label;
};

Expand Down
20 changes: 16 additions & 4 deletions doc/api/crypto.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -5340,6 +5340,9 @@ An array of supported digest functions can be retrieved using
<!-- YAML
added: v0.11.14
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65073
description: The `mgf1Hash` option was added.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63188
description: Passing a CryptoKey as `privateKey` is no longer supported.
Expand DownExpand Up@@ -5370,8 +5373,11 @@ changes:
<!--lint disable maximum-line-length remark-lint-->

* `privateKey` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject|URL}
* `oaepHash` {string} The hash function to use for OAEP padding and MGF1.
**Default:** `'sha1'`
* `oaepHash` {string} The hash function to use for OAEP padding and, unless
`mgf1Hash` is set, MGF1. **Default:** `'sha1'`
* `mgf1Hash` {string} The hash function to use for the MGF1 mask generation
function of OAEP padding. If not specified, the value of `oaepHash` is used.
This allows the OAEP digest and the MGF1 digest to differ.
* `oaepLabel` {string|ArrayBuffer|Buffer|TypedArray|DataView} The label to
use for OAEP padding. If not specified, no label is used.
* `padding` {crypto.constants} An optional padding value defined in
Expand DownExpand Up@@ -5491,6 +5497,9 @@ be passed instead of a public key.
<!-- YAML
added: v0.11.14
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65073
description: The `mgf1Hash` option was added.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63188
description: Passing a CryptoKey as `key` is no longer supported.
Expand All@@ -5516,8 +5525,11 @@ changes:
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
* `key` {string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
A PEM encoded public or private key, or {KeyObject}.
* `oaepHash` {string} The hash function to use for OAEP padding and MGF1.
**Default:** `'sha1'`
* `oaepHash` {string} The hash function to use for OAEP padding and, unless
`mgf1Hash` is set, MGF1. **Default:** `'sha1'`
* `mgf1Hash` {string} The hash function to use for the MGF1 mask generation
function of OAEP padding. If not specified, the value of `oaepHash` is used.
This allows the OAEP digest and the MGF1 digest to differ.
* `oaepLabel` {string|ArrayBuffer|Buffer|TypedArray|DataView} The label to
use for OAEP padding. If not specified, no label is used.
* `passphrase` {string|ArrayBuffer|Buffer|TypedArray|DataView} An optional
Expand Down
6 changes: 4 additions & 2 deletions lib/internal/crypto/cipher.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -68,15 +68,17 @@ function rsaFunctionFor(method, defaultPadding, keyType) {
preparePrivateKey(key, keyName) :
preparePublicOrPrivateKey(key, keyName);
const padding = key.padding || defaultPadding;
const { oaepHash, encoding } = key;
const { oaepHash, mgf1Hash, encoding } = key;
let { oaepLabel } = key;
if (oaepHash !== undefined)
validateString(oaepHash, 'key.oaepHash');
if (mgf1Hash !== undefined)
validateString(mgf1Hash, 'key.mgf1Hash');
if (oaepLabel !== undefined)
oaepLabel = getArrayBufferOrView(oaepLabel, 'key.oaepLabel', encoding);
buffer = getArrayBufferOrView(buffer, 'buffer', encoding);
return method(data, format, type, passphrase, namedCurve, buffer,
padding, oaepHash, oaepLabel);
padding, oaepHash, oaepLabel, mgf1Hash);
};
}

Expand Down
13 changes: 12 additions & 1 deletion src/crypto/crypto_cipher.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -801,6 +801,7 @@ bool PublicKeyCipher::Cipher(
const EVPKeyPointer& pkey,
int padding,
const Digest& digest,
const Digest& mgf1_digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
std::unique_ptr<BackingStore>* out) {
Expand All@@ -810,6 +811,7 @@ bool PublicKeyCipher::Cipher(
const ncrypto::Cipher::CipherParams params{
.padding = padding,
.digest = digest,
.mgf1_digest = mgf1_digest,
.label = label,
};

Expand DownExpand Up@@ -882,8 +884,17 @@ void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& args) {
if (!oaep_label.CheckSizeInt32()) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "oaepLabel is too big");
}

Digest mgf1_digest;
if (args[offset + 4]->IsString()) {
Utf8Value mgf1_str(env->isolate(), args[offset + 4]);
mgf1_digest = Digest::FromName(*mgf1_str);
if (!mgf1_digest) return THROW_ERR_OSSL_EVP_INVALID_DIGEST(env);
}

std::unique_ptr<BackingStore> out;
if (!Cipher<cipher>(env, pkey, padding, digest, oaep_label, buf, &out)) {
if (!Cipher<cipher>(
env, pkey, padding, digest, mgf1_digest, oaep_label, buf, &out)) {
return ThrowCryptoError(env, ERR_get_error());
}

Expand Down
1 change: 1 addition & 0 deletions src/crypto/crypto_cipher.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,6 +106,7 @@ class PublicKeyCipher {
const ncrypto::EVPKeyPointer& pkey,
int padding,
const ncrypto::Digest& digest,
const ncrypto::Digest& mgf1_digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
std::unique_ptr<v8::BackingStore>* out);
Expand Down
1 change: 1 addition & 0 deletions src/crypto/crypto_rsa.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,7 @@ WebCryptoCipherStatus RSA_Cipher(Environment* env,
const ncrypto::Rsa::CipherParams nparams{
.padding = params.padding,
.digest = params.digest,
.mgf1_digest = params.digest,
.label = params.label,
};

Expand Down
149 changes: 149 additions & 0 deletions test/parallel/test-crypto-rsa-oaep-mgf1.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

// Tests the `mgf1Hash` option of crypto.publicEncrypt() and
// crypto.privateDecrypt(), which allows the MGF1 digest of RSA-OAEP padding to
// differ from the OAEP message digest (`oaepHash`). This is required for
// interoperability with profiles such as XML Encryption's `rsa-oaep-mgf1p`,
// where the OAEP digest may be changed but MGF1 is fixed to SHA-1.

const assert = require('assert');
const crypto = require('crypto');
const fixtures = require('../common/fixtures');
const { hasFIPS } = require('../common/crypto');

const constants = crypto.constants;

const publicKey = fixtures.readKey('rsa_public.pem', 'ascii');
const privateKey = fixtures.readKey('rsa_private.pem', 'ascii');

const input = Buffer.from('the quick brown fox jumps over the lazy dog');

// A round-trip with mismatched OAEP and MGF1 digests must succeed when both
// sides agree on the digests.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// mgf1Hash actually affects the padding: a ciphertext produced with
// oaepHash=sha256 and mgf1Hash=sha1 must NOT decrypt when MGF1 defaults to the
// OAEP digest (sha256), which is the pre-existing behavior.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, input);

assert.throws(() => {
crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
// No mgf1Hash: MGF1 follows oaepHash (sha256) and must fail to unpad.
}, encrypted);
}, {
code: hasFIPS(3, 5) ? 'ERR_OSSL_EVP_PROVIDER_ASYM_CIPHER_FAILURE' :
'ERR_OSSL_RSA_OAEP_DECODING_ERROR'
});
}

// Backward compatibility: omitting mgf1Hash on both sides keeps MGF1 == oaepHash
// (the historical behavior), so this round-trips, and setting mgf1Hash equal to
// oaepHash is equivalent to omitting it.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha256',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// The default oaepHash is sha1, so mgf1Hash defaults to sha1 as well. A
// ciphertext encrypted with all defaults must decrypt with an explicit
// mgf1Hash: 'sha1'.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
mgf1Hash: 'sha1',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// A few other digest combinations round-trip.
for (const [oaepHash, mgf1Hash] of [
['sha512', 'sha1'],
['sha384', 'sha256'],
['sha1', 'sha256'],
]) {
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash,
mgf1Hash,
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash,
mgf1Hash,
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// mgf1Hash must be a string.
for (const mgf1Hash of [1, true, {}, [], null]) {
assert.throws(() => {
crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash,
}, input);
}, { code: 'ERR_INVALID_ARG_TYPE' });
}

// An unknown mgf1Hash digest name is rejected.
assert.throws(() => {
crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'not-a-real-digest',
}, input);
}, { code: 'ERR_OSSL_EVP_INVALID_DIGEST' });
1 change: 1 addition & 0 deletions typings/internalBinding/crypto.d.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -767,6 +767,7 @@ declare namespace InternalCryptoBinding {
padding: number,
oaepHash: string | undefined,
oaepLabel: OptionalByteSource,
mgf1Hash: string | undefined,
]
) => Buffer;
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
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
10 changes: 7 additions & 3 deletions deps/ncrypto/ncrypto.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -5650,9 +5650,11 @@ DataPointer RSA_Cipher(const EVPKeyPointer& key,
if (!key) return {};
EVPKeyCtxPointer ctx = key.newCtx();

const Digest& mgf1_digest =
params.mgf1_digest != nullptr ? params.mgf1_digest : params.digest;
if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) ||
(params.digest != nullptr && (!ctx.setRsaOaepMd(params.digest) ||
!ctx.setRsaMgf1Md(params.digest)))) {
(params.digest != nullptr &&
(!ctx.setRsaOaepMd(params.digest) || !ctx.setRsaMgf1Md(mgf1_digest)))) {
return {};
}

Expand DownExpand Up@@ -5691,7 +5693,9 @@ DataPointer CipherImpl(const EVPKeyPointer& key,
if (!key) return {};
EVPKeyCtxPointer ctx = key.newCtx();
if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) ||
(params.digest != nullptr && !ctx.setRsaOaepMd(params.digest))) {
(params.digest != nullptr && !ctx.setRsaOaepMd(params.digest)) ||
(params.mgf1_digest != nullptr &&
!ctx.setRsaMgf1Md(params.mgf1_digest))) {
return {};
}

Expand Down
1 change: 1 addition & 0 deletions deps/ncrypto/ncrypto.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -508,6 +508,7 @@ class Cipher final {
struct CipherParams {
int padding;
Digest digest;
Digest mgf1_digest;
const Buffer<const void> label;
};

Expand Down
20 changes: 16 additions & 4 deletions doc/api/crypto.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -5340,6 +5340,9 @@ An array of supported digest functions can be retrieved using
<!-- YAML
added: v0.11.14
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65073
description: The `mgf1Hash` option was added.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63188
description: Passing a CryptoKey as `privateKey` is no longer supported.
Expand DownExpand Up@@ -5370,8 +5373,11 @@ changes:
<!--lint disable maximum-line-length remark-lint-->

* `privateKey` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject|URL}
* `oaepHash` {string} The hash function to use for OAEP padding and MGF1.
**Default:** `'sha1'`
* `oaepHash` {string} The hash function to use for OAEP padding and, unless
`mgf1Hash` is set, MGF1. **Default:** `'sha1'`
* `mgf1Hash` {string} The hash function to use for the MGF1 mask generation
function of OAEP padding. If not specified, the value of `oaepHash` is used.
This allows the OAEP digest and the MGF1 digest to differ.
* `oaepLabel` {string|ArrayBuffer|Buffer|TypedArray|DataView} The label to
use for OAEP padding. If not specified, no label is used.
* `padding` {crypto.constants} An optional padding value defined in
Expand DownExpand Up@@ -5491,6 +5497,9 @@ be passed instead of a public key.
<!-- YAML
added: v0.11.14
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65073
description: The `mgf1Hash` option was added.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63188
description: Passing a CryptoKey as `key` is no longer supported.
Expand All@@ -5516,8 +5525,11 @@ changes:
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
* `key` {string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
A PEM encoded public or private key, or {KeyObject}.
* `oaepHash` {string} The hash function to use for OAEP padding and MGF1.
**Default:** `'sha1'`
* `oaepHash` {string} The hash function to use for OAEP padding and, unless
`mgf1Hash` is set, MGF1. **Default:** `'sha1'`
* `mgf1Hash` {string} The hash function to use for the MGF1 mask generation
function of OAEP padding. If not specified, the value of `oaepHash` is used.
This allows the OAEP digest and the MGF1 digest to differ.
* `oaepLabel` {string|ArrayBuffer|Buffer|TypedArray|DataView} The label to
use for OAEP padding. If not specified, no label is used.
* `passphrase` {string|ArrayBuffer|Buffer|TypedArray|DataView} An optional
Expand Down
6 changes: 4 additions & 2 deletions lib/internal/crypto/cipher.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -68,15 +68,17 @@ function rsaFunctionFor(method, defaultPadding, keyType) {
preparePrivateKey(key, keyName) :
preparePublicOrPrivateKey(key, keyName);
const padding = key.padding || defaultPadding;
const { oaepHash, encoding } = key;
const { oaepHash, mgf1Hash, encoding } = key;
let { oaepLabel } = key;
if (oaepHash !== undefined)
validateString(oaepHash, 'key.oaepHash');
if (mgf1Hash !== undefined)
validateString(mgf1Hash, 'key.mgf1Hash');
if (oaepLabel !== undefined)
oaepLabel = getArrayBufferOrView(oaepLabel, 'key.oaepLabel', encoding);
buffer = getArrayBufferOrView(buffer, 'buffer', encoding);
return method(data, format, type, passphrase, namedCurve, buffer,
padding, oaepHash, oaepLabel);
padding, oaepHash, oaepLabel, mgf1Hash);
};
}

Expand Down
13 changes: 12 additions & 1 deletion src/crypto/crypto_cipher.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -801,6 +801,7 @@ bool PublicKeyCipher::Cipher(
const EVPKeyPointer& pkey,
int padding,
const Digest& digest,
const Digest& mgf1_digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
std::unique_ptr<BackingStore>* out) {
Expand All@@ -810,6 +811,7 @@ bool PublicKeyCipher::Cipher(
const ncrypto::Cipher::CipherParams params{
.padding = padding,
.digest = digest,
.mgf1_digest = mgf1_digest,
.label = label,
};

Expand DownExpand Up@@ -882,8 +884,17 @@ void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& args) {
if (!oaep_label.CheckSizeInt32()) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "oaepLabel is too big");
}

Digest mgf1_digest;
if (args[offset + 4]->IsString()) {
Utf8Value mgf1_str(env->isolate(), args[offset + 4]);
mgf1_digest = Digest::FromName(*mgf1_str);
if (!mgf1_digest) return THROW_ERR_OSSL_EVP_INVALID_DIGEST(env);
}

std::unique_ptr<BackingStore> out;
if (!Cipher<cipher>(env, pkey, padding, digest, oaep_label, buf, &out)) {
if (!Cipher<cipher>(
env, pkey, padding, digest, mgf1_digest, oaep_label, buf, &out)) {
return ThrowCryptoError(env, ERR_get_error());
}

Expand Down
1 change: 1 addition & 0 deletions src/crypto/crypto_cipher.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,6 +106,7 @@ class PublicKeyCipher {
const ncrypto::EVPKeyPointer& pkey,
int padding,
const ncrypto::Digest& digest,
const ncrypto::Digest& mgf1_digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
std::unique_ptr<v8::BackingStore>* out);
Expand Down
1 change: 1 addition & 0 deletions src/crypto/crypto_rsa.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,7 @@ WebCryptoCipherStatus RSA_Cipher(Environment* env,
const ncrypto::Rsa::CipherParams nparams{
.padding = params.padding,
.digest = params.digest,
.mgf1_digest = params.digest,
.label = params.label,
};

Expand Down
149 changes: 149 additions & 0 deletions test/parallel/test-crypto-rsa-oaep-mgf1.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

// Tests the `mgf1Hash` option of crypto.publicEncrypt() and
// crypto.privateDecrypt(), which allows the MGF1 digest of RSA-OAEP padding to
// differ from the OAEP message digest (`oaepHash`). This is required for
// interoperability with profiles such as XML Encryption's `rsa-oaep-mgf1p`,
// where the OAEP digest may be changed but MGF1 is fixed to SHA-1.

const assert = require('assert');
const crypto = require('crypto');
const fixtures = require('../common/fixtures');
const { hasFIPS } = require('../common/crypto');

const constants = crypto.constants;

const publicKey = fixtures.readKey('rsa_public.pem', 'ascii');
const privateKey = fixtures.readKey('rsa_private.pem', 'ascii');

const input = Buffer.from('the quick brown fox jumps over the lazy dog');

// A round-trip with mismatched OAEP and MGF1 digests must succeed when both
// sides agree on the digests.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// mgf1Hash actually affects the padding: a ciphertext produced with
// oaepHash=sha256 and mgf1Hash=sha1 must NOT decrypt when MGF1 defaults to the
// OAEP digest (sha256), which is the pre-existing behavior.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, input);

assert.throws(() => {
crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
// No mgf1Hash: MGF1 follows oaepHash (sha256) and must fail to unpad.
}, encrypted);
}, {
code: hasFIPS(3, 5) ? 'ERR_OSSL_EVP_PROVIDER_ASYM_CIPHER_FAILURE' :
'ERR_OSSL_RSA_OAEP_DECODING_ERROR'
});
}

// Backward compatibility: omitting mgf1Hash on both sides keeps MGF1 == oaepHash
// (the historical behavior), so this round-trips, and setting mgf1Hash equal to
// oaepHash is equivalent to omitting it.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha256',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// The default oaepHash is sha1, so mgf1Hash defaults to sha1 as well. A
// ciphertext encrypted with all defaults must decrypt with an explicit
// mgf1Hash: 'sha1'.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
mgf1Hash: 'sha1',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// A few other digest combinations round-trip.
for (const [oaepHash, mgf1Hash] of [
['sha512', 'sha1'],
['sha384', 'sha256'],
['sha1', 'sha256'],
]) {
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash,
mgf1Hash,
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash,
mgf1Hash,
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// mgf1Hash must be a string.
for (const mgf1Hash of [1, true, {}, [], null]) {
assert.throws(() => {
crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash,
}, input);
}, { code: 'ERR_INVALID_ARG_TYPE' });
}

// An unknown mgf1Hash digest name is rejected.
assert.throws(() => {
crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'not-a-real-digest',
}, input);
}, { code: 'ERR_OSSL_EVP_INVALID_DIGEST' });
1 change: 1 addition & 0 deletions typings/internalBinding/crypto.d.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -767,6 +767,7 @@ declare namespace InternalCryptoBinding {
padding: number,
oaepHash: string | undefined,
oaepLabel: OptionalByteSource,
mgf1Hash: string | undefined,
]
) => Buffer;
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
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
10 changes: 7 additions & 3 deletions deps/ncrypto/ncrypto.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -5650,9 +5650,11 @@ DataPointer RSA_Cipher(const EVPKeyPointer& key,
if (!key) return {};
EVPKeyCtxPointer ctx = key.newCtx();

const Digest& mgf1_digest =
params.mgf1_digest != nullptr ? params.mgf1_digest : params.digest;
if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) ||
(params.digest != nullptr && (!ctx.setRsaOaepMd(params.digest) ||
!ctx.setRsaMgf1Md(params.digest)))) {
(params.digest != nullptr &&
(!ctx.setRsaOaepMd(params.digest) || !ctx.setRsaMgf1Md(mgf1_digest)))) {
return {};
}

Expand DownExpand Up@@ -5691,7 +5693,9 @@ DataPointer CipherImpl(const EVPKeyPointer& key,
if (!key) return {};
EVPKeyCtxPointer ctx = key.newCtx();
if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) ||
(params.digest != nullptr && !ctx.setRsaOaepMd(params.digest))) {
(params.digest != nullptr && !ctx.setRsaOaepMd(params.digest)) ||
(params.mgf1_digest != nullptr &&
!ctx.setRsaMgf1Md(params.mgf1_digest))) {
return {};
}

Expand Down
1 change: 1 addition & 0 deletions deps/ncrypto/ncrypto.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -508,6 +508,7 @@ class Cipher final {
struct CipherParams {
int padding;
Digest digest;
Digest mgf1_digest;
const Buffer<const void> label;
};

Expand Down
20 changes: 16 additions & 4 deletions doc/api/crypto.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -5340,6 +5340,9 @@ An array of supported digest functions can be retrieved using
<!-- YAML
added: v0.11.14
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65073
description: The `mgf1Hash` option was added.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63188
description: Passing a CryptoKey as `privateKey` is no longer supported.
Expand DownExpand Up@@ -5370,8 +5373,11 @@ changes:
<!--lint disable maximum-line-length remark-lint-->

* `privateKey` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject|URL}
* `oaepHash` {string} The hash function to use for OAEP padding and MGF1.
**Default:** `'sha1'`
* `oaepHash` {string} The hash function to use for OAEP padding and, unless
`mgf1Hash` is set, MGF1. **Default:** `'sha1'`
* `mgf1Hash` {string} The hash function to use for the MGF1 mask generation
function of OAEP padding. If not specified, the value of `oaepHash` is used.
This allows the OAEP digest and the MGF1 digest to differ.
* `oaepLabel` {string|ArrayBuffer|Buffer|TypedArray|DataView} The label to
use for OAEP padding. If not specified, no label is used.
* `padding` {crypto.constants} An optional padding value defined in
Expand DownExpand Up@@ -5491,6 +5497,9 @@ be passed instead of a public key.
<!-- YAML
added: v0.11.14
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65073
description: The `mgf1Hash` option was added.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63188
description: Passing a CryptoKey as `key` is no longer supported.
Expand All@@ -5516,8 +5525,11 @@ changes:
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
* `key` {string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
A PEM encoded public or private key, or {KeyObject}.
* `oaepHash` {string} The hash function to use for OAEP padding and MGF1.
**Default:** `'sha1'`
* `oaepHash` {string} The hash function to use for OAEP padding and, unless
`mgf1Hash` is set, MGF1. **Default:** `'sha1'`
* `mgf1Hash` {string} The hash function to use for the MGF1 mask generation
function of OAEP padding. If not specified, the value of `oaepHash` is used.
This allows the OAEP digest and the MGF1 digest to differ.
* `oaepLabel` {string|ArrayBuffer|Buffer|TypedArray|DataView} The label to
use for OAEP padding. If not specified, no label is used.
* `passphrase` {string|ArrayBuffer|Buffer|TypedArray|DataView} An optional
Expand Down
6 changes: 4 additions & 2 deletions lib/internal/crypto/cipher.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -68,15 +68,17 @@ function rsaFunctionFor(method, defaultPadding, keyType) {
preparePrivateKey(key, keyName) :
preparePublicOrPrivateKey(key, keyName);
const padding = key.padding || defaultPadding;
const { oaepHash, encoding } = key;
const { oaepHash, mgf1Hash, encoding } = key;
let { oaepLabel } = key;
if (oaepHash !== undefined)
validateString(oaepHash, 'key.oaepHash');
if (mgf1Hash !== undefined)
validateString(mgf1Hash, 'key.mgf1Hash');
if (oaepLabel !== undefined)
oaepLabel = getArrayBufferOrView(oaepLabel, 'key.oaepLabel', encoding);
buffer = getArrayBufferOrView(buffer, 'buffer', encoding);
return method(data, format, type, passphrase, namedCurve, buffer,
padding, oaepHash, oaepLabel);
padding, oaepHash, oaepLabel, mgf1Hash);
};
}

Expand Down
13 changes: 12 additions & 1 deletion src/crypto/crypto_cipher.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -801,6 +801,7 @@ bool PublicKeyCipher::Cipher(
const EVPKeyPointer& pkey,
int padding,
const Digest& digest,
const Digest& mgf1_digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
std::unique_ptr<BackingStore>* out) {
Expand All@@ -810,6 +811,7 @@ bool PublicKeyCipher::Cipher(
const ncrypto::Cipher::CipherParams params{
.padding = padding,
.digest = digest,
.mgf1_digest = mgf1_digest,
.label = label,
};

Expand DownExpand Up@@ -882,8 +884,17 @@ void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& args) {
if (!oaep_label.CheckSizeInt32()) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "oaepLabel is too big");
}

Digest mgf1_digest;
if (args[offset + 4]->IsString()) {
Utf8Value mgf1_str(env->isolate(), args[offset + 4]);
mgf1_digest = Digest::FromName(*mgf1_str);
if (!mgf1_digest) return THROW_ERR_OSSL_EVP_INVALID_DIGEST(env);
}

std::unique_ptr<BackingStore> out;
if (!Cipher<cipher>(env, pkey, padding, digest, oaep_label, buf, &out)) {
if (!Cipher<cipher>(
env, pkey, padding, digest, mgf1_digest, oaep_label, buf, &out)) {
return ThrowCryptoError(env, ERR_get_error());
}

Expand Down
1 change: 1 addition & 0 deletions src/crypto/crypto_cipher.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,6 +106,7 @@ class PublicKeyCipher {
const ncrypto::EVPKeyPointer& pkey,
int padding,
const ncrypto::Digest& digest,
const ncrypto::Digest& mgf1_digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
std::unique_ptr<v8::BackingStore>* out);
Expand Down
1 change: 1 addition & 0 deletions src/crypto/crypto_rsa.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,7 @@ WebCryptoCipherStatus RSA_Cipher(Environment* env,
const ncrypto::Rsa::CipherParams nparams{
.padding = params.padding,
.digest = params.digest,
.mgf1_digest = params.digest,
.label = params.label,
};

Expand Down
149 changes: 149 additions & 0 deletions test/parallel/test-crypto-rsa-oaep-mgf1.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

// Tests the `mgf1Hash` option of crypto.publicEncrypt() and
// crypto.privateDecrypt(), which allows the MGF1 digest of RSA-OAEP padding to
// differ from the OAEP message digest (`oaepHash`). This is required for
// interoperability with profiles such as XML Encryption's `rsa-oaep-mgf1p`,
// where the OAEP digest may be changed but MGF1 is fixed to SHA-1.

const assert = require('assert');
const crypto = require('crypto');
const fixtures = require('../common/fixtures');
const { hasFIPS } = require('../common/crypto');

const constants = crypto.constants;

const publicKey = fixtures.readKey('rsa_public.pem', 'ascii');
const privateKey = fixtures.readKey('rsa_private.pem', 'ascii');

const input = Buffer.from('the quick brown fox jumps over the lazy dog');

// A round-trip with mismatched OAEP and MGF1 digests must succeed when both
// sides agree on the digests.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// mgf1Hash actually affects the padding: a ciphertext produced with
// oaepHash=sha256 and mgf1Hash=sha1 must NOT decrypt when MGF1 defaults to the
// OAEP digest (sha256), which is the pre-existing behavior.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, input);

assert.throws(() => {
crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
// No mgf1Hash: MGF1 follows oaepHash (sha256) and must fail to unpad.
}, encrypted);
}, {
code: hasFIPS(3, 5) ? 'ERR_OSSL_EVP_PROVIDER_ASYM_CIPHER_FAILURE' :
'ERR_OSSL_RSA_OAEP_DECODING_ERROR'
});
}

// Backward compatibility: omitting mgf1Hash on both sides keeps MGF1 == oaepHash
// (the historical behavior), so this round-trips, and setting mgf1Hash equal to
// oaepHash is equivalent to omitting it.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha256',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// The default oaepHash is sha1, so mgf1Hash defaults to sha1 as well. A
// ciphertext encrypted with all defaults must decrypt with an explicit
// mgf1Hash: 'sha1'.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
mgf1Hash: 'sha1',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// A few other digest combinations round-trip.
for (const [oaepHash, mgf1Hash] of [
['sha512', 'sha1'],
['sha384', 'sha256'],
['sha1', 'sha256'],
]) {
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash,
mgf1Hash,
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash,
mgf1Hash,
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// mgf1Hash must be a string.
for (const mgf1Hash of [1, true, {}, [], null]) {
assert.throws(() => {
crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash,
}, input);
}, { code: 'ERR_INVALID_ARG_TYPE' });
}

// An unknown mgf1Hash digest name is rejected.
assert.throws(() => {
crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'not-a-real-digest',
}, input);
}, { code: 'ERR_OSSL_EVP_INVALID_DIGEST' });
1 change: 1 addition & 0 deletions typings/internalBinding/crypto.d.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -767,6 +767,7 @@ declare namespace InternalCryptoBinding {
padding: number,
oaepHash: string | undefined,
oaepLabel: OptionalByteSource,
mgf1Hash: string | undefined,
]
) => Buffer;
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Closed
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
10 changes: 7 additions & 3 deletions deps/ncrypto/ncrypto.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -5650,9 +5650,11 @@ DataPointer RSA_Cipher(const EVPKeyPointer& key,
if (!key) return {};
EVPKeyCtxPointer ctx = key.newCtx();

const Digest& mgf1_digest =
params.mgf1_digest != nullptr ? params.mgf1_digest : params.digest;
if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) ||
(params.digest != nullptr && (!ctx.setRsaOaepMd(params.digest) ||
!ctx.setRsaMgf1Md(params.digest)))) {
(params.digest != nullptr &&
(!ctx.setRsaOaepMd(params.digest) || !ctx.setRsaMgf1Md(mgf1_digest)))) {
return {};
}

Expand DownExpand Up@@ -5691,7 +5693,9 @@ DataPointer CipherImpl(const EVPKeyPointer& key,
if (!key) return {};
EVPKeyCtxPointer ctx = key.newCtx();
if (!ctx || init(ctx.get()) <= 0 || !ctx.setRsaPadding(params.padding) ||
(params.digest != nullptr && !ctx.setRsaOaepMd(params.digest))) {
(params.digest != nullptr && !ctx.setRsaOaepMd(params.digest)) ||
(params.mgf1_digest != nullptr &&
!ctx.setRsaMgf1Md(params.mgf1_digest))) {
return {};
}

Expand Down
1 change: 1 addition & 0 deletions deps/ncrypto/ncrypto.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -508,6 +508,7 @@ class Cipher final {
struct CipherParams {
int padding;
Digest digest;
Digest mgf1_digest;
const Buffer<const void> label;
};

Expand Down
20 changes: 16 additions & 4 deletions doc/api/crypto.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -5340,6 +5340,9 @@ An array of supported digest functions can be retrieved using
<!-- YAML
added: v0.11.14
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65073
description: The `mgf1Hash` option was added.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63188
description: Passing a CryptoKey as `privateKey` is no longer supported.
Expand DownExpand Up@@ -5370,8 +5373,11 @@ changes:
<!--lint disable maximum-line-length remark-lint-->

* `privateKey` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject|URL}
* `oaepHash` {string} The hash function to use for OAEP padding and MGF1.
**Default:** `'sha1'`
* `oaepHash` {string} The hash function to use for OAEP padding and, unless
`mgf1Hash` is set, MGF1. **Default:** `'sha1'`
* `mgf1Hash` {string} The hash function to use for the MGF1 mask generation
function of OAEP padding. If not specified, the value of `oaepHash` is used.
This allows the OAEP digest and the MGF1 digest to differ.
* `oaepLabel` {string|ArrayBuffer|Buffer|TypedArray|DataView} The label to
use for OAEP padding. If not specified, no label is used.
* `padding` {crypto.constants} An optional padding value defined in
Expand DownExpand Up@@ -5491,6 +5497,9 @@ be passed instead of a public key.
<!-- YAML
added: v0.11.14
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/65073
description: The `mgf1Hash` option was added.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/63188
description: Passing a CryptoKey as `key` is no longer supported.
Expand All@@ -5516,8 +5525,11 @@ changes:
* `key` {Object|string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
* `key` {string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
A PEM encoded public or private key, or {KeyObject}.
* `oaepHash` {string} The hash function to use for OAEP padding and MGF1.
**Default:** `'sha1'`
* `oaepHash` {string} The hash function to use for OAEP padding and, unless
`mgf1Hash` is set, MGF1. **Default:** `'sha1'`
* `mgf1Hash` {string} The hash function to use for the MGF1 mask generation
function of OAEP padding. If not specified, the value of `oaepHash` is used.
This allows the OAEP digest and the MGF1 digest to differ.
* `oaepLabel` {string|ArrayBuffer|Buffer|TypedArray|DataView} The label to
use for OAEP padding. If not specified, no label is used.
* `passphrase` {string|ArrayBuffer|Buffer|TypedArray|DataView} An optional
Expand Down
6 changes: 4 additions & 2 deletions lib/internal/crypto/cipher.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -68,15 +68,17 @@ function rsaFunctionFor(method, defaultPadding, keyType) {
preparePrivateKey(key, keyName) :
preparePublicOrPrivateKey(key, keyName);
const padding = key.padding || defaultPadding;
const { oaepHash, encoding } = key;
const { oaepHash, mgf1Hash, encoding } = key;
let { oaepLabel } = key;
if (oaepHash !== undefined)
validateString(oaepHash, 'key.oaepHash');
if (mgf1Hash !== undefined)
validateString(mgf1Hash, 'key.mgf1Hash');
if (oaepLabel !== undefined)
oaepLabel = getArrayBufferOrView(oaepLabel, 'key.oaepLabel', encoding);
buffer = getArrayBufferOrView(buffer, 'buffer', encoding);
return method(data, format, type, passphrase, namedCurve, buffer,
padding, oaepHash, oaepLabel);
padding, oaepHash, oaepLabel, mgf1Hash);
};
}

Expand Down
13 changes: 12 additions & 1 deletion src/crypto/crypto_cipher.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -801,6 +801,7 @@ bool PublicKeyCipher::Cipher(
const EVPKeyPointer& pkey,
int padding,
const Digest& digest,
const Digest& mgf1_digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
std::unique_ptr<BackingStore>* out) {
Expand All@@ -810,6 +811,7 @@ bool PublicKeyCipher::Cipher(
const ncrypto::Cipher::CipherParams params{
.padding = padding,
.digest = digest,
.mgf1_digest = mgf1_digest,
.label = label,
};

Expand DownExpand Up@@ -882,8 +884,17 @@ void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& args) {
if (!oaep_label.CheckSizeInt32()) [[unlikely]] {
return THROW_ERR_OUT_OF_RANGE(env, "oaepLabel is too big");
}

Digest mgf1_digest;
if (args[offset + 4]->IsString()) {
Utf8Value mgf1_str(env->isolate(), args[offset + 4]);
mgf1_digest = Digest::FromName(*mgf1_str);
if (!mgf1_digest) return THROW_ERR_OSSL_EVP_INVALID_DIGEST(env);
}

std::unique_ptr<BackingStore> out;
if (!Cipher<cipher>(env, pkey, padding, digest, oaep_label, buf, &out)) {
if (!Cipher<cipher>(
env, pkey, padding, digest, mgf1_digest, oaep_label, buf, &out)) {
return ThrowCryptoError(env, ERR_get_error());
}

Expand Down
1 change: 1 addition & 0 deletions src/crypto/crypto_cipher.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,6 +106,7 @@ class PublicKeyCipher {
const ncrypto::EVPKeyPointer& pkey,
int padding,
const ncrypto::Digest& digest,
const ncrypto::Digest& mgf1_digest,
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
std::unique_ptr<v8::BackingStore>* out);
Expand Down
1 change: 1 addition & 0 deletions src/crypto/crypto_rsa.cc
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,7 @@ WebCryptoCipherStatus RSA_Cipher(Environment* env,
const ncrypto::Rsa::CipherParams nparams{
.padding = params.padding,
.digest = params.digest,
.mgf1_digest = params.digest,
.label = params.label,
};

Expand Down
149 changes: 149 additions & 0 deletions test/parallel/test-crypto-rsa-oaep-mgf1.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

// Tests the `mgf1Hash` option of crypto.publicEncrypt() and
// crypto.privateDecrypt(), which allows the MGF1 digest of RSA-OAEP padding to
// differ from the OAEP message digest (`oaepHash`). This is required for
// interoperability with profiles such as XML Encryption's `rsa-oaep-mgf1p`,
// where the OAEP digest may be changed but MGF1 is fixed to SHA-1.

const assert = require('assert');
const crypto = require('crypto');
const fixtures = require('../common/fixtures');
const { hasFIPS } = require('../common/crypto');

const constants = crypto.constants;

const publicKey = fixtures.readKey('rsa_public.pem', 'ascii');
const privateKey = fixtures.readKey('rsa_private.pem', 'ascii');

const input = Buffer.from('the quick brown fox jumps over the lazy dog');

// A round-trip with mismatched OAEP and MGF1 digests must succeed when both
// sides agree on the digests.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// mgf1Hash actually affects the padding: a ciphertext produced with
// oaepHash=sha256 and mgf1Hash=sha1 must NOT decrypt when MGF1 defaults to the
// OAEP digest (sha256), which is the pre-existing behavior.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha1',
}, input);

assert.throws(() => {
crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
// No mgf1Hash: MGF1 follows oaepHash (sha256) and must fail to unpad.
}, encrypted);
}, {
code: hasFIPS(3, 5) ? 'ERR_OSSL_EVP_PROVIDER_ASYM_CIPHER_FAILURE' :
'ERR_OSSL_RSA_OAEP_DECODING_ERROR'
});
}

// Backward compatibility: omitting mgf1Hash on both sides keeps MGF1 == oaepHash
// (the historical behavior), so this round-trips, and setting mgf1Hash equal to
// oaepHash is equivalent to omitting it.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'sha256',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// The default oaepHash is sha1, so mgf1Hash defaults to sha1 as well. A
// ciphertext encrypted with all defaults must decrypt with an explicit
// mgf1Hash: 'sha1'.
{
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
mgf1Hash: 'sha1',
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// A few other digest combinations round-trip.
for (const [oaepHash, mgf1Hash] of [
['sha512', 'sha1'],
['sha384', 'sha256'],
['sha1', 'sha256'],
]) {
const encrypted = crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash,
mgf1Hash,
}, input);

const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash,
mgf1Hash,
}, encrypted);

assert.deepStrictEqual(decrypted, input);
}

// mgf1Hash must be a string.
for (const mgf1Hash of [1, true, {}, [], null]) {
assert.throws(() => {
crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash,
}, input);
}, { code: 'ERR_INVALID_ARG_TYPE' });
}

// An unknown mgf1Hash digest name is rejected.
assert.throws(() => {
crypto.publicEncrypt({
key: publicKey,
padding: constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
mgf1Hash: 'not-a-real-digest',
}, input);
}, { code: 'ERR_OSSL_EVP_INVALID_DIGEST' });
1 change: 1 addition & 0 deletions typings/internalBinding/crypto.d.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -767,6 +767,7 @@ declare namespace InternalCryptoBinding {
padding: number,
oaepHash: string | undefined,
oaepLabel: OptionalByteSource,
mgf1Hash: string | undefined,
]
) => Buffer;
}
Expand Down
Loading