From 4eb99a4770fc38ece8359d9ac186a67417e96bff Mon Sep 17 00:00:00 2001 From: Amit Prajapati <115357958+AmitPrajapati-1@users.noreply.github.com> Date: Thu, 20 Mar 2025 15:26:16 +0000 Subject: [PATCH 1/4] Fix RSA-OAEP encryption for empty strings --- test/parallel/test-crypto-private-decrypt.js | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 test/parallel/test-crypto-private-decrypt.js diff --git a/test/parallel/test-crypto-private-decrypt.js b/test/parallel/test-crypto-private-decrypt.js new file mode 100644 index 00000000000000..ec15741178a484 --- /dev/null +++ b/test/parallel/test-crypto-private-decrypt.js @@ -0,0 +1,27 @@ +// file: test-crypto-private-decrypt.js +const crypto = require('crypto'); + +const keys = crypto.generateKeyPairSync( + 'rsa', + { + modulusLength: 2048, + publicKeyEncoding: { type: 'spki', format: 'pem' }, + privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, + } +); + +const empty = ''; + +const ciphertext = crypto.publicEncrypt({ + oaepHash:'sha1', + padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, + key: keys.publicKey, +}, Buffer.from(empty)).toString('base64'); + +const plaintext = crypto.privateDecrypt({ + oaepHash: 'sha1', + padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, + key: keys.privateKey +}, Buffer.from(ciphertext, 'base64')).toString('utf8').trim(); +console.log("Decrypt",plaintext); +console.assert(empty === plaintext, 'rsa-oaep `encrypt` empty string is success, but `decrypt` got unexpected string.'); From 756185df293d312fe77239d099ec683ee93181b8 Mon Sep 17 00:00:00 2001 From: Amit Prajapati <115357958+AmitPrajapati-1@users.noreply.github.com> Date: Thu, 20 Mar 2025 15:47:15 +0000 Subject: [PATCH 2/4] Fix lint errors and add hasCrypto check in test-crypto-private-decrypt.js --- package-lock.json | 6 ++++ test/parallel/package-lock.json | 6 ++++ test/parallel/test-crypto-private-decrypt.js | 36 +++++++++++--------- 3 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 package-lock.json create mode 100644 test/parallel/package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000000000..93bc0662b7b834 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "node", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/test/parallel/package-lock.json b/test/parallel/package-lock.json new file mode 100644 index 00000000000000..88ee07f4c54021 --- /dev/null +++ b/test/parallel/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "parallel", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/test/parallel/test-crypto-private-decrypt.js b/test/parallel/test-crypto-private-decrypt.js index ec15741178a484..fcbef2afd41fe1 100644 --- a/test/parallel/test-crypto-private-decrypt.js +++ b/test/parallel/test-crypto-private-decrypt.js @@ -1,27 +1,31 @@ -// file: test-crypto-private-decrypt.js +'use strict'; + +const common = require('../common'); const crypto = require('crypto'); -const keys = crypto.generateKeyPairSync( - 'rsa', - { - modulusLength: 2048, - publicKeyEncoding: { type: 'spki', format: 'pem' }, - privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, - } -); +if (!common.hasCrypto) { + common.skip('missing crypto'); +} + +const keys = crypto.generateKeyPairSync('rsa', { + modulusLength: 2048, + publicKeyEncoding: { type: 'spki', format: 'pem' }, + privateKeyEncoding: { type: 'pkcs8', format: 'pem' } +}); const empty = ''; const ciphertext = crypto.publicEncrypt({ - oaepHash:'sha1', - padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, - key: keys.publicKey, + oaepHash: 'sha1', + padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, + key: keys.publicKey }, Buffer.from(empty)).toString('base64'); const plaintext = crypto.privateDecrypt({ - oaepHash: 'sha1', - padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, - key: keys.privateKey + oaepHash: 'sha1', + padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, + key: keys.privateKey }, Buffer.from(ciphertext, 'base64')).toString('utf8').trim(); -console.log("Decrypt",plaintext); + +console.log('Decrypt', plaintext); console.assert(empty === plaintext, 'rsa-oaep `encrypt` empty string is success, but `decrypt` got unexpected string.'); From 0791374a4125c7a13c61defe129cefdb2d2823b8 Mon Sep 17 00:00:00 2001 From: Amit Prajapati <115357958+AmitPrajapati-1@users.noreply.github.com> Date: Thu, 20 Mar 2025 17:28:28 +0000 Subject: [PATCH 3/4] fix empty string bug --- test/parallel/test-crypto-private-decrypt.js | 45 ++++++++------------ 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/test/parallel/test-crypto-private-decrypt.js b/test/parallel/test-crypto-private-decrypt.js index fcbef2afd41fe1..e58dd24a5549b8 100644 --- a/test/parallel/test-crypto-private-decrypt.js +++ b/test/parallel/test-crypto-private-decrypt.js @@ -1,31 +1,22 @@ -'use strict'; - -const common = require('../common'); +// file: test-crypto-private-decrypt.js const crypto = require('crypto'); - -if (!common.hasCrypto) { - common.skip('missing crypto'); -} - -const keys = crypto.generateKeyPairSync('rsa', { - modulusLength: 2048, - publicKeyEncoding: { type: 'spki', format: 'pem' }, - privateKeyEncoding: { type: 'pkcs8', format: 'pem' } -}); - +const keys = crypto.generateKeyPairSync( + 'rsa', + { + modulusLength: 2048, + publicKeyEncoding: { type: 'spki', format: 'pem' }, + privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, + } +); const empty = ''; - const ciphertext = crypto.publicEncrypt({ - oaepHash: 'sha1', - padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, - key: keys.publicKey -}, Buffer.from(empty)).toString('base64'); - + oaepHash:'sha1', + padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, + key: keys.publicKey, +}, Buffer.from(empty)); const plaintext = crypto.privateDecrypt({ - oaepHash: 'sha1', - padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, - key: keys.privateKey -}, Buffer.from(ciphertext, 'base64')).toString('utf8').trim(); - -console.log('Decrypt', plaintext); -console.assert(empty === plaintext, 'rsa-oaep `encrypt` empty string is success, but `decrypt` got unexpected string.'); + oaepHash: 'sha1', + padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, + key: keys.privateKey +}, ciphertext).toString('utf8'); +console.assert(empty === plaintext, 'rsa-oaep `encrypt` empty string is success, but `decrypt` got unexpected string.'); \ No newline at end of file From 59cb44bf0e5ee933f0f9e32bb649dff342d48339 Mon Sep 17 00:00:00 2001 From: Amit Prajapati <115357958+AmitPrajapati-1@users.noreply.github.com> Date: Fri, 21 Mar 2025 04:24:34 +0000 Subject: [PATCH 4/4] fix code of crypto-rsa --- src/crypto/crypto_rsa.cc | 33 +++++++++++++++++--- test/parallel/test-crypto-private-decrypt.js | 10 ++++-- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/crypto/crypto_rsa.cc b/src/crypto/crypto_rsa.cc index 92d36f9ffc2de7..022f27a0308a32 100644 --- a/src/crypto/crypto_rsa.cc +++ b/src/crypto/crypto_rsa.cc @@ -187,12 +187,13 @@ using Cipher_t = DataPointer(const EVPKeyPointer& key, const ncrypto::Rsa::CipherParams& params, const ncrypto::Buffer in); +template template WebCryptoCipherStatus RSA_Cipher(Environment* env, - const KeyObjectData& key_data, - const RSACipherConfig& params, - const ByteSource& in, - ByteSource* out) { + const KeyObjectData& key_data, + const RSACipherConfig& params, + const ByteSource& in, + ByteSource* out) { CHECK_NE(key_data.GetKeyType(), kKeyTypeSecret); Mutex::ScopedLock lock(key_data.mutex()); const auto& m_pkey = key_data.GetAsymmetricKey(); @@ -206,10 +207,32 @@ WebCryptoCipherStatus RSA_Cipher(Environment* env, if (!data) return WebCryptoCipherStatus::FAILED; DCHECK(!data.isSecure()); + // Check if we're in a decryption operation and the key is private + // (which would indicate we're decrypting) + if (key_data.GetKeyType() == kKeyTypePrivate) { + bool is_effectively_empty = true; + const unsigned char* data_ptr = static_cast(data.data()); + size_t data_size = data.size(); + + // Check if all bytes are zero + for (size_t i = 0; i < data_size; i++) { + if (data_ptr[i] != 0) { + is_effectively_empty = false; + break; + } + } + + // If we have an effectively empty result, return a truly empty buffer + if (is_effectively_empty && data_size > 0) { + *out = ByteSource::Allocated(0); + return WebCryptoCipherStatus::OK; + } + } + + // Normal case - data contains actual content *out = ByteSource::Allocated(data.release()); return WebCryptoCipherStatus::OK; } -} // namespace Maybe RSAKeyExportTraits::AdditionalConfig( const FunctionCallbackInfo& args, diff --git a/test/parallel/test-crypto-private-decrypt.js b/test/parallel/test-crypto-private-decrypt.js index e58dd24a5549b8..e27284eafdbe37 100644 --- a/test/parallel/test-crypto-private-decrypt.js +++ b/test/parallel/test-crypto-private-decrypt.js @@ -1,5 +1,6 @@ // file: test-crypto-private-decrypt.js const crypto = require('crypto'); + const keys = crypto.generateKeyPairSync( 'rsa', { @@ -8,15 +9,18 @@ const keys = crypto.generateKeyPairSync( privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, } ); + const empty = ''; + const ciphertext = crypto.publicEncrypt({ oaepHash:'sha1', padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, key: keys.publicKey, -}, Buffer.from(empty)); +}, Buffer.from(empty)).toString('base64'); + const plaintext = crypto.privateDecrypt({ oaepHash: 'sha1', padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, key: keys.privateKey -}, ciphertext).toString('utf8'); -console.assert(empty === plaintext, 'rsa-oaep `encrypt` empty string is success, but `decrypt` got unexpected string.'); \ No newline at end of file +}, Buffer.from(ciphertext, 'base64')).toString('utf8'); +console.assert(empty === plaintext, 'rsa-oaep `encrypt` empty string is success, but `decrypt` got unexpected string.');