mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Switch asymmetric encryption implementation to BoringSSL (#13786)
This commit is contained in:
24
bench/crypto/asymmetricCipher.js
Normal file
24
bench/crypto/asymmetricCipher.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { bench, run } from "mitata";
|
||||
const crypto = require("node:crypto");
|
||||
|
||||
const keyPair = crypto.generateKeyPairSync('rsa', {
|
||||
modulusLength: 2048,
|
||||
publicKeyEncoding: {
|
||||
type: "spki",
|
||||
format: "pem",
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: "pkcs8",
|
||||
format: "pem",
|
||||
},
|
||||
});
|
||||
|
||||
// Max message size for 2048-bit RSA keys
|
||||
const plaintext = crypto.getRandomValues(Buffer.alloc(214));
|
||||
|
||||
bench("RSA_PKCS1_OAEP_PADDING round-trip", () => {
|
||||
const ciphertext = crypto.publicEncrypt(keyPair.publicKey, plaintext);
|
||||
crypto.privateDecrypt(keyPair.privateKey, ciphertext);
|
||||
});
|
||||
|
||||
await run();
|
||||
@@ -21,6 +21,7 @@
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
#include "ErrorCode.h"
|
||||
#include "KeyObject.h"
|
||||
#include "JavaScriptCore/JSArrayBufferView.h"
|
||||
#include "JavaScriptCore/JSCJSValue.h"
|
||||
@@ -47,18 +48,19 @@
|
||||
#include "JSBuffer.h"
|
||||
#include "CryptoAlgorithmHMAC.h"
|
||||
#include "CryptoAlgorithmEd25519.h"
|
||||
#include "CryptoAlgorithmRSA_OAEP.h"
|
||||
#include "CryptoAlgorithmRSA_PSS.h"
|
||||
#include "CryptoAlgorithmRSASSA_PKCS1_v1_5.h"
|
||||
#include "CryptoAlgorithmECDSA.h"
|
||||
#include "CryptoAlgorithmEcdsaParams.h"
|
||||
#include "CryptoAlgorithmRsaOaepParams.h"
|
||||
#include "CryptoAlgorithmRsaPssParams.h"
|
||||
#include "CryptoAlgorithmRegistry.h"
|
||||
#include "wtf/ForbidHeapAllocation.h"
|
||||
#include "wtf/Noncopyable.h"
|
||||
using namespace JSC;
|
||||
using namespace Bun;
|
||||
using JSGlobalObject
|
||||
= JSC::JSGlobalObject;
|
||||
using JSGlobalObject = JSC::JSGlobalObject;
|
||||
using Exception = JSC::Exception;
|
||||
using JSValue = JSC::JSValue;
|
||||
using JSString = JSC::JSString;
|
||||
@@ -81,6 +83,8 @@ JSC_DECLARE_HOST_FUNCTION(KeyObject__generateKeySync);
|
||||
JSC_DECLARE_HOST_FUNCTION(KeyObject__generateKeyPairSync);
|
||||
JSC_DECLARE_HOST_FUNCTION(KeyObject__Sign);
|
||||
JSC_DECLARE_HOST_FUNCTION(KeyObject__Verify);
|
||||
JSC_DECLARE_HOST_FUNCTION(KeyObject__publicEncrypt);
|
||||
JSC_DECLARE_HOST_FUNCTION(KeyObject__privateDecrypt);
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
@@ -571,7 +575,7 @@ JSC_DEFINE_HOST_FUNCTION(KeyObject__createPrivateKey, (JSC::JSGlobalObject * glo
|
||||
return JSValue::encode(JSC::jsUndefined());
|
||||
}
|
||||
auto pKeyID = EVP_PKEY_id(pkey.get());
|
||||
auto impl = CryptoKeyRSA::create(pKeyID == EVP_PKEY_RSA_PSS ? CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5 : CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5, CryptoAlgorithmIdentifier::SHA_1, false, CryptoKeyType::Private, WTFMove(pkey), true, CryptoKeyUsageDecrypt);
|
||||
auto impl = CryptoKeyRSA::create(pKeyID == EVP_PKEY_RSA_PSS ? CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5 : CryptoAlgorithmIdentifier::RSA_OAEP, CryptoAlgorithmIdentifier::SHA_1, false, CryptoKeyType::Private, WTFMove(pkey), true, CryptoKeyUsageDecrypt);
|
||||
return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl)));
|
||||
} else if (type == "pkcs8"_s) {
|
||||
|
||||
@@ -1165,11 +1169,11 @@ JSC_DEFINE_HOST_FUNCTION(KeyObject__createPublicKey, (JSC::JSGlobalObject * glob
|
||||
}
|
||||
|
||||
auto pKeyID = EVP_PKEY_id(pkey.get());
|
||||
return KeyObject__createRSAFromPrivate(globalObject, pkey.get(), pKeyID == EVP_PKEY_RSA_PSS ? CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5 : CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5);
|
||||
return KeyObject__createRSAFromPrivate(globalObject, pkey.get(), pKeyID == EVP_PKEY_RSA_PSS ? CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5 : CryptoAlgorithmIdentifier::RSA_OAEP);
|
||||
}
|
||||
|
||||
auto pKeyID = EVP_PKEY_id(pkey.get());
|
||||
auto impl = CryptoKeyRSA::create(pKeyID == EVP_PKEY_RSA_PSS ? CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5 : CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5, CryptoAlgorithmIdentifier::SHA_1, false, CryptoKeyType::Public, WTFMove(pkey), true, CryptoKeyUsageEncrypt);
|
||||
auto impl = CryptoKeyRSA::create(pKeyID == EVP_PKEY_RSA_PSS ? CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5 : CryptoAlgorithmIdentifier::RSA_OAEP, CryptoAlgorithmIdentifier::SHA_1, false, CryptoKeyType::Public, WTFMove(pkey), true, CryptoKeyUsageEncrypt);
|
||||
return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl)));
|
||||
} else if (type == "spki"_s) {
|
||||
// We use d2i_PUBKEY() to import a public key.
|
||||
@@ -2943,6 +2947,149 @@ JSC_DEFINE_HOST_FUNCTION(KeyObject__SymmetricKeySize, (JSC::JSGlobalObject * glo
|
||||
return JSC::JSValue::encode(JSC::jsUndefined());
|
||||
}
|
||||
|
||||
static EncodedJSValue doAsymmetricCipher(JSGlobalObject* globalObject, CallFrame* callFrame, bool encrypt)
|
||||
{
|
||||
auto count = callFrame->argumentCount();
|
||||
auto& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
if (count != 2) {
|
||||
return Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_MISSING_ARGS,
|
||||
"expected object as first argument"_s);
|
||||
}
|
||||
|
||||
auto* jsKey = jsDynamicCast<JSObject*>(callFrame->uncheckedArgument(0));
|
||||
if (!jsKey) {
|
||||
return Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_TYPE,
|
||||
"expected object as first argument"_s);
|
||||
}
|
||||
|
||||
auto jsCryptoKeyValue = jsKey->getIfPropertyExists(
|
||||
globalObject, PropertyName(Identifier::fromString(vm, "key"_s)));
|
||||
if (jsCryptoKeyValue.isUndefinedOrNull() || jsCryptoKeyValue.isEmpty()) {
|
||||
return Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_TYPE,
|
||||
"expected key property in key object"_s);
|
||||
}
|
||||
auto* jsCryptoKey = jsDynamicCast<JSCryptoKey*>(jsCryptoKeyValue);
|
||||
|
||||
auto& cryptoKey = jsCryptoKey->wrapped();
|
||||
// We should only encrypt to public keys, and decrypt with private keys.
|
||||
if ((encrypt && cryptoKey.type() != CryptoKeyType::Public)
|
||||
|| (!encrypt && cryptoKey.type() != CryptoKeyType::Private)
|
||||
// RSA-OAEP is the modern alternative to RSAES-PKCS1-v1_5, which is vulnerable to
|
||||
// known-ciphertext attacks. Node.js does not support it either.
|
||||
|| cryptoKey.algorithmIdentifier() != CryptoAlgorithmIdentifier::RSA_OAEP) {
|
||||
return Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_VALUE,
|
||||
"unsupported key type for asymmetric encryption"_s);
|
||||
}
|
||||
|
||||
bool setCustomHash = false;
|
||||
auto oaepHash = WebCore::CryptoAlgorithmIdentifier::SHA_1;
|
||||
auto jsOaepHash = jsKey->getIfPropertyExists(
|
||||
globalObject, PropertyName(Identifier::fromString(vm, "oaepHash"_s)));
|
||||
if (!jsOaepHash.isUndefined() && !jsOaepHash.isEmpty()) {
|
||||
if (UNLIKELY(!jsOaepHash.isString())) {
|
||||
return Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_TYPE,
|
||||
"expected string for oaepHash"_s);
|
||||
}
|
||||
auto oaepHashStr = jsOaepHash.toWTFString(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, encodedJSValue());
|
||||
|
||||
auto oaepHashId = CryptoAlgorithmRegistry::singleton().identifier(oaepHashStr);
|
||||
if (UNLIKELY(!oaepHashId)) {
|
||||
return Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_CRYPTO_INVALID_DIGEST,
|
||||
"unsupported digest for oaepHash"_s);
|
||||
}
|
||||
switch (*oaepHashId) {
|
||||
case WebCore::CryptoAlgorithmIdentifier::SHA_1:
|
||||
case WebCore::CryptoAlgorithmIdentifier::SHA_224:
|
||||
case WebCore::CryptoAlgorithmIdentifier::SHA_256:
|
||||
case WebCore::CryptoAlgorithmIdentifier::SHA_384:
|
||||
case WebCore::CryptoAlgorithmIdentifier::SHA_512: {
|
||||
setCustomHash = true;
|
||||
oaepHash = *oaepHashId;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_CRYPTO_INVALID_DIGEST,
|
||||
"unsupported digest for oaepHash"_s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<BufferSource::VariantType> oaepLabel = std::nullopt;
|
||||
auto jsOaepLabel = jsKey->getIfPropertyExists(
|
||||
globalObject, PropertyName(Identifier::fromString(vm, "oaepLabel"_s)));
|
||||
if (!jsOaepLabel.isUndefined() && !jsOaepLabel.isEmpty()) {
|
||||
if (UNLIKELY(!jsOaepLabel.isCell())) {
|
||||
return Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_TYPE,
|
||||
"expected Buffer or array-like object for oaepLabel"_s);
|
||||
}
|
||||
auto jsOaepLabelCell = jsOaepLabel.asCell();
|
||||
auto jsOaepLabelType = jsOaepLabelCell->type();
|
||||
|
||||
if (isTypedArrayTypeIncludingDataView(jsOaepLabelType)) {
|
||||
auto* jsBufferView = jsCast<JSArrayBufferView*>(jsOaepLabelCell);
|
||||
oaepLabel = std::optional<BufferSource::VariantType>{jsBufferView->unsharedImpl()};
|
||||
} else if (jsOaepLabelType == ArrayBufferType) {
|
||||
auto* jsBuffer = jsDynamicCast<JSArrayBuffer*>(jsOaepLabelCell);
|
||||
oaepLabel = std::optional<BufferSource::VariantType>{jsBuffer->impl()};
|
||||
} else {
|
||||
return Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_TYPE,
|
||||
"expected Buffer or array-like object for oaepLabel"_s);
|
||||
}
|
||||
}
|
||||
|
||||
auto padding = RSA_PKCS1_OAEP_PADDING;
|
||||
auto jsPadding = jsKey->getIfPropertyExists(
|
||||
globalObject, PropertyName(Identifier::fromString(vm, "padding"_s)));
|
||||
if (!jsPadding.isUndefinedOrNull() && !jsPadding.isEmpty()) {
|
||||
if (UNLIKELY(!jsPadding.isNumber())) {
|
||||
return Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_TYPE,
|
||||
"expected number for padding"_s);
|
||||
}
|
||||
padding = jsPadding.toUInt32(globalObject);
|
||||
if (padding == RSA_PKCS1_PADDING && !encrypt) {
|
||||
return Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_VALUE,
|
||||
"RSA_PKCS1_PADDING is no longer supported for private decryption"_s);
|
||||
}
|
||||
if (padding != RSA_PKCS1_OAEP_PADDING && (setCustomHash || oaepLabel.has_value())) {
|
||||
return Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_VALUE,
|
||||
"oaepHash/oaepLabel cannot be set without RSA_PKCS1_OAEP_PADDING"_s);
|
||||
}
|
||||
}
|
||||
|
||||
auto jsBuffer = KeyObject__GetBuffer(callFrame->uncheckedArgument(1));
|
||||
if (jsBuffer.hasException()) {
|
||||
return Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_TYPE,
|
||||
"expected Buffer or array-like object as second argument"_s);
|
||||
}
|
||||
auto buffer = jsBuffer.releaseReturnValue();
|
||||
|
||||
auto params = CryptoAlgorithmRsaOaepParams{};
|
||||
params.label = oaepLabel;
|
||||
params.padding = padding;
|
||||
const auto& rsaKey = downcast<CryptoKeyRSA>(cryptoKey);
|
||||
auto operation = encrypt ? CryptoAlgorithmRSA_OAEP::platformEncryptWithHash : CryptoAlgorithmRSA_OAEP::platformDecryptWithHash;
|
||||
auto result = operation(params, rsaKey, buffer, oaepHash);
|
||||
if (result.hasException()) {
|
||||
WebCore::propagateException(*globalObject, scope, result.releaseException());
|
||||
return encodedJSUndefined();
|
||||
}
|
||||
auto outBuffer = result.releaseReturnValue();
|
||||
return JSValue::encode(WebCore::createBuffer(globalObject, outBuffer));
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(KeyObject__publicEncrypt, (JSGlobalObject* globalObject, CallFrame* callFrame))
|
||||
{
|
||||
return doAsymmetricCipher(globalObject, callFrame, true);
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(KeyObject__privateDecrypt, (JSGlobalObject* globalObject, CallFrame* callFrame))
|
||||
{
|
||||
return doAsymmetricCipher(globalObject, callFrame, false);
|
||||
}
|
||||
|
||||
JSValue createNodeCryptoBinding(Zig::GlobalObject* globalObject)
|
||||
{
|
||||
VM& vm = globalObject->vm();
|
||||
@@ -2974,6 +3121,11 @@ JSValue createNodeCryptoBinding(Zig::GlobalObject* globalObject)
|
||||
obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "sign"_s)), JSC::JSFunction::create(vm, globalObject, 3, "sign"_s, KeyObject__Sign, ImplementationVisibility::Public, NoIntrinsic), 0);
|
||||
obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "verify"_s)), JSC::JSFunction::create(vm, globalObject, 4, "verify"_s, KeyObject__Verify, ImplementationVisibility::Public, NoIntrinsic), 0);
|
||||
|
||||
obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "publicEncrypt"_s)),
|
||||
JSFunction::create(vm, globalObject, 2, "publicEncrypt"_s, KeyObject__publicEncrypt, ImplementationVisibility::Public, NoIntrinsic), 0);
|
||||
obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "privateDecrypt"_s)),
|
||||
JSFunction::create(vm, globalObject, 2, "privateDecrypt"_s, KeyObject__privateDecrypt, ImplementationVisibility::Public, NoIntrinsic), 0);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,9 @@ public:
|
||||
static constexpr CryptoAlgorithmIdentifier s_identifier = CryptoAlgorithmIdentifier::RSA_OAEP;
|
||||
static Ref<CryptoAlgorithm> create();
|
||||
|
||||
static ExceptionOr<Vector<uint8_t>> platformEncryptWithHash(const CryptoAlgorithmRsaOaepParams&, const CryptoKeyRSA&, const Vector<uint8_t>&, CryptoAlgorithmIdentifier hashIdentifier);
|
||||
static ExceptionOr<Vector<uint8_t>> platformDecryptWithHash(const CryptoAlgorithmRsaOaepParams&, const CryptoKeyRSA&, const Vector<uint8_t>&, CryptoAlgorithmIdentifier hashIdentifier);
|
||||
|
||||
private:
|
||||
CryptoAlgorithmRSA_OAEP() = default;
|
||||
CryptoAlgorithmIdentifier identifier() const final;
|
||||
|
||||
@@ -38,11 +38,11 @@ namespace WebCore {
|
||||
|
||||
ExceptionOr<Vector<uint8_t>> CryptoAlgorithmRSA_OAEP::platformEncrypt(const CryptoAlgorithmRsaOaepParams& parameters, const CryptoKeyRSA& key, const Vector<uint8_t>& plainText)
|
||||
{
|
||||
#if 1 // defined(EVP_PKEY_CTX_set_rsa_oaep_md) && defined(EVP_PKEY_CTX_set_rsa_mgf1_md) && defined(EVP_PKEY_CTX_set0_rsa_oaep_label)
|
||||
const EVP_MD* md = digestAlgorithm(key.hashAlgorithmIdentifier());
|
||||
if (!md)
|
||||
return Exception { NotSupportedError };
|
||||
return CryptoAlgorithmRSA_OAEP::platformEncryptWithHash(parameters, key, plainText, key.hashAlgorithmIdentifier());
|
||||
}
|
||||
|
||||
ExceptionOr<Vector<uint8_t>> CryptoAlgorithmRSA_OAEP::platformEncryptWithHash(const CryptoAlgorithmRsaOaepParams& parameters, const CryptoKeyRSA& key, const Vector<uint8_t>& plainText, CryptoAlgorithmIdentifier hashIdentifier)
|
||||
{
|
||||
auto ctx = EvpPKeyCtxPtr(EVP_PKEY_CTX_new(key.platformKey(), nullptr));
|
||||
if (!ctx)
|
||||
return Exception { OperationError };
|
||||
@@ -50,14 +50,25 @@ ExceptionOr<Vector<uint8_t>> CryptoAlgorithmRSA_OAEP::platformEncrypt(const Cryp
|
||||
if (EVP_PKEY_encrypt_init(ctx.get()) <= 0)
|
||||
return Exception { OperationError };
|
||||
|
||||
if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING) <= 0)
|
||||
auto padding = parameters.padding;
|
||||
if (padding == 0) {
|
||||
padding = RSA_PKCS1_OAEP_PADDING;
|
||||
}
|
||||
|
||||
if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), padding) <= 0)
|
||||
return Exception { OperationError };
|
||||
|
||||
if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx.get(), md) <= 0)
|
||||
return Exception { OperationError };
|
||||
if (padding == RSA_PKCS1_OAEP_PADDING) {
|
||||
const EVP_MD* md = digestAlgorithm(hashIdentifier);
|
||||
if (!md)
|
||||
return Exception { NotSupportedError };
|
||||
|
||||
if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx.get(), md) <= 0)
|
||||
return Exception { OperationError };
|
||||
if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx.get(), md) <= 0)
|
||||
return Exception { OperationError };
|
||||
|
||||
if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx.get(), md) <= 0)
|
||||
return Exception { OperationError };
|
||||
}
|
||||
|
||||
if (!parameters.labelVector().isEmpty()) {
|
||||
size_t labelSize = parameters.labelVector().size();
|
||||
@@ -80,18 +91,15 @@ ExceptionOr<Vector<uint8_t>> CryptoAlgorithmRSA_OAEP::platformEncrypt(const Cryp
|
||||
cipherText.shrink(cipherTextLen);
|
||||
|
||||
return cipherText;
|
||||
#else
|
||||
return Exception { NotSupportedError };
|
||||
#endif
|
||||
}
|
||||
|
||||
ExceptionOr<Vector<uint8_t>> CryptoAlgorithmRSA_OAEP::platformDecrypt(const CryptoAlgorithmRsaOaepParams& parameters, const CryptoKeyRSA& key, const Vector<uint8_t>& cipherText)
|
||||
{
|
||||
#if 1 // defined(EVP_PKEY_CTX_set_rsa_oaep_md) && defined(EVP_PKEY_CTX_set_rsa_mgf1_md) && defined(EVP_PKEY_CTX_set0_rsa_oaep_label)
|
||||
const EVP_MD* md = digestAlgorithm(key.hashAlgorithmIdentifier());
|
||||
if (!md)
|
||||
return Exception { NotSupportedError };
|
||||
return CryptoAlgorithmRSA_OAEP::platformDecryptWithHash(parameters, key, cipherText, key.hashAlgorithmIdentifier());
|
||||
}
|
||||
|
||||
ExceptionOr<Vector<uint8_t>> CryptoAlgorithmRSA_OAEP::platformDecryptWithHash(const CryptoAlgorithmRsaOaepParams& parameters, const CryptoKeyRSA& key, const Vector<uint8_t>& cipherText, CryptoAlgorithmIdentifier hashIdentifier)
|
||||
{
|
||||
auto ctx = EvpPKeyCtxPtr(EVP_PKEY_CTX_new(key.platformKey(), nullptr));
|
||||
if (!ctx)
|
||||
return Exception { OperationError };
|
||||
@@ -99,14 +107,25 @@ ExceptionOr<Vector<uint8_t>> CryptoAlgorithmRSA_OAEP::platformDecrypt(const Cryp
|
||||
if (EVP_PKEY_decrypt_init(ctx.get()) <= 0)
|
||||
return Exception { OperationError };
|
||||
|
||||
if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING) <= 0)
|
||||
auto padding = parameters.padding;
|
||||
if (padding == 0) {
|
||||
padding = RSA_PKCS1_OAEP_PADDING;
|
||||
}
|
||||
|
||||
if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), padding) <= 0)
|
||||
return Exception { OperationError };
|
||||
|
||||
if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx.get(), md) <= 0)
|
||||
return Exception { OperationError };
|
||||
if (padding == RSA_PKCS1_OAEP_PADDING) {
|
||||
const EVP_MD* md = digestAlgorithm(hashIdentifier);
|
||||
if (!md)
|
||||
return Exception { NotSupportedError };
|
||||
|
||||
if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx.get(), md) <= 0)
|
||||
return Exception { OperationError };
|
||||
if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx.get(), md) <= 0)
|
||||
return Exception { OperationError };
|
||||
|
||||
if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx.get(), md) <= 0)
|
||||
return Exception { OperationError };
|
||||
}
|
||||
|
||||
if (!parameters.labelVector().isEmpty()) {
|
||||
size_t labelSize = parameters.labelVector().size();
|
||||
@@ -129,9 +148,6 @@ ExceptionOr<Vector<uint8_t>> CryptoAlgorithmRSA_OAEP::platformDecrypt(const Cryp
|
||||
plainText.shrink(plainTextLen);
|
||||
|
||||
return plainText;
|
||||
#else
|
||||
return Exception { NotSupportedError };
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace WebCore
|
||||
|
||||
@@ -37,6 +37,7 @@ class CryptoAlgorithmRsaOaepParams final : public CryptoAlgorithmParameters {
|
||||
public:
|
||||
// Use labelVector() instead of label. The label will be gone once labelVector() is called.
|
||||
mutable std::optional<BufferSource::VariantType> label;
|
||||
size_t padding = 0; // 0 represents the default value of the API
|
||||
|
||||
Class parametersClass() const final { return Class::RsaOaepParams; }
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ const {
|
||||
generateKeyPairSync,
|
||||
sign: nativeSign,
|
||||
verify: nativeVerify,
|
||||
publicEncrypt,
|
||||
privateDecrypt,
|
||||
} = $cpp("KeyObject.cpp", "createNodeCryptoBinding");
|
||||
|
||||
const {
|
||||
@@ -11610,16 +11612,10 @@ var require_privateDecrypt = __commonJS({
|
||||
var require_browser10 = __commonJS({
|
||||
"node_modules/public-encrypt/browser.js"(exports) {
|
||||
var publicEncrypt = require_publicEncrypt();
|
||||
exports.publicEncrypt = function (key, buf, options) {
|
||||
return publicEncrypt(getKeyFrom(key, "public"), buf, options);
|
||||
};
|
||||
var privateDecrypt = require_privateDecrypt();
|
||||
exports.privateDecrypt = function (key, buf, options) {
|
||||
return privateDecrypt(getKeyFrom(key, "private"), buf, options);
|
||||
};
|
||||
exports.privateEncrypt = function (key, buf) {
|
||||
return publicEncrypt(getKeyFrom(key, "private"), buf, !0);
|
||||
};
|
||||
var privateDecrypt = require_privateDecrypt();
|
||||
exports.publicDecrypt = function (key, buf) {
|
||||
return privateDecrypt(getKeyFrom(key, "public"), buf, !0);
|
||||
};
|
||||
@@ -11721,10 +11717,8 @@ var require_crypto_browserify2 = __commonJS({
|
||||
exports.Verify = sign.Verify;
|
||||
exports.createECDH = require_browser9();
|
||||
var publicEncrypt = require_browser10();
|
||||
exports.publicEncrypt = publicEncrypt.publicEncrypt;
|
||||
exports.privateEncrypt = publicEncrypt.privateEncrypt;
|
||||
exports.publicDecrypt = publicEncrypt.publicDecrypt;
|
||||
exports.privateDecrypt = publicEncrypt.privateDecrypt;
|
||||
exports.getRandomValues = values => crypto.getRandomValues(values);
|
||||
var rf = require_browser11();
|
||||
exports.randomFill = rf.randomFill;
|
||||
@@ -12034,7 +12028,7 @@ function _createPublicKey(key) {
|
||||
}
|
||||
return KeyObject.from(
|
||||
createPublicKey({
|
||||
key: createPrivateKey({ key: actual_key, format: key.format, passphrase: key.passphrase }),
|
||||
key: createPrivateKey({ key: actual_key, format: key.format || "pem", passphrase: key.passphrase }),
|
||||
format: "",
|
||||
}),
|
||||
);
|
||||
@@ -12166,6 +12160,52 @@ crypto_exports.verify = function (algorithm, data, key, signature, callback) {
|
||||
}
|
||||
};
|
||||
|
||||
// We are not allowed to call createPublicKey/createPrivateKey when we're already working with a
|
||||
// KeyObject/CryptoKey of the same type (public/private).
|
||||
function toCryptoKey(key, asPublic) {
|
||||
// Top level CryptoKey.
|
||||
if (key instanceof KeyObject || key instanceof CryptoKey) {
|
||||
if (asPublic && key.type === "private") {
|
||||
return _createPublicKey(key).$bunNativePtr;
|
||||
}
|
||||
return key.$bunNativePtr || key;
|
||||
}
|
||||
|
||||
// Nested CryptoKey.
|
||||
if (key.key instanceof KeyObject || key.key instanceof CryptoKey) {
|
||||
if (asPublic && key.key.type === "private") {
|
||||
return _createPublicKey(key.key).$bunNativePtr;
|
||||
}
|
||||
return key.key.$bunNativePtr || key.key;
|
||||
}
|
||||
|
||||
// One of string, ArrayBuffer, Buffer, TypedArray, DataView, or Object.
|
||||
return asPublic ? _createPublicKey(key).$bunNativePtr : _createPrivateKey(key).$bunNativePtr;
|
||||
}
|
||||
|
||||
function doAsymmetricCipher(key, message, operation, isEncrypt) {
|
||||
// Our crypto bindings expect the key to be a `JSCryptoKey` property within an object.
|
||||
const cryptoKey = toCryptoKey(key, isEncrypt);
|
||||
const oaepLabel =
|
||||
typeof key.oaepLabel === "string" ? Buffer.from(key.oaepLabel, key.encoding) : key.oaepLabel;
|
||||
const keyObject = {
|
||||
key: cryptoKey,
|
||||
oaepHash: key.oaepHash,
|
||||
oaepLabel,
|
||||
padding: key.padding,
|
||||
};
|
||||
const buffer = typeof message === "string" ? Buffer.from(message, key.encoding) : message;
|
||||
return operation(keyObject, buffer);
|
||||
}
|
||||
|
||||
crypto_exports.publicEncrypt = function(key, message) {
|
||||
return doAsymmetricCipher(key, message, publicEncrypt, true);
|
||||
}
|
||||
|
||||
crypto_exports.privateDecrypt = function(key, message) {
|
||||
return doAsymmetricCipher(key, message, privateDecrypt, false);
|
||||
}
|
||||
|
||||
__export(crypto_exports, {
|
||||
DEFAULT_ENCODING: () => DEFAULT_ENCODING,
|
||||
getRandomValues: () => getRandomValues,
|
||||
|
||||
405
test/js/node/crypto/crypto-rsa.test.js
Normal file
405
test/js/node/crypto/crypto-rsa.test.js
Normal file
@@ -0,0 +1,405 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// Copied from https://github.com/nodejs/node/blob/dcc2ed944f641004c0339bf76db58ccfefedd138/test/parallel/test-crypto-rsa-dsa.js
|
||||
|
||||
const crypto = require("crypto");
|
||||
const constants = crypto.constants;
|
||||
const fixtures = require("../test/common/fixtures");
|
||||
|
||||
// Test certificates
|
||||
const certPem = fixtures.readKey("rsa_cert.crt");
|
||||
const keyPem = fixtures.readKey("rsa_private.pem");
|
||||
const rsaKeySize = 2048;
|
||||
const rsaPubPem = fixtures.readKey("rsa_public.pem", "ascii");
|
||||
const rsaKeyPem = fixtures.readKey("rsa_private.pem", "ascii");
|
||||
const rsaKeyPemEncrypted = fixtures.readKey("rsa_private_encrypted.pem", "ascii");
|
||||
const rsaPkcs8KeyPem = fixtures.readKey("rsa_private_pkcs8.pem");
|
||||
|
||||
const ec = new TextEncoder();
|
||||
|
||||
const decryptError = {
|
||||
message: expect.any(String),
|
||||
};
|
||||
|
||||
function getBufferCopy(buf) {
|
||||
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
||||
}
|
||||
|
||||
describe("RSA encryption/decryption", () => {
|
||||
const input = "I AM THE WALRUS";
|
||||
const bufferToEncrypt = Buffer.from(input);
|
||||
const bufferPassword = Buffer.from("password");
|
||||
|
||||
let encryptedBuffer;
|
||||
let otherEncrypted;
|
||||
|
||||
beforeAll(() => {
|
||||
encryptedBuffer = crypto.publicEncrypt(rsaPubPem, bufferToEncrypt);
|
||||
|
||||
const ab = getBufferCopy(ec.encode(rsaPubPem));
|
||||
const ab2enc = getBufferCopy(bufferToEncrypt);
|
||||
|
||||
crypto.publicEncrypt(ab, ab2enc);
|
||||
crypto.publicEncrypt(new Uint8Array(ab), new Uint8Array(ab2enc));
|
||||
crypto.publicEncrypt(new DataView(ab), new DataView(ab2enc));
|
||||
otherEncrypted = crypto.publicEncrypt(
|
||||
{
|
||||
key: Buffer.from(ab).toString("hex"),
|
||||
encoding: "hex",
|
||||
},
|
||||
Buffer.from(ab2enc).toString("hex"),
|
||||
);
|
||||
});
|
||||
|
||||
test("privateDecrypt with rsaKeyPem", () => {
|
||||
const decryptedBuffer = crypto.privateDecrypt(rsaKeyPem, encryptedBuffer);
|
||||
expect(decryptedBuffer.toString()).toBe(input);
|
||||
});
|
||||
|
||||
test("privateDecrypt with otherEncrypted", () => {
|
||||
const otherDecrypted = crypto.privateDecrypt(rsaKeyPem, otherEncrypted);
|
||||
expect(otherDecrypted.toString()).toBe(input);
|
||||
});
|
||||
|
||||
test("privateDecrypt with rsaPkcs8KeyPem", () => {
|
||||
const decryptedBuffer = crypto.privateDecrypt(rsaPkcs8KeyPem, encryptedBuffer);
|
||||
expect(decryptedBuffer.toString()).toBe(input);
|
||||
});
|
||||
|
||||
test("privateDecrypt with password", () => {
|
||||
const decryptedBufferWithPassword = crypto.privateDecrypt(
|
||||
{
|
||||
key: rsaKeyPemEncrypted,
|
||||
passphrase: "password",
|
||||
},
|
||||
encryptedBuffer,
|
||||
);
|
||||
expect(decryptedBufferWithPassword.toString()).toBe(input);
|
||||
|
||||
const otherDecryptedBufferWithPassword = crypto.privateDecrypt(
|
||||
{
|
||||
key: rsaKeyPemEncrypted,
|
||||
passphrase: ec.encode("password"),
|
||||
},
|
||||
encryptedBuffer,
|
||||
);
|
||||
expect(otherDecryptedBufferWithPassword.toString()).toBe(decryptedBufferWithPassword.toString());
|
||||
});
|
||||
|
||||
test("publicEncrypt and privateDecrypt with password", () => {
|
||||
const encryptedBuffer = crypto.publicEncrypt(
|
||||
{
|
||||
key: rsaKeyPemEncrypted,
|
||||
passphrase: "password",
|
||||
},
|
||||
bufferToEncrypt,
|
||||
);
|
||||
|
||||
const decryptedBufferWithPassword = crypto.privateDecrypt(
|
||||
{
|
||||
key: rsaKeyPemEncrypted,
|
||||
passphrase: "password",
|
||||
},
|
||||
encryptedBuffer,
|
||||
);
|
||||
expect(decryptedBufferWithPassword.toString()).toBe(input);
|
||||
});
|
||||
|
||||
test("privateEncrypt and publicDecrypt with buffer password", () => {
|
||||
const encryptedBuffer = crypto.privateEncrypt(
|
||||
{
|
||||
key: rsaKeyPemEncrypted,
|
||||
passphrase: bufferPassword,
|
||||
},
|
||||
bufferToEncrypt,
|
||||
);
|
||||
|
||||
const decryptedBufferWithPassword = crypto.publicDecrypt(
|
||||
{
|
||||
key: rsaKeyPemEncrypted,
|
||||
passphrase: bufferPassword,
|
||||
},
|
||||
encryptedBuffer,
|
||||
);
|
||||
expect(decryptedBufferWithPassword.toString()).toBe(input);
|
||||
});
|
||||
|
||||
test("privateEncrypt and publicDecrypt with RSA_PKCS1_PADDING", () => {
|
||||
const encryptedBuffer = crypto.privateEncrypt(
|
||||
{
|
||||
padding: crypto.constants.RSA_PKCS1_PADDING,
|
||||
key: rsaKeyPemEncrypted,
|
||||
passphrase: bufferPassword,
|
||||
},
|
||||
bufferToEncrypt,
|
||||
);
|
||||
|
||||
const decryptedBufferWithPassword = crypto.publicDecrypt(
|
||||
{
|
||||
padding: crypto.constants.RSA_PKCS1_PADDING,
|
||||
key: rsaKeyPemEncrypted,
|
||||
passphrase: bufferPassword,
|
||||
},
|
||||
encryptedBuffer,
|
||||
);
|
||||
expect(decryptedBufferWithPassword.toString()).toBe(input);
|
||||
|
||||
const decryptedBufferWithoutPadding = crypto.publicDecrypt(
|
||||
{
|
||||
key: rsaKeyPemEncrypted,
|
||||
passphrase: bufferPassword,
|
||||
},
|
||||
encryptedBuffer,
|
||||
);
|
||||
expect(decryptedBufferWithoutPadding.toString()).toBe(input);
|
||||
});
|
||||
|
||||
test("publicEncrypt and privateDecrypt with certPem and keyPem", () => {
|
||||
const encryptedBuffer = crypto.publicEncrypt(certPem, bufferToEncrypt);
|
||||
const decryptedBuffer = crypto.privateDecrypt(keyPem, encryptedBuffer);
|
||||
expect(decryptedBuffer.toString()).toBe(input);
|
||||
});
|
||||
|
||||
test("publicEncrypt and privateDecrypt with keyPem", () => {
|
||||
const encryptedBuffer = crypto.publicEncrypt(keyPem, bufferToEncrypt);
|
||||
const decryptedBuffer = crypto.privateDecrypt(keyPem, encryptedBuffer);
|
||||
expect(decryptedBuffer.toString()).toBe(input);
|
||||
});
|
||||
|
||||
test("privateEncrypt and publicDecrypt with keyPem", () => {
|
||||
const encryptedBuffer = crypto.privateEncrypt(keyPem, bufferToEncrypt);
|
||||
const decryptedBuffer = crypto.publicDecrypt(keyPem, encryptedBuffer);
|
||||
expect(decryptedBuffer.toString()).toBe(input);
|
||||
});
|
||||
|
||||
test("privateDecrypt with wrong password", () => {
|
||||
expect(() => {
|
||||
crypto.privateDecrypt(
|
||||
{
|
||||
key: rsaKeyPemEncrypted,
|
||||
passphrase: "wrong",
|
||||
},
|
||||
bufferToEncrypt,
|
||||
);
|
||||
}).toThrow(expect.objectContaining(decryptError));
|
||||
});
|
||||
|
||||
test("publicEncrypt with wrong password", () => {
|
||||
expect(() => {
|
||||
crypto.publicEncrypt(
|
||||
{
|
||||
key: rsaKeyPemEncrypted,
|
||||
passphrase: "wrong",
|
||||
},
|
||||
encryptedBuffer,
|
||||
);
|
||||
}).toThrow(expect.objectContaining(decryptError));
|
||||
});
|
||||
|
||||
test("publicDecrypt with wrong password", () => {
|
||||
const encryptedBuffer = crypto.privateEncrypt(
|
||||
{
|
||||
key: rsaKeyPemEncrypted,
|
||||
passphrase: Buffer.from("password"),
|
||||
},
|
||||
bufferToEncrypt,
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
crypto.publicDecrypt(
|
||||
{
|
||||
key: rsaKeyPemEncrypted,
|
||||
passphrase: Buffer.from("wrong"),
|
||||
},
|
||||
encryptedBuffer,
|
||||
);
|
||||
}).toThrow(expect.objectContaining(decryptError));
|
||||
});
|
||||
});
|
||||
|
||||
function test_rsa(padding, encryptOaepHash, decryptOaepHash, exceptionThrown) {
|
||||
const size = padding === "RSA_NO_PADDING" ? rsaKeySize / 8 : 32;
|
||||
const input = Buffer.allocUnsafe(size);
|
||||
for (let i = 0; i < input.length; i++) input[i] = (i * 7 + 11) & 0xff;
|
||||
const bufferToEncrypt = Buffer.from(input);
|
||||
|
||||
padding = constants[padding];
|
||||
|
||||
const encryptedBuffer = crypto.publicEncrypt(
|
||||
{
|
||||
key: rsaPubPem,
|
||||
padding: padding,
|
||||
oaepHash: encryptOaepHash,
|
||||
},
|
||||
bufferToEncrypt,
|
||||
);
|
||||
|
||||
if (padding === constants.RSA_PKCS1_PADDING) {
|
||||
expect(() => {
|
||||
crypto.privateDecrypt(
|
||||
{
|
||||
key: rsaKeyPem,
|
||||
padding: padding,
|
||||
oaepHash: decryptOaepHash,
|
||||
},
|
||||
encryptedBuffer,
|
||||
);
|
||||
}).toThrow(expect.objectContaining({ code: "ERR_INVALID_ARG_VALUE" }));
|
||||
|
||||
expect(() => {
|
||||
crypto.privateDecrypt(
|
||||
{
|
||||
key: rsaPkcs8KeyPem,
|
||||
padding: padding,
|
||||
oaepHash: decryptOaepHash,
|
||||
},
|
||||
encryptedBuffer,
|
||||
);
|
||||
}).toThrow(expect.objectContaining({ code: "ERR_INVALID_ARG_VALUE" }));
|
||||
} else {
|
||||
const decryptedBuffer = crypto.privateDecrypt(
|
||||
{
|
||||
key: rsaKeyPem,
|
||||
padding: padding,
|
||||
oaepHash: decryptOaepHash,
|
||||
},
|
||||
encryptedBuffer,
|
||||
);
|
||||
expect(decryptedBuffer).toEqual(input);
|
||||
|
||||
const decryptedBufferPkcs8 = crypto.privateDecrypt(
|
||||
{
|
||||
key: rsaPkcs8KeyPem,
|
||||
padding: padding,
|
||||
oaepHash: decryptOaepHash,
|
||||
},
|
||||
encryptedBuffer,
|
||||
);
|
||||
expect(decryptedBufferPkcs8).toEqual(input);
|
||||
}
|
||||
}
|
||||
|
||||
test(`RSA with RSA_NO_PADDING`, () => {
|
||||
test_rsa("RSA_NO_PADDING");
|
||||
});
|
||||
|
||||
test(`RSA with RSA_PKCS1_PADDING`, () => {
|
||||
test_rsa("RSA_PKCS1_PADDING");
|
||||
});
|
||||
|
||||
test(`RSA with RSA_PKCS1_OAEP_PADDING`, () => {
|
||||
test_rsa("RSA_PKCS1_OAEP_PADDING");
|
||||
test_rsa("RSA_PKCS1_OAEP_PADDING", undefined, "sha1");
|
||||
test_rsa("RSA_PKCS1_OAEP_PADDING", "sha1", undefined);
|
||||
test_rsa("RSA_PKCS1_OAEP_PADDING", "sha256", "sha256");
|
||||
test_rsa("RSA_PKCS1_OAEP_PADDING", "sha512", "sha512");
|
||||
});
|
||||
|
||||
test(`RSA with hash mismatch`, () => {
|
||||
expect(() => {
|
||||
test_rsa("RSA_PKCS1_OAEP_PADDING", "sha256", "sha512");
|
||||
}).toThrow(expect.objectContaining(decryptError));
|
||||
});
|
||||
|
||||
test("RSA-OAEP test vectors", () => {
|
||||
const { decryptionTests } = JSON.parse(fixtures.readSync("rsa-oaep-test-vectors.js", "utf8"));
|
||||
|
||||
for (const { ct, oaepHash, oaepLabel } of decryptionTests) {
|
||||
const label = oaepLabel ? Buffer.from(oaepLabel, "hex") : undefined;
|
||||
const copiedLabel = oaepLabel ? getBufferCopy(label) : undefined;
|
||||
|
||||
const decrypted = crypto.privateDecrypt(
|
||||
{
|
||||
key: rsaPkcs8KeyPem,
|
||||
oaepHash,
|
||||
oaepLabel: oaepLabel ? label : undefined,
|
||||
},
|
||||
Buffer.from(ct, "hex"),
|
||||
);
|
||||
|
||||
expect(decrypted.toString("utf8")).toBe("Hello Node.js");
|
||||
|
||||
const otherDecrypted = crypto.privateDecrypt(
|
||||
{
|
||||
key: rsaPkcs8KeyPem,
|
||||
oaepHash,
|
||||
oaepLabel: copiedLabel,
|
||||
},
|
||||
Buffer.from(ct, "hex"),
|
||||
);
|
||||
|
||||
expect(otherDecrypted.toString("utf8")).toBe("Hello Node.js");
|
||||
}
|
||||
});
|
||||
|
||||
describe("Invalid oaepHash and oaepLabel options", () => {
|
||||
const testCases = [
|
||||
{ fn: crypto.publicEncrypt, name: "publicEncrypt", key: rsaPubPem },
|
||||
{ fn: crypto.privateDecrypt, name: "privateDecrypt", key: rsaKeyPem },
|
||||
];
|
||||
|
||||
testCases.forEach(({ fn, name, key }) => {
|
||||
test(`${name} with invalid oaepHash`, () => {
|
||||
expect(() => {
|
||||
fn(
|
||||
{
|
||||
key,
|
||||
oaepHash: "Hello world",
|
||||
},
|
||||
Buffer.alloc(10),
|
||||
);
|
||||
}).toThrow(expect.objectContaining({
|
||||
code: "ERR_CRYPTO_INVALID_DIGEST",
|
||||
}));
|
||||
|
||||
[0, false, null, Symbol(), () => {}].forEach(oaepHash => {
|
||||
expect(() => {
|
||||
fn(
|
||||
{
|
||||
key,
|
||||
oaepHash,
|
||||
},
|
||||
Buffer.alloc(10),
|
||||
);
|
||||
}).toThrow(expect.objectContaining({
|
||||
code: "ERR_INVALID_ARG_TYPE",
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
test(`${name} with invalid oaepLabel`, () => {
|
||||
[0, false, null, Symbol(), () => {}, {}].forEach(oaepLabel => {
|
||||
expect(() => {
|
||||
fn(
|
||||
{
|
||||
key,
|
||||
oaepLabel,
|
||||
},
|
||||
Buffer.alloc(10),
|
||||
);
|
||||
}).toThrow(expect.objectContaining({
|
||||
code: "ERR_INVALID_ARG_TYPE",
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user