diff --git a/docs/runtime/nodejs-apis.md b/docs/runtime/nodejs-apis.md index 7050fb9683..a41a7fa2a3 100644 --- a/docs/runtime/nodejs-apis.md +++ b/docs/runtime/nodejs-apis.md @@ -106,8 +106,6 @@ This page is updated regularly to reflect compatibility status of the latest ver 🟡 Missing `secureHeapUsed` `setEngine` `setFips` -Some methods are not optimized yet. - ### [`node:domain`](https://nodejs.org/api/domain.html) 🟡 Missing `Domain` `active` @@ -379,6 +377,7 @@ The table below lists all globals implemented by Node.js and Bun's current compa ### [`require()`](https://nodejs.org/api/globals.html#require) 🟢 Fully implemented, including [`require.main`](https://nodejs.org/api/modules.html#requiremain), [`require.cache`](https://nodejs.org/api/modules.html#requirecache), [`require.resolve`](https://nodejs.org/api/modules.html#requireresolverequest-options). + ### [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) 🟢 Fully implemented. diff --git a/src/bun.js/bindings/AsymmetricKeyValue.cpp b/src/bun.js/bindings/AsymmetricKeyValue.cpp new file mode 100644 index 0000000000..bd04e24f2c --- /dev/null +++ b/src/bun.js/bindings/AsymmetricKeyValue.cpp @@ -0,0 +1,138 @@ +// Attribution: Some parts of of this module are derived from code originating from the Node.js +// crypto module which is licensed under an MIT license: +// +// Copyright Node.js contributors. All rights reserved. +// +// 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. + +#include "root.h" +#include "ErrorCode.h" +#include "BunCommonStrings.h" +#include "JavaScriptCore/JSArrayBufferView.h" +#include "JavaScriptCore/JSCJSValue.h" +#include "JavaScriptCore/JSCast.h" +#include "ZigGlobalObject.h" +#include "webcrypto/JSCryptoKey.h" +#include "webcrypto/JSSubtleCrypto.h" +#include "webcrypto/CryptoKeyOKP.h" +#include "webcrypto/CryptoKeyEC.h" +#include "webcrypto/CryptoKeyRSA.h" +#include "webcrypto/CryptoKeyAES.h" +#include "webcrypto/CryptoKeyHMAC.h" +#include "webcrypto/CryptoKeyRaw.h" +#include "webcrypto/CryptoKeyUsage.h" +#include "webcrypto/JsonWebKey.h" +#include "webcrypto/JSJsonWebKey.h" +#include "JavaScriptCore/JSObject.h" +#include "JavaScriptCore/ObjectConstructor.h" +#include "headers-handwritten.h" +#include +#include +#include +#include +#include +#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" +#include "ncrypto.h" +#include "AsymmetricKeyValue.h" +using namespace JSC; +using namespace Bun; +using JSGlobalObject = JSC::JSGlobalObject; +using Exception = JSC::Exception; +using JSValue = JSC::JSValue; +using JSString = JSC::JSString; +using JSModuleLoader = JSC::JSModuleLoader; +using JSModuleRecord = JSC::JSModuleRecord; +using Identifier = JSC::Identifier; +using SourceOrigin = JSC::SourceOrigin; +using JSObject = JSC::JSObject; +using JSNonFinalObject = JSC::JSNonFinalObject; + +namespace WebCore { + +AsymmetricKeyValue::~AsymmetricKeyValue() +{ + if (key && owned) { + EVP_PKEY_free(key); + } +} + +AsymmetricKeyValue::AsymmetricKeyValue(WebCore::CryptoKey& cryptoKey) +{ + auto id = cryptoKey.algorithmIdentifier(); + owned = false; + key = nullptr; + + switch (id) { + case CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5: + case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: + case CryptoAlgorithmIdentifier::RSA_OAEP: + case CryptoAlgorithmIdentifier::RSA_PSS: + key = downcast(cryptoKey).platformKey(); + break; + case CryptoAlgorithmIdentifier::ECDSA: + case CryptoAlgorithmIdentifier::ECDH: + key = downcast(cryptoKey).platformKey(); + break; + case CryptoAlgorithmIdentifier::X25519: + case CryptoAlgorithmIdentifier::Ed25519: { + const auto& okpKey = downcast(cryptoKey); + auto keyData = okpKey.exportKey(); + if (okpKey.type() == CryptoKeyType::Private) { + key = EVP_PKEY_new_raw_private_key(okpKey.namedCurve() == CryptoKeyOKP::NamedCurve::X25519 ? EVP_PKEY_X25519 : EVP_PKEY_ED25519, nullptr, keyData.data(), keyData.size()); + owned = true; + break; + } else { + auto* evp_key = EVP_PKEY_new_raw_public_key(okpKey.namedCurve() == CryptoKeyOKP::NamedCurve::X25519 ? EVP_PKEY_X25519 : EVP_PKEY_ED25519, nullptr, keyData.data(), keyData.size()); + key = evp_key; + owned = true; + break; + } + } + case CryptoAlgorithmIdentifier::AES_CTR: + case CryptoAlgorithmIdentifier::AES_CBC: + case CryptoAlgorithmIdentifier::AES_GCM: + case CryptoAlgorithmIdentifier::AES_CFB: + case CryptoAlgorithmIdentifier::AES_KW: + case CryptoAlgorithmIdentifier::HMAC: + case CryptoAlgorithmIdentifier::SHA_1: + case CryptoAlgorithmIdentifier::SHA_224: + case CryptoAlgorithmIdentifier::SHA_256: + case CryptoAlgorithmIdentifier::SHA_384: + case CryptoAlgorithmIdentifier::SHA_512: + case CryptoAlgorithmIdentifier::HKDF: + case CryptoAlgorithmIdentifier::PBKDF2: + case CryptoAlgorithmIdentifier::None: + key = nullptr; + break; + } +} + +} // namespace WebCore diff --git a/src/bun.js/bindings/BunCommonStrings.h b/src/bun.js/bindings/BunCommonStrings.h index c2afbd122a..e5e0f3117b 100644 --- a/src/bun.js/bindings/BunCommonStrings.h +++ b/src/bun.js/bindings/BunCommonStrings.h @@ -68,6 +68,25 @@ macro(rsaPss, "rsa-pss") \ macro(s3Error, "S3Error") \ macro(strict, "strict") \ + macro(jwkCrv, "crv") \ + macro(jwkD, "d") \ + macro(jwkDp, "dp") \ + macro(jwkDq, "dq") \ + macro(jwkDsa, "DSA") \ + macro(jwkE, "e") \ + macro(jwkEc, "EC") \ + macro(jwkG, "g") \ + macro(jwkK, "k") \ + macro(jwkP, "p") \ + macro(jwkQ, "q") \ + macro(jwkQi, "qi") \ + macro(jwkKty, "kty") \ + macro(jwkN, "n") \ + macro(jwkOct, "oct") \ + macro(jwkOkp, "OKP") \ + macro(jwkRsa, "RSA") \ + macro(jwkX, "x") \ + macro(jwkY, "y") \ macro(systemError, "SystemError") \ macro(ucs2, "ucs2") \ macro(utf16le, "utf16le") \ diff --git a/src/bun.js/bindings/ErrorCode.cpp b/src/bun.js/bindings/ErrorCode.cpp index 15777ecbac..1b40ed8972 100644 --- a/src/bun.js/bindings/ErrorCode.cpp +++ b/src/bun.js/bindings/ErrorCode.cpp @@ -28,6 +28,7 @@ #include #include "ErrorCode.h" #include "ErrorStackTrace.h" +#include "KeyObject.h" namespace WTF { template<> class StringTypeAdapter, void> { @@ -627,6 +628,12 @@ WTF::String ERR_OUT_OF_RANGE(JSC::ThrowScope& scope, JSC::JSGlobalObject* global namespace ERR { +EncodedJSValue INVALID_ARG_TYPE(ThrowScope& scope, JSGlobalObject* globalObject, ASCIILiteral message) +{ + scope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, message)); + return {}; +} + JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value) { auto message = Message::ERR_INVALID_ARG_TYPE(throwScope, globalObject, arg_name, expected_type, val_actual_value); @@ -665,6 +672,22 @@ JSC::EncodedJSValue INVALID_ARG_TYPE_INSTANCE(JSC::ThrowScope& throwScope, JSC:: return {}; } +JSC::EncodedJSValue INVALID_ARG_TYPE_INSTANCE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral arg_name, WTF::ASCIILiteral expected_instance_types, JSC::JSValue val_actual_value) +{ + JSC::VM& vm = globalObject->vm(); + WTF::StringBuilder builder; + builder.append("The \""_s); + builder.append(arg_name); + builder.append("\" argument must be an instance of "_s); + builder.append(expected_instance_types); + builder.append(". Received "_s); + determineSpecificType(vm, globalObject, builder, val_actual_value); + RETURN_IF_EXCEPTION(throwScope, {}); + + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, builder.toString())); + return {}; +} + // When you want INVALID_ARG_TYPE to say "The argument must be an instance of X. Received Y." instead of "The argument must be of type X. Received Y." JSC::EncodedJSValue INVALID_ARG_INSTANCE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value) { @@ -780,6 +803,12 @@ JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObjec return {}; } +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, ASCIILiteral message) +{ + scope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_OUT_OF_RANGE, message)); + return {}; +} + JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, JSC::JSValue value, const WTF::String& reason) { ASCIILiteral type = String(name).contains('.') ? "property"_s : "argument"_s; @@ -874,7 +903,7 @@ JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobal return {}; } -JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, WTF::ASCIILiteral reason, JSC::JSValue value, const WTF::Vector& oneOf) +JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, WTF::ASCIILiteral reason, JSC::JSValue value, const std::span oneOf) { WTF::StringBuilder builder; builder.append("The "_s); @@ -902,6 +931,32 @@ JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobal return {}; } +JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, WTF::ASCIILiteral reason, JSC::JSValue value, const std::span oneOf) +{ + WTF::StringBuilder builder; + builder.append("The "_s); + if (WTF::StringView(name).contains('.')) { + builder.append("property '"_s); + } else { + builder.append("argument '"_s); + } + builder.append(name); + builder.append("' "_s); + builder.append(reason); + + bool first = true; + for (int32_t oneOfStr : oneOf) { + if (!first) { + builder.append(", "_s); + } + first = false; + builder.append(oneOfStr); + } + + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_VALUE, builder.toString())); + return {}; +} + JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& name, JSC::JSValue value, const WTF::String& reason) { WTF::StringBuilder builder; @@ -917,7 +972,7 @@ JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobal builder.append(reason); builder.append(". Received "_s); - JSValueToStringSafe(globalObject, builder, value); + JSValueToStringSafe(globalObject, builder, value, true); RETURN_IF_EXCEPTION(throwScope, {}); throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_VALUE, builder.toString())); @@ -1091,6 +1146,12 @@ JSC::EncodedJSValue CRYPTO_UNSUPPORTED_OPERATION(JSC::ThrowScope& throwScope, JS return {}; } +JSC::EncodedJSValue CRYPTO_UNSUPPORTED_OPERATION(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) +{ + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_UNSUPPORTED_OPERATION, "Unsupported crypto operation"_s)); + return {}; +} + JSC::EncodedJSValue CRYPTO_INVALID_KEYLEN(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) { throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_INVALID_KEYLEN, "Invalid key length"_s)); @@ -1154,12 +1215,36 @@ JSC::EncodedJSValue CRYPTO_JWK_UNSUPPORTED_CURVE(JSC::ThrowScope& throwScope, JS return {}; } +JSC::EncodedJSValue CRYPTO_JWK_UNSUPPORTED_CURVE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral message, const char* curveName) +{ + WTF::StringBuilder builder; + builder.append(message); + if (curveName) { + builder.append(std::span { curveName, strlen(curveName) }); + } + builder.append('.'); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_JWK_UNSUPPORTED_CURVE, builder.toString())); + return {}; +} + +JSC::EncodedJSValue CRYPTO_JWK_UNSUPPORTED_KEY_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) +{ + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE, "Unsupported JWK Key Type."_s)); + return {}; +} + JSC::EncodedJSValue CRYPTO_INVALID_JWK(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) { throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_INVALID_JWK, "Invalid JWK data"_s)); return {}; } +JSC::EncodedJSValue CRYPTO_INVALID_JWK(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral message) +{ + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_INVALID_JWK, message)); + return {}; +} + JSC::EncodedJSValue CRYPTO_SIGN_KEY_REQUIRED(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) { auto message = "No key provided to sign"_s; @@ -1180,12 +1265,36 @@ JSC::EncodedJSValue CRYPTO_INVALID_KEY_OBJECT_TYPE(JSC::ThrowScope& throwScope, return {}; } -JSC::EncodedJSValue CRYPTO_INCOMPATIBLE_KEY_OPTIONS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& receivedKeyEncoding, const WTF::String& expectedOperation) +JSC::EncodedJSValue CRYPTO_INVALID_KEY_OBJECT_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, CryptoKeyType receivedType, ASCIILiteral expected) +{ + WTF::StringBuilder builder; + builder.append("Invalid key object type "_s); + switch (receivedType) { + case CryptoKeyType::Private: + builder.append("private"_s); + break; + case CryptoKeyType::Public: + builder.append("public"_s); + break; + case CryptoKeyType::Secret: + builder.append("secret"_s); + break; + } + builder.append(", expected "_s); + builder.append(expected); + builder.append('.'); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE, builder.toString())); + return {}; +} + +JSC::EncodedJSValue CRYPTO_INCOMPATIBLE_KEY_OPTIONS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::StringView& receivedKeyEncoding, const WTF::String& expectedOperation) { WTF::StringBuilder builder; builder.append("The selected key encoding "_s); builder.append(receivedKeyEncoding); - builder.append(" does not support "_s); + builder.append(' '); + builder.append(expectedOperation); + builder.append('.'); throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS, builder.toString())); return {}; } @@ -1199,6 +1308,15 @@ JSC::EncodedJSValue CRYPTO_INVALID_DIGEST(JSC::ThrowScope& throwScope, JSC::JSGl return {}; } +JSC::EncodedJSValue CRYPTO_INVALID_DIGEST(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral message, const WTF::StringView& digest) +{ + WTF::StringBuilder builder; + builder.append(message); + builder.append(digest); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_INVALID_DIGEST, builder.toString())); + return {}; +} + JSC::EncodedJSValue CRYPTO_HASH_FINALIZED(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) { throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CRYPTO_HASH_FINALIZED, "Digest already called"_s)); @@ -1225,12 +1343,53 @@ JSC::EncodedJSValue CRYPTO_UNKNOWN_DH_GROUP(JSC::ThrowScope& scope, JSGlobalObje return {}; } +JSC::EncodedJSValue OSSL_EVP_INVALID_DIGEST(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject) +{ + scope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_OSSL_EVP_INVALID_DIGEST, "Invalid digest used"_s)); + return {}; +} + JSC::EncodedJSValue MISSING_PASSPHRASE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral message) { throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_MISSING_PASSPHRASE, message)); return {}; } +JSC::EncodedJSValue KEY_GENERATION_JOB_FAILED(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) +{ + auto message = "Key generation job failed"_s; + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_KEY_GENERATION_JOB_FAILED, message)); + return {}; +} + +JSC::EncodedJSValue INCOMPATIBLE_OPTION_PAIR(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral opt1, ASCIILiteral opt2) +{ + WTF::StringBuilder builder; + builder.append("Option \""_s); + builder.append(opt1); + builder.append("\" cannot be used in combination with option \""_s); + builder.append(opt2); + builder.append("\""_s); + + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INCOMPATIBLE_OPTION_PAIR, builder.toString())); + return {}; +} + +JSC::EncodedJSValue MISSING_OPTION(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, ASCIILiteral message) +{ + WTF::StringBuilder builder; + builder.append(message); + builder.append(" is required"_s); + scope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_MISSING_OPTION, builder.toString())); + return {}; +} + +EncodedJSValue CLOSED_MESSAGE_PORT(ThrowScope& scope, JSGlobalObject* globalObject) +{ + scope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_CLOSED_MESSAGE_PORT, "Cannot send data on closed MessagePort"_s)); + return {}; +} + } // namespace ERR static JSC::JSValue ERR_INVALID_ARG_TYPE(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue arg0, JSValue arg1, JSValue arg2) diff --git a/src/bun.js/bindings/ErrorCode.h b/src/bun.js/bindings/ErrorCode.h index 1e61d02b76..59a2cc39bb 100644 --- a/src/bun.js/bindings/ErrorCode.h +++ b/src/bun.js/bindings/ErrorCode.h @@ -7,6 +7,7 @@ #include #include "BunClientData.h" #include "ErrorCode+List.h" +#include "CryptoKeyType.h" namespace Bun { @@ -65,18 +66,22 @@ enum Bound { namespace ERR { +JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral message); JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value); JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value); JSC::EncodedJSValue INVALID_ARG_INSTANCE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value); JSC::EncodedJSValue INVALID_ARG_TYPE_INSTANCE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral arg_name, WTF::ASCIILiteral expected_type, WTF::ASCIILiteral expected_instance_types, JSC::JSValue val_actual_value); +JSC::EncodedJSValue INVALID_ARG_TYPE_INSTANCE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral arg_name, WTF::ASCIILiteral expected_instance_types, JSC::JSValue val_actual_value); JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, double lower, double upper, JSC::JSValue actual); JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name, double lower, double upper, JSC::JSValue actual); JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, double bound_num, Bound bound, JSC::JSValue actual); JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, const WTF::String& msg, JSC::JSValue actual); JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name_val, const WTF::String& msg, JSC::JSValue actual); +JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral message); JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, JSC::JSValue value, const WTF::String& reason = "is invalid"_s); JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue name, JSC::JSValue value, WTF::ASCIILiteral reason, JSC::JSArray* oneOf); -JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, WTF::ASCIILiteral reason, JSC::JSValue value, const WTF::Vector& oneOf); +JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, WTF::ASCIILiteral reason, JSC::JSValue value, std::span oneOf); +JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, WTF::ASCIILiteral reason, JSC::JSValue value, std::span oneOf); JSC::EncodedJSValue INVALID_ARG_VALUE_RangeError(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral name, JSC::JSValue value, const WTF::String& reason = "is invalid"_s); JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue name, JSC::JSValue value, const WTF::String& reason = "is invalid"_s); JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& name, JSC::JSValue value, const WTF::String& reason = "is invalid"_s); @@ -97,11 +102,16 @@ JSC::EncodedJSValue CRYPTO_INVALID_KEYPAIR(JSC::ThrowScope& throwScope, JSC::JSG JSC::EncodedJSValue CRYPTO_ECDH_INVALID_PUBLIC_KEY(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); JSC::EncodedJSValue CRYPTO_ECDH_INVALID_FORMAT(JSC::ThrowScope&, JSC::JSGlobalObject*, const WTF::String& formatString); JSC::EncodedJSValue CRYPTO_JWK_UNSUPPORTED_CURVE(JSC::ThrowScope&, JSC::JSGlobalObject*, const WTF::String&); +JSC::EncodedJSValue CRYPTO_JWK_UNSUPPORTED_CURVE(JSC::ThrowScope&, JSC::JSGlobalObject*, ASCIILiteral message, const char* curveName); +JSC::EncodedJSValue CRYPTO_JWK_UNSUPPORTED_KEY_TYPE(JSC::ThrowScope&, JSC::JSGlobalObject*); JSC::EncodedJSValue CRYPTO_INVALID_JWK(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); +JSC::EncodedJSValue CRYPTO_INVALID_JWK(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral message); JSC::EncodedJSValue CRYPTO_SIGN_KEY_REQUIRED(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); JSC::EncodedJSValue CRYPTO_INVALID_KEY_OBJECT_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSValue received, WTF::ASCIILiteral expected); -JSC::EncodedJSValue CRYPTO_INCOMPATIBLE_KEY_OPTIONS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& received, const WTF::String& expected); +JSC::EncodedJSValue CRYPTO_INVALID_KEY_OBJECT_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, CryptoKeyType receivedType, WTF::ASCIILiteral expected); +JSC::EncodedJSValue CRYPTO_INCOMPATIBLE_KEY_OPTIONS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::StringView& receivedKeyEncoding, const WTF::String& expectedOperation); JSC::EncodedJSValue CRYPTO_INVALID_DIGEST(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::StringView& digest); +JSC::EncodedJSValue CRYPTO_INVALID_DIGEST(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral message, const WTF::StringView& digest); JSC::EncodedJSValue CRYPTO_HASH_FINALIZED(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); JSC::EncodedJSValue CRYPTO_HASH_UPDATE_FAILED(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); JSC::EncodedJSValue MISSING_PASSPHRASE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral message); @@ -113,9 +123,15 @@ JSC::EncodedJSValue CRYPTO_UNKNOWN_CIPHER(JSC::ThrowScope&, JSC::JSGlobalObject* JSC::EncodedJSValue CRYPTO_INVALID_AUTH_TAG(JSC::ThrowScope&, JSC::JSGlobalObject*, const WTF::String& message); JSC::EncodedJSValue CRYPTO_INVALID_IV(JSC::ThrowScope&, JSC::JSGlobalObject*); JSC::EncodedJSValue CRYPTO_UNSUPPORTED_OPERATION(JSC::ThrowScope&, JSC::JSGlobalObject*, WTF::ASCIILiteral message); +JSC::EncodedJSValue CRYPTO_UNSUPPORTED_OPERATION(JSC::ThrowScope&, JSC::JSGlobalObject*); JSC::EncodedJSValue CRYPTO_INVALID_KEYLEN(JSC::ThrowScope&, JSC::JSGlobalObject*); JSC::EncodedJSValue CRYPTO_INVALID_STATE(JSC::ThrowScope&, JSC::JSGlobalObject*, WTF::ASCIILiteral message); JSC::EncodedJSValue CRYPTO_INVALID_MESSAGELEN(JSC::ThrowScope&, JSC::JSGlobalObject*); +JSC::EncodedJSValue OSSL_EVP_INVALID_DIGEST(JSC::ThrowScope&, JSC::JSGlobalObject*); +JSC::EncodedJSValue KEY_GENERATION_JOB_FAILED(JSC::ThrowScope&, JSC::JSGlobalObject*); +JSC::EncodedJSValue INCOMPATIBLE_OPTION_PAIR(JSC::ThrowScope&, JSC::JSGlobalObject*, ASCIILiteral opt1, ASCIILiteral opt2); +JSC::EncodedJSValue MISSING_OPTION(JSC::ThrowScope&, JSC::JSGlobalObject*, ASCIILiteral message); +JSC::EncodedJSValue CLOSED_MESSAGE_PORT(JSC::ThrowScope&, JSC::JSGlobalObject*); // URL diff --git a/src/bun.js/bindings/ErrorCode.ts b/src/bun.js/bindings/ErrorCode.ts index cae2652bf1..c62733066c 100644 --- a/src/bun.js/bindings/ErrorCode.ts +++ b/src/bun.js/bindings/ErrorCode.ts @@ -246,7 +246,9 @@ const errors: ErrorCodeMapping = [ ["ERR_ZLIB_INITIALIZATION_FAILED", Error], ["MODULE_NOT_FOUND", Error], ["ERR_INTERNAL_ASSERTION", Error], - + ["ERR_OSSL_EVP_INVALID_DIGEST", Error], + ["ERR_KEY_GENERATION_JOB_FAILED", Error], + ["ERR_MISSING_OPTION", TypeError], ["ERR_REDIS_CONNECTION_CLOSED", Error, "RedisError"], ["ERR_REDIS_INVALID_RESPONSE", Error, "RedisError"], ["ERR_REDIS_INVALID_BULK_STRING", Error, "RedisError"], diff --git a/src/bun.js/bindings/ExceptionCode.h b/src/bun.js/bindings/ExceptionCode.h index ebbb9501bc..d0e7f10a07 100644 --- a/src/bun.js/bindings/ExceptionCode.h +++ b/src/bun.js/bindings/ExceptionCode.h @@ -74,6 +74,7 @@ enum ExceptionCode { InvalidThisError, InvalidURLError, + CryptoOperationFailedError, }; } // namespace WebCore @@ -121,7 +122,8 @@ template<> struct EnumTraits { WebCore::ExceptionCode::StackOverflowError, WebCore::ExceptionCode::ExistingExceptionError, WebCore::ExceptionCode::InvalidThisError, - WebCore::ExceptionCode::InvalidURLError>; + WebCore::ExceptionCode::InvalidURLError, + WebCore::ExceptionCode::CryptoOperationFailedError>; }; } // namespace WTF diff --git a/src/bun.js/bindings/JSDOMExceptionHandling.cpp b/src/bun.js/bindings/JSDOMExceptionHandling.cpp index a5c09653cf..cfa87ce0a4 100644 --- a/src/bun.js/bindings/JSDOMExceptionHandling.cpp +++ b/src/bun.js/bindings/JSDOMExceptionHandling.cpp @@ -182,6 +182,9 @@ JSValue createDOMException(JSGlobalObject* lexicalGlobalObject, ExceptionCode ec case ExceptionCode::InvalidURLError: return Bun::createError(lexicalGlobalObject, Bun::ErrorCode::ERR_INVALID_URL, message.isEmpty() ? "Invalid URL"_s : message); + case ExceptionCode::CryptoOperationFailedError: + return Bun::createError(lexicalGlobalObject, Bun::ErrorCode::ERR_CRYPTO_OPERATION_FAILED, message.isEmpty() ? "Crypto operation failed"_s : message); + default: { // FIXME: All callers to createDOMException need to pass in the correct global object. // For now, we're going to assume the lexicalGlobalObject. Which is wrong in cases like this: diff --git a/src/bun.js/bindings/JSX509Certificate.cpp b/src/bun.js/bindings/JSX509Certificate.cpp index 43aa3948c7..87fd4dbdeb 100644 --- a/src/bun.js/bindings/JSX509Certificate.cpp +++ b/src/bun.js/bindings/JSX509Certificate.cpp @@ -17,11 +17,8 @@ #include #include #include -#include "JSCryptoKey.h" #include -#include "CryptoKeyEC.h" -#include "CryptoKeyRSA.h" #include "openssl/evp.h" #include "JavaScriptCore/ObjectPrototype.h" #include "BunString.h" @@ -30,31 +27,8 @@ #include "wtf/text/ExternalStringImpl.h" #include #include - -RefPtr toCryptoKey(EVP_PKEY* pkey) -{ - auto type = EVP_PKEY_base_id(pkey); - unsigned usages = 0; - usages |= WebCore::CryptoKeyUsageSign; - usages |= WebCore::CryptoKeyUsageVerify; - usages |= WebCore::CryptoKeyUsageDeriveKey; - usages |= WebCore::CryptoKeyUsageDeriveBits; - usages |= WebCore::CryptoKeyUsageWrapKey; - usages |= WebCore::CryptoKeyUsageUnwrapKey; - usages |= WebCore::CryptoKeyUsageEncrypt; - usages |= WebCore::CryptoKeyUsageDecrypt; - - if (type == EVP_PKEY_EC) { - return WebCore::CryptoKeyEC::create(WebCore::CryptoAlgorithmIdentifier::ECDH, WebCore::CryptoKeyEC::NamedCurve::P256, WebCore::CryptoKeyType::Public, WebCore::EvpPKeyPtr(pkey), true, usages); - } else if (type == EVP_PKEY_RSA) { - return WebCore::CryptoKeyRSA::create(WebCore::CryptoAlgorithmIdentifier::RSA_OAEP, WebCore::CryptoAlgorithmIdentifier::SHA_1, true, WebCore::CryptoKeyType::Public, WebCore::EvpPKeyPtr(pkey), true, usages); - } else if (type == EVP_PKEY_ED25519) { - return WebCore::CryptoKeyEC::create(WebCore::CryptoAlgorithmIdentifier::ECDH, WebCore::CryptoKeyEC::NamedCurve::P256, WebCore::CryptoKeyType::Public, WebCore::EvpPKeyPtr(pkey), true, usages); - } - - EVP_PKEY_free(pkey); - return nullptr; -} +#include "CryptoUtil.h" +#include "JSPublicKeyObject.h" namespace Bun { @@ -282,13 +256,13 @@ JSX509Certificate* JSX509Certificate::create(JSC::VM& vm, JSC::Structure* struct return nullptr; } - return create(vm, structure, globalObject, std::move(result.value)); + return create(vm, structure, globalObject, WTFMove(result.value)); } JSX509Certificate* JSX509Certificate::create(JSC::VM& vm, JSC::Structure* structure, JSC::JSGlobalObject* globalObject, ncrypto::X509Pointer&& cert) { auto* certificate = create(vm, structure); - certificate->m_x509 = std::move(cert); + certificate->m_x509 = WTFMove(cert); size_t size = i2d_X509(certificate->m_x509.get(), nullptr); certificate->m_extraMemorySizeForGC = size; vm.heap.reportExtraMemoryAllocated(certificate, size); @@ -705,26 +679,16 @@ JSValue JSX509Certificate::publicKey() return m_publicKey.get(this); } -bool JSX509Certificate::checkPrivateKey(JSGlobalObject* globalObject, EVP_PKEY* pkey) +bool JSX509Certificate::checkPrivateKey(const KeyObject& keyObject) { - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - if (!pkey) - return false; - - return view().checkPrivateKey(ncrypto::EVPKeyPointer(pkey)); + const auto& key = keyObject.asymmetricKey(); + return view().checkPrivateKey(key); } -bool JSX509Certificate::verify(JSGlobalObject* globalObject, EVP_PKEY* pkey) +bool JSX509Certificate::verify(const KeyObject& keyObject) { - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - if (!pkey) - return false; - - return view().checkPublicKey(ncrypto::EVPKeyPointer(pkey)); + const auto& key = keyObject.asymmetricKey(); + return view().checkPublicKey(key); } // This one doesn't depend on a JSX509Certificate object @@ -1063,24 +1027,20 @@ JSC::JSObject* JSX509Certificate::toLegacyObject(JSGlobalObject* globalObject) return object; } -JSValue JSX509Certificate::computePublicKey(ncrypto::X509View view, JSGlobalObject* globalObject) +JSValue JSX509Certificate::computePublicKey(ncrypto::X509View view, JSGlobalObject* lexicalGlobalObject) { - VM& vm = globalObject->vm(); + VM& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); auto result = view.getPublicKey(); if (!result) { - throwBoringSSLError(vm, scope, globalObject, result.error.value_or(0)); + throwCryptoError(lexicalGlobalObject, scope, result.error.value_or(0)); return {}; } - RefPtr key = toCryptoKey(result.value.release()); - if (!key) { - throwError(globalObject, scope, ErrorCode::ERR_CRYPTO_INVALID_STATE, "Failed to convert public key to CryptoKey"_s); - return {}; - } - - return toJSNewlyCreated(globalObject, defaultGlobalObject(globalObject), key.releaseNonNull()); + auto handle = KeyObject::create(CryptoKeyType::Public, WTFMove(result.value)); + return JSPublicKeyObject::create(vm, globalObject->m_JSPublicKeyObjectClassStructure.get(lexicalGlobalObject), lexicalGlobalObject, WTFMove(handle)); } JSValue JSX509Certificate::computeInfoAccess(ncrypto::X509View view, JSGlobalObject* globalObject, bool legacy) diff --git a/src/bun.js/bindings/JSX509Certificate.h b/src/bun.js/bindings/JSX509Certificate.h index 9fd998cd8f..0984374087 100644 --- a/src/bun.js/bindings/JSX509Certificate.h +++ b/src/bun.js/bindings/JSX509Certificate.h @@ -11,6 +11,7 @@ #include #include #include +#include "KeyObject.h" namespace Zig { class GlobalObject; @@ -68,8 +69,8 @@ public: bool checkEmail(JSGlobalObject*, std::span, uint32_t flags); bool checkIP(JSGlobalObject*, const char*); bool checkIssued(JSGlobalObject*, JSX509Certificate* issuer); - bool checkPrivateKey(JSGlobalObject*, EVP_PKEY* pkey); - bool verify(JSGlobalObject*, EVP_PKEY* pkey); + bool checkPrivateKey(const KeyObject&); + bool verify(const KeyObject&); JSC::JSObject* toLegacyObject(JSGlobalObject*); static JSObject* toLegacyObject(ncrypto::X509View view, JSGlobalObject*); diff --git a/src/bun.js/bindings/JSX509CertificatePrototype.cpp b/src/bun.js/bindings/JSX509CertificatePrototype.cpp index 9e6b4a1fab..10e60553fe 100644 --- a/src/bun.js/bindings/JSX509CertificatePrototype.cpp +++ b/src/bun.js/bindings/JSX509CertificatePrototype.cpp @@ -21,6 +21,7 @@ #include "wtf/DateMath.h" #include "AsymmetricKeyValue.h" #include +#include "JSKeyObject.h" namespace Bun { @@ -99,7 +100,7 @@ JSC_DEFINE_HOST_FUNCTION(jsX509CertificateProtoFuncToString, (JSGlobalObject * g JSX509Certificate* thisObject = jsDynamicCast(callFrame->thisValue()); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "toString"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "toString"_s); return {}; } @@ -248,7 +249,7 @@ JSC_DEFINE_HOST_FUNCTION(jsX509CertificateProtoFuncCheckEmail, (JSGlobalObject * JSX509Certificate* thisObject = jsDynamicCast(callFrame->thisValue()); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "checkEmail"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "checkEmail"_s); return {}; } @@ -281,7 +282,7 @@ JSC_DEFINE_HOST_FUNCTION(jsX509CertificateProtoFuncCheckHost, (JSGlobalObject * JSX509Certificate* thisObject = jsDynamicCast(callFrame->thisValue()); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "checkHost"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "checkHost"_s); return {}; } @@ -314,7 +315,7 @@ JSC_DEFINE_HOST_FUNCTION(jsX509CertificateProtoFuncCheckIP, (JSGlobalObject * gl JSX509Certificate* thisObject = jsDynamicCast(callFrame->thisValue()); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "checkIP"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "checkIP"_s); return {}; } @@ -369,34 +370,19 @@ JSC_DEFINE_HOST_FUNCTION(jsX509CertificateProtoFuncCheckPrivateKey, (JSGlobalObj if (UNLIKELY(!thisObject)) return throwVMError(globalObject, scope, createError(globalObject, ErrorCode::ERR_INVALID_THIS, "checkPrivateKey called on incompatible receiver"_s)); - JSValue privateKey = callFrame->argument(0); - RETURN_IF_EXCEPTION(scope, {}); + JSValue pkeyValue = callFrame->argument(0); - WebCore::JSCryptoKey* key = WebCore::JSCryptoKey::fromJS(globalObject, privateKey); - if (!key) - return throwVMError(globalObject, scope, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, "Private key must be a valid CryptoKey"_s)); - - auto& wrapped = key->wrapped(); - - if (wrapped.type() != WebCore::CryptoKeyType::Private) - return throwVMError(globalObject, scope, createError(globalObject, ErrorCode::ERR_INVALID_ARG_VALUE, "CryptoKey must be a private key"_s)); - - bool isValid = false; - switch (wrapped.keyClass()) { - case WebCore::CryptoKeyClass::RSA: - isValid = thisObject->checkPrivateKey(globalObject, downcast(wrapped).platformKey()); - break; - case WebCore::CryptoKeyClass::EC: - isValid = thisObject->checkPrivateKey(globalObject, downcast(wrapped).platformKey()); - break; - default: - break; + JSKeyObject* keyObject = jsDynamicCast(pkeyValue); + if (!keyObject) { + return ERR::INVALID_ARG_TYPE(scope, globalObject, "pkey"_s, "KeyObject"_s, pkeyValue); } - if (!isValid) - return JSValue::encode(jsUndefined()); + auto& handle = keyObject->handle(); + if (handle.type() != CryptoKeyType::Private) { + return ERR::INVALID_ARG_VALUE(scope, globalObject, "pkey"_s, pkeyValue); + } - return JSValue::encode(privateKey); + return JSValue::encode(jsBoolean(thisObject->checkPrivateKey(handle))); } JSC_DEFINE_HOST_FUNCTION(jsX509CertificateProtoFuncToJSON, (JSGlobalObject * globalObject, CallFrame* callFrame)) @@ -420,7 +406,7 @@ JSC_DEFINE_HOST_FUNCTION(jsX509CertificateProtoFuncToLegacyObject, (JSGlobalObje JSX509Certificate* thisObject = jsDynamicCast(callFrame->thisValue()); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "toLegacyObject"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "toLegacyObject"_s); return {}; } @@ -448,32 +434,23 @@ JSC_DEFINE_HOST_FUNCTION(jsX509CertificateProtoFuncVerify, (JSGlobalObject * glo JSX509Certificate* thisObject = jsDynamicCast(callFrame->thisValue()); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "verify"_s); + throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "verify"_s); return {}; } - JSObject* arg0 = callFrame->argument(0).getObject(); - if (!arg0) - return throwVMError(globalObject, scope, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, "argument 0 must be an object"_s)); + JSValue pkeyValue = callFrame->argument(0); - JSCryptoKey* key = JSCryptoKey::fromJS(globalObject, arg0); - if (!key) - return throwVMError(globalObject, scope, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, "argument 0 must be a valid KeyObject"_s)); - - auto& wrapped = key->wrapped(); - // A Public Key can be derived from a private key, so we allow both. - // Node has ^ comment, but the test suite passes a private key. - if (wrapped.type() != CryptoKeyType::Public) { - return throwVMError(globalObject, scope, createError(globalObject, ErrorCode::ERR_INVALID_ARG_VALUE, "argument 0 must be a public key"_s)); + JSKeyObject* keyObject = jsDynamicCast(pkeyValue); + if (!keyObject) { + return ERR::INVALID_ARG_TYPE(scope, globalObject, "pkey"_s, "KeyObject"_s, pkeyValue); } - AsymmetricKeyValue asymmetricKeyValue(wrapped); - if (!asymmetricKeyValue) - return throwVMError(globalObject, scope, createError(globalObject, ErrorCode::ERR_INVALID_ARG_VALUE, "argument 0 must be a valid public key"_s)); + const auto& handle = keyObject->handle(); + if (handle.type() != CryptoKeyType::Public) { + return ERR::INVALID_ARG_VALUE(scope, globalObject, "pkey"_s, pkeyValue); + } - ncrypto::ClearErrorOnReturn clearErrorOnReturn; - int result = X509_verify(thisObject->view().get(), asymmetricKeyValue.key); - return JSValue::encode(jsBoolean(result == 1)); + return JSValue::encode(jsBoolean(thisObject->verify(handle))); } JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_ca, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) @@ -483,7 +460,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_ca, (JSGlobalObject * globalObj JSX509Certificate* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "ca"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "ca"_s); return {}; } @@ -497,7 +474,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_fingerprint, (JSGlobalObject * JSX509Certificate* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "fingerprint"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "fingerprint"_s); return {}; } @@ -511,7 +488,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_fingerprint256, (JSGlobalObject JSX509Certificate* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "fingerprint256"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "fingerprint256"_s); return {}; } @@ -525,7 +502,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_fingerprint512, (JSGlobalObject JSX509Certificate* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "fingerprint512"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "fingerprint512"_s); return {}; } @@ -539,7 +516,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_subject, (JSGlobalObject * glob JSX509Certificate* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "subject"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "subject"_s); return {}; } @@ -553,7 +530,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_subjectAltName, (JSGlobalObject JSX509Certificate* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "subjectAltName"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "subjectAltName"_s); return {}; } @@ -567,7 +544,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_infoAccess, (JSGlobalObject * g JSX509Certificate* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "infoAccess"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "infoAccess"_s); return {}; } @@ -586,7 +563,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_keyUsage, (JSGlobalObject * glo JSX509Certificate* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "keyUsage"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "keyUsage"_s); return {}; } @@ -600,7 +577,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_issuer, (JSGlobalObject * globa JSX509Certificate* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "issuer"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "issuer"_s); return {}; } @@ -614,7 +591,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_issuerCertificate, (JSGlobalObj JSX509Certificate* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "issuerCertificate"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "issuerCertificate"_s); return {}; } @@ -641,7 +618,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_publicKey, (JSGlobalObject * gl JSX509Certificate* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "publicKey"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "publicKey"_s); return {}; } @@ -655,7 +632,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_raw, (JSGlobalObject * globalOb JSX509Certificate* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "raw"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "raw"_s); return {}; } @@ -669,7 +646,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_serialNumber, (JSGlobalObject * JSX509Certificate* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "serialNumber"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "serialNumber"_s); return {}; } @@ -683,7 +660,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_validFrom, (JSGlobalObject * gl JSX509Certificate* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "validFrom"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "validFrom"_s); return {}; } @@ -697,7 +674,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_validTo, (JSGlobalObject * glob JSX509Certificate* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "validTo"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "validTo"_s); return {}; } @@ -711,7 +688,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_validToDate, (JSGlobalObject * auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "validToDate"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "validToDate"_s); return {}; } @@ -734,7 +711,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_validFromDate, (JSGlobalObject JSX509Certificate* thisObject = jsDynamicCast(JSValue::decode(thisValue)); if (UNLIKELY(!thisObject)) { - Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "validFromDate"_s); + Bun::throwThisTypeError(*globalObject, scope, "X509Certificate"_s, "validFromDate"_s); return {}; } diff --git a/src/bun.js/bindings/KeyObject.cpp b/src/bun.js/bindings/KeyObject.cpp deleted file mode 100644 index 3c527a6c72..0000000000 --- a/src/bun.js/bindings/KeyObject.cpp +++ /dev/null @@ -1,3329 +0,0 @@ -// Attribution: Some parts of of this module are derived from code originating from the Node.js -// crypto module which is licensed under an MIT license: -// -// Copyright Node.js contributors. All rights reserved. -// -// 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. - -#include "root.h" -#include "ErrorCode.h" -#include "BunCommonStrings.h" -#include "KeyObject.h" -#include "JavaScriptCore/JSArrayBufferView.h" -#include "JavaScriptCore/JSCJSValue.h" -#include "JavaScriptCore/JSCast.h" -#include "ZigGlobalObject.h" -#include "webcrypto/JSCryptoKey.h" -#include "webcrypto/JSSubtleCrypto.h" -#include "webcrypto/CryptoKeyOKP.h" -#include "webcrypto/CryptoKeyEC.h" -#include "webcrypto/CryptoKeyRSA.h" -#include "webcrypto/CryptoKeyAES.h" -#include "webcrypto/CryptoKeyHMAC.h" -#include "webcrypto/CryptoKeyRaw.h" -#include "webcrypto/CryptoKeyUsage.h" -#include "webcrypto/JsonWebKey.h" -#include "webcrypto/JSJsonWebKey.h" -#include "JavaScriptCore/JSObject.h" -#include "JavaScriptCore/ObjectConstructor.h" -#include "headers-handwritten.h" -#include -#include -#include -#include -#include -#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" -#include "ncrypto.h" -#include "AsymmetricKeyValue.h" -using namespace JSC; -using namespace Bun; -using JSGlobalObject = JSC::JSGlobalObject; -using Exception = JSC::Exception; -using JSValue = JSC::JSValue; -using JSString = JSC::JSString; -using JSModuleLoader = JSC::JSModuleLoader; -using JSModuleRecord = JSC::JSModuleRecord; -using Identifier = JSC::Identifier; -using SourceOrigin = JSC::SourceOrigin; -using JSObject = JSC::JSObject; -using JSNonFinalObject = JSC::JSNonFinalObject; - -JSC_DECLARE_HOST_FUNCTION(KeyObject__AsymmetricKeyType); -JSC_DECLARE_HOST_FUNCTION(KeyObject_AsymmetricKeyDetails); -JSC_DECLARE_HOST_FUNCTION(KeyObject__SymmetricKeySize); -JSC_DECLARE_HOST_FUNCTION(KeyObject__Equals); -JSC_DECLARE_HOST_FUNCTION(KeyObject__Exports); -JSC_DECLARE_HOST_FUNCTION(KeyObject__createSecretKey); -JSC_DECLARE_HOST_FUNCTION(KeyObject__createPublicKey); -JSC_DECLARE_HOST_FUNCTION(KeyObject__createPrivateKey); -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 { - -static bool KeyObject__IsASN1Sequence(const unsigned char* data, size_t size, - size_t* data_offset, size_t* data_size) -{ - if (size < 2 || data[0] != 0x30) - return false; - - if (data[1] & 0x80) { - // Long form. - size_t n_bytes = data[1] & ~0x80; - if (n_bytes > size || n_bytes > sizeof(size_t)) - return false; - size_t length = 0; - for (size_t i = 0; i < n_bytes; i++) - length = (length << 8) | data[i + 2]; - *data_offset = 2 + n_bytes; - *data_size = std::min(size - 2 - n_bytes, length); - } else { - // Short form. - *data_offset = 2; - *data_size = std::min(size - 2, data[1]); - } - - return true; -} -// TODO: @cirospaciari - is this supposed to be unused? -// static bool KeyObject__IsRSAPrivateKey(const unsigned char* data, size_t size) -// { -// // Both RSAPrivateKey and RSAPublicKey structures start with a SEQUENCE. -// size_t offset, len; -// if (!KeyObject__IsASN1Sequence(data, size, &offset, &len)) -// return false; - -// // An RSAPrivateKey sequence always starts with a single-byte integer whose -// // value is either 0 or 1, whereas an RSAPublicKey starts with the modulus -// // (which is the product of two primes and therefore at least 4), so we can -// // decide the type of the structure based on the first three bytes of the -// // sequence. -// return len >= 3 && data[offset] == 2 && data[offset + 1] == 1 && !(data[offset + 2] & 0xfe); -// } - -static bool KeyObject__IsEncryptedPrivateKeyInfo(const unsigned char* data, size_t size) -{ - // Both PrivateKeyInfo and EncryptedPrivateKeyInfo start with a SEQUENCE. - size_t offset, len; - if (!KeyObject__IsASN1Sequence(data, size, &offset, &len)) - return false; - - // A PrivateKeyInfo sequence always starts with an integer whereas an - // EncryptedPrivateKeyInfo starts with an AlgorithmIdentifier. - return len >= 1 && data[offset] != 2; -} - -struct AsymmetricKeyValueWithDER { - EVP_PKEY* key; - unsigned char* der_data; - long der_len; -}; - -class KeyPassphrase { -public: - enum class Tag { - None = 0, - String = 1, - ArrayBuffer = 2, - }; - -private: - WTF::CString m_passphraseString; - JSC::JSUint8Array* m_passphraseArray = nullptr; - Tag tag = Tag::None; - -public: - bool hasPassphrase() - { - return tag != Tag::None; - } - - char* data() - { - switch (tag) { - case Tag::ArrayBuffer: { - return reinterpret_cast(this->m_passphraseArray->vector()); - } - - case Tag::String: { - return const_cast(this->m_passphraseString.data()); - } - - default: { - return nullptr; - } - } - - return nullptr; - } - - size_t length() - { - switch (tag) { - case Tag::ArrayBuffer: { - return this->m_passphraseArray->length(); - } - case Tag::String: { - return this->m_passphraseString.length(); - } - default: { - return 0; - } - } - - return 0; - } - - KeyPassphrase(JSValue passphraseJSValue, JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope) - { - this->tag = Tag::None; - this->m_passphraseString = WTF::CString(); - this->m_passphraseArray = nullptr; - - if (passphraseJSValue.isUndefinedOrNull() || passphraseJSValue.isEmpty()) { - return; - } - if (passphraseJSValue.isString()) { - auto passphrase_wtfstr = passphraseJSValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, ); - if (!passphrase_wtfstr.isNull()) { - if (auto pass = passphrase_wtfstr.tryGetUTF8()) { - if (pass.has_value()) { - this->tag = Tag::String; - this->m_passphraseString = WTFMove(pass.value()); - } - } - } - } else if (auto* array = jsDynamicCast(passphraseJSValue)) { - if (UNLIKELY(array->isDetached())) { - JSC::throwTypeError(globalObject, scope, "passphrase must not be detached"_s); - return; - } - - this->m_passphraseArray = array; - this->tag = Tag::ArrayBuffer; - } else { - JSC::throwTypeError(globalObject, scope, "passphrase must be a Buffer or String"_s); - } - } - - ~KeyPassphrase() - { - } - - WTF_MAKE_NONCOPYABLE(KeyPassphrase); - WTF_FORBID_HEAP_ALLOCATION(KeyPassphrase); -}; - -int PasswordCallback(char* buf, int size, int rwflag, void* u) -{ - auto result = static_cast(u); - if (result != nullptr && result->hasPassphrase() && size > 0) { - auto data = result->data(); - if (data != nullptr) { - size_t buflen = static_cast(size); - size_t len = result->length(); - if (buflen < len) - return -1; - memcpy(buf, result->data(), len); - return len; - } - } - - return -1; -} - -AsymmetricKeyValueWithDER KeyObject__ParsePublicKeyPEM(const char* key_pem, - size_t key_pem_len) -{ - auto bp = BIOPtr(BIO_new_mem_buf(const_cast(key_pem), key_pem_len)); - auto result = AsymmetricKeyValueWithDER { .key = nullptr, .der_data = nullptr, .der_len = 0 }; - - if (!bp) { - ERR_clear_error(); - return result; - } - - // Try parsing as a SubjectPublicKeyInfo first. - if (PEM_bytes_read_bio(&result.der_data, &result.der_len, nullptr, "PUBLIC KEY", bp.get(), nullptr, nullptr) == 1) { - // OpenSSL might modify the pointer, so we need to make a copy before parsing. - const unsigned char* p = result.der_data; - result.key = d2i_PUBKEY(nullptr, &p, result.der_len); - if (result.key) { - return result; - } - } - - ERR_clear_error(); - BIO_reset(bp.get()); - - // Maybe it is PKCS#1. - if (PEM_bytes_read_bio(&result.der_data, &result.der_len, nullptr, "RSA PUBLIC KEY", bp.get(), nullptr, nullptr) == 1) { - const unsigned char* p = result.der_data; - result.key = d2i_PublicKey(EVP_PKEY_RSA, nullptr, &p, result.der_len); - if (result.key) { - return result; - } - } - ERR_clear_error(); - BIO_reset(bp.get()); - - // X.509 fallback. - if (PEM_bytes_read_bio(&result.der_data, &result.der_len, nullptr, "CERTIFICATE", bp.get(), nullptr, nullptr) == 1) { - const unsigned char* p = result.der_data; - X509Ptr x509(d2i_X509(nullptr, &p, result.der_len)); - result.key = x509 ? X509_get_pubkey(x509.get()) : nullptr; - if (result.key) { - return result; - } - OPENSSL_clear_free(result.der_data, result.der_len); - ERR_clear_error(); - result.der_data = nullptr; - result.der_len = 0; - } else { - OPENSSL_clear_free(result.der_data, result.der_len); - ERR_clear_error(); - result.der_data = nullptr; - result.der_len = 0; - } - return result; -} - -JSC_DEFINE_HOST_FUNCTION(KeyObject__createPrivateKey, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - ncrypto::ClearErrorOnReturn clearErrorOnReturn; - - auto count = callFrame->argumentCount(); - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - - if (count < 1) { - JSC::throwTypeError(globalObject, scope, "createPrivateKey requires 1 arguments"_s); - return {}; - } - - auto* options = jsDynamicCast(callFrame->argument(0)); - if (!options) { - JSC::throwTypeError(globalObject, scope, "expected options to be a object"_s); - return {}; - } - - JSValue keyJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "key"_s))); - if (keyJSValue.isUndefinedOrNull() || keyJSValue.isEmpty()) { - JSC::throwTypeError(globalObject, scope, "key is required"_s); - return {}; - } - if (!keyJSValue.isCell()) { - JSC::throwTypeError(globalObject, scope, "key must be a Buffer, Array-like or object"_s); - return {}; - } - - JSValue formatJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "format"_s))); - if (formatJSValue.isUndefinedOrNull() || formatJSValue.isEmpty()) { - JSC::throwTypeError(globalObject, scope, "format is required"_s); - return {}; - } - - if (!formatJSValue.isString()) { - JSC::throwTypeError(globalObject, scope, "format must be a string"_s); - return {}; - } - auto format = formatJSValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - - Zig::GlobalObject* zigGlobalObject = reinterpret_cast(globalObject); - auto* structure = zigGlobalObject->JSCryptoKeyStructure(); - - void* data; - size_t byteLength; - - auto keyJSValueCell = keyJSValue.asCell(); - auto type = keyJSValueCell->type(); - - switch (type) { - - case DataViewType: - case Uint8ArrayType: - case Uint8ClampedArrayType: - case Uint16ArrayType: - case Uint32ArrayType: - case Int8ArrayType: - case Int16ArrayType: - case Int32ArrayType: - case Float16ArrayType: - case Float32ArrayType: - case Float64ArrayType: - case BigInt64ArrayType: - case BigUint64ArrayType: { - JSC::JSArrayBufferView* view = jsCast(keyJSValueCell); - - data = view->vector(); - byteLength = view->length(); - break; - } - case ArrayBufferType: { - auto* jsBuffer = jsDynamicCast(keyJSValueCell); - if (UNLIKELY(!jsBuffer)) { - throwException(globalObject, scope, createTypeError(globalObject, "ERR_INVALID_ARG_TYPE: expected key to be Buffer or array-like object"_s)); - return {}; - } - auto* buffer = jsBuffer->impl(); - data = buffer->data(); - byteLength = buffer->byteLength(); - break; - } - default: { - if (jsDynamicCast(keyJSValue)) { - if (format != "jwk"_s) { - JSC::throwTypeError(globalObject, scope, "format should be 'jwk' when key type is 'object'"_s); - return {}; - } - auto jwk = WebCore::convertDictionary(*globalObject, keyJSValue); - RETURN_IF_EXCEPTION(scope, {}); - if (jwk.kty == "OKP"_s) { - if (jwk.crv == "Ed25519"_s) { - auto result = CryptoKeyOKP::importJwk(CryptoAlgorithmIdentifier::Ed25519, CryptoKeyOKP::NamedCurve::Ed25519, WTFMove(jwk), true, CryptoKeyUsageSign); - if (UNLIKELY(result == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid Ed25519 private key"_s)); - return {}; - } - auto impl = result.releaseNonNull(); - if (impl->type() != CryptoKeyType::Private) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid private key"_s)); - return {}; - } - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else if (jwk.crv == "X25519"_s) { - auto result = CryptoKeyOKP::importJwk(CryptoAlgorithmIdentifier::Ed25519, CryptoKeyOKP::NamedCurve::X25519, WTFMove(jwk), true, CryptoKeyUsageSign); - if (UNLIKELY(result == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid X25519 private key"_s)); - return {}; - } - auto impl = result.releaseNonNull(); - if (impl->type() != CryptoKeyType::Private) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid private key"_s)); - return {}; - } - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else { - throwException(globalObject, scope, createTypeError(globalObject, "Unsupported OKP curve"_s)); - return {}; - } - } else if (jwk.kty == "EC"_s) { - auto result = CryptoKeyEC::importJwk(CryptoAlgorithmIdentifier::ECDSA, jwk.crv, WTFMove(jwk), true, jwk.usages); - if (UNLIKELY(result == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid EC private key"_s)); - return {}; - } - auto impl = result.releaseNonNull(); - if (impl->type() != CryptoKeyType::Private) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid private key"_s)); - return {}; - } - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else if (jwk.kty == "RSA"_s) { - auto result = CryptoKeyRSA::importJwk(CryptoAlgorithmIdentifier::RSA_OAEP, std::nullopt, WTFMove(jwk), true, jwk.usages); - if (UNLIKELY(result == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid RSA private key"_s)); - return {}; - } - auto impl = result.releaseNonNull(); - if (impl->type() != CryptoKeyType::Private) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid private key"_s)); - return {}; - } - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else { - throwException(globalObject, scope, createTypeError(globalObject, "Unsupported private key"_s)); - return {}; - } - } - JSC::throwTypeError(globalObject, scope, "The \"key\" property must be of type object"_s); - return {}; - } - } - - if (format == "jwk"_s) { - JSC::throwTypeError(globalObject, scope, "The \"key\" property must be of type object"_s); - return {}; - } - - if (UNLIKELY(!data) || UNLIKELY(!byteLength)) { - throwException(globalObject, scope, createTypeError(globalObject, "ERR_INVALID_ARG_TYPE: expected key to be Buffer or array-like object"_s)); - return {}; - } - - JSValue passphraseJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "passphrase"_s))); - KeyPassphrase passphrase(passphraseJSValue, globalObject, scope); - RETURN_IF_EXCEPTION(scope, {}); - - if (format == "pem"_s) { - ASSERT(data); - auto bio = BIOPtr(BIO_new_mem_buf(const_cast((char*)data), byteLength)); - auto pkey = EvpPKeyPtr(PEM_read_bio_PrivateKey(bio.get(), nullptr, PasswordCallback, &passphrase)); - - if (!pkey) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid private key pem file"_s)); - return {}; - } - auto pKeyID = EVP_PKEY_id(pkey.get()); - - if (pKeyID == EVP_PKEY_RSA || pKeyID == EVP_PKEY_RSA_PSS) { - auto impl = CryptoKeyRSA::create(pKeyID == EVP_PKEY_RSA_PSS ? CryptoAlgorithmIdentifier::RSA_PSS : 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 (pKeyID == EVP_PKEY_ED25519 || pKeyID == EVP_PKEY_X25519) { - size_t out_len = 0; - if (!EVP_PKEY_get_raw_private_key(pkey.get(), nullptr, &out_len)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid private key"_s)); - return {}; - } - Vector out(out_len); - if (!EVP_PKEY_get_raw_private_key(pkey.get(), out.data(), &out_len) || out_len != out.size()) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid private key"_s)); - return {}; - } - auto result = CryptoKeyOKP::create(CryptoAlgorithmIdentifier::Ed25519, pKeyID == EVP_PKEY_ED25519 ? CryptoKeyOKP::NamedCurve::Ed25519 : CryptoKeyOKP::NamedCurve::X25519, CryptoKeyType::Private, WTFMove(out), true, CryptoKeyUsageSign); - if (UNLIKELY(result == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid private key"_s)); - return {}; - } - auto impl = result.releaseNonNull(); - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else if (pKeyID == EVP_PKEY_EC) { - EC_KEY* ec_key = EVP_PKEY_get1_EC_KEY(pkey.get()); - if (UNLIKELY(ec_key == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid EC private key"_s)); - return {}; - } - const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key); - // Get the curve name - int curve_name = EC_GROUP_get_curve_name(ec_group); - if (curve_name == NID_undef) { - EC_KEY_free(ec_key); - throwException(globalObject, scope, createTypeError(globalObject, "Unable to identify EC curve"_s)); - return {}; - } - CryptoKeyEC::NamedCurve curve; - if (curve_name == NID_X9_62_prime256v1) - curve = CryptoKeyEC::NamedCurve::P256; - else if (curve_name == NID_secp384r1) - curve = CryptoKeyEC::NamedCurve::P384; - else if (curve_name == NID_secp521r1) - curve = CryptoKeyEC::NamedCurve::P521; - else { - EC_KEY_free(ec_key); - throwException(globalObject, scope, createTypeError(globalObject, "Unsupported EC curve"_s)); - return {}; - } - EC_KEY_free(ec_key); - auto impl = CryptoKeyEC::create(CryptoAlgorithmIdentifier::ECDH, curve, CryptoKeyType::Private, WTFMove(pkey), true, CryptoKeyUsageSign); - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else { - throwException(globalObject, scope, createTypeError(globalObject, "Unsupported private key"_s)); - return {}; - } - } - if (format == "der"_s) { - JSValue typeJSValue = options->getIfPropertyExists(globalObject, PropertyName(vm.propertyNames->type)); - WTF::String type = "pkcs8"_s; - if (!typeJSValue.isUndefinedOrNull() && !typeJSValue.isEmpty()) { - if (!typeJSValue.isString()) { - JSC::throwTypeError(globalObject, scope, "type must be a string"_s); - return {}; - } - type = typeJSValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - } - - if (type == "pkcs1"_s) { - // must be RSA - const unsigned char* p = reinterpret_cast(data); - auto pkey = EvpPKeyPtr(d2i_PrivateKey(EVP_PKEY_RSA, nullptr, &p, byteLength)); - if (!pkey) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid use of PKCS#1 as private key"_s)); - return {}; - } - auto pKeyID = EVP_PKEY_id(pkey.get()); - 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) { - - auto bio = BIOPtr(BIO_new_mem_buf(const_cast((char*)data), byteLength)); - WebCore::EvpPKeyPtr pkey; - if (KeyObject__IsEncryptedPrivateKeyInfo(const_cast((unsigned char*)data), byteLength)) { - pkey = EvpPKeyPtr(d2i_PKCS8PrivateKey_bio(bio.get(), - nullptr, - PasswordCallback, - &passphrase)); - } else { - auto* p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(bio.get(), nullptr); - if (!p8inf) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid PKCS8 data"_s)); - return {}; - } - pkey = EvpPKeyPtr(EVP_PKCS82PKEY(p8inf)); - } - if (!pkey) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid private key"_s)); - return {}; - } - auto pKeyID = EVP_PKEY_id(pkey.get()); - - if (pKeyID == EVP_PKEY_RSA || pKeyID == EVP_PKEY_RSA_PSS) { - auto impl = CryptoKeyRSA::create(pKeyID == EVP_PKEY_RSA_PSS ? CryptoAlgorithmIdentifier::RSA_PSS : 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 (pKeyID == EVP_PKEY_ED25519) { - auto result = CryptoKeyOKP::importPkcs8(CryptoAlgorithmIdentifier::Ed25519, CryptoKeyOKP::NamedCurve::Ed25519, Vector(std::span { (uint8_t*)data, byteLength }), true, CryptoKeyUsageSign); - if (UNLIKELY(result == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid Ed25519 private key"_s)); - return {}; - } - auto impl = result.releaseNonNull(); - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else if (pKeyID == EVP_PKEY_X25519) { - auto result = CryptoKeyOKP::importPkcs8(CryptoAlgorithmIdentifier::X25519, CryptoKeyOKP::NamedCurve::X25519, Vector(std::span { (uint8_t*)data, byteLength }), true, CryptoKeyUsageDeriveKey); - if (UNLIKELY(result == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid X25519 private key"_s)); - return {}; - } - auto impl = result.releaseNonNull(); - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else if (pKeyID == EVP_PKEY_EC) { - EC_KEY* ec_key = EVP_PKEY_get1_EC_KEY(pkey.get()); - if (UNLIKELY(ec_key == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid EC private key"_s)); - return {}; - } - const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key); - // Get the curve name - int curve_name = EC_GROUP_get_curve_name(ec_group); - if (curve_name == NID_undef) { - EC_KEY_free(ec_key); - throwException(globalObject, scope, createTypeError(globalObject, "Unable to identify EC curve"_s)); - return {}; - } - CryptoKeyEC::NamedCurve curve; - if (curve_name == NID_X9_62_prime256v1) - curve = CryptoKeyEC::NamedCurve::P256; - else if (curve_name == NID_secp384r1) - curve = CryptoKeyEC::NamedCurve::P384; - else if (curve_name == NID_secp521r1) - curve = CryptoKeyEC::NamedCurve::P521; - else { - EC_KEY_free(ec_key); - throwException(globalObject, scope, createTypeError(globalObject, "Unsupported EC curve"_s)); - return {}; - } - auto result = CryptoKeyEC::platformImportPkcs8(CryptoAlgorithmIdentifier::ECDH, curve, Vector(std::span { (uint8_t*)data, byteLength }), true, CryptoKeyUsageSign); - if (UNLIKELY(result == nullptr)) { - result = CryptoKeyEC::platformImportPkcs8(CryptoAlgorithmIdentifier::ECDSA, curve, Vector(std::span { (uint8_t*)data, byteLength }), true, CryptoKeyUsageSign); - } - EC_KEY_free(ec_key); - if (UNLIKELY(result == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid EC private key"_s)); - return {}; - } - auto impl = result.releaseNonNull(); - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else { - throwException(globalObject, scope, createTypeError(globalObject, "Unsupported private key"_s)); - return {}; - } - } else if (type == "sec1"_s) { - const unsigned char* p = reinterpret_cast(data); - auto pkey = EvpPKeyPtr(d2i_PrivateKey(EVP_PKEY_EC, nullptr, &p, byteLength)); - auto pKeyID = EVP_PKEY_id(pkey.get()); - - if (pKeyID == EVP_PKEY_EC) { - EC_KEY* ec_key = EVP_PKEY_get1_EC_KEY(pkey.get()); - if (UNLIKELY(ec_key == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid EC private key"_s)); - return {}; - } - const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key); - // Get the curve name - int curve_name = EC_GROUP_get_curve_name(ec_group); - if (curve_name == NID_undef) { - EC_KEY_free(ec_key); - throwException(globalObject, scope, createTypeError(globalObject, "Unable to identify EC curve"_s)); - return {}; - } - CryptoKeyEC::NamedCurve curve; - if (curve_name == NID_X9_62_prime256v1) - curve = CryptoKeyEC::NamedCurve::P256; - else if (curve_name == NID_secp384r1) - curve = CryptoKeyEC::NamedCurve::P384; - else if (curve_name == NID_secp521r1) - curve = CryptoKeyEC::NamedCurve::P521; - else { - EC_KEY_free(ec_key); - throwException(globalObject, scope, createTypeError(globalObject, "Unsupported EC curve"_s)); - return {}; - } - EC_KEY_free(ec_key); - auto impl = CryptoKeyEC::create(CryptoAlgorithmIdentifier::ECDH, curve, CryptoKeyType::Private, WTFMove(pkey), true, CryptoKeyUsageSign); - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid EC private key"_s)); - return {}; - } - } - - JSC::throwTypeError(globalObject, scope, "type should be 'pkcs1', 'pkcs8' or 'sec1'"_s); - return {}; - } - - JSC::throwTypeError(globalObject, scope, "format should be 'pem' or 'der'"_s); - return {}; -} - -static JSC::EncodedJSValue KeyObject__createRSAFromPrivate(JSC::JSGlobalObject* globalObject, EVP_PKEY* pkey, WebCore::CryptoAlgorithmIdentifier alg) -{ - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - const RSA* rsa_key = EVP_PKEY_get0_RSA(pkey); - - auto publicRSA = RSAPtr(RSAPublicKey_dup(rsa_key)); - if (!publicRSA) { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Failed to create a public key from private"_s); - return {}; - } - auto publicPKey = EvpPKeyPtr(EVP_PKEY_new()); - if (EVP_PKEY_set1_RSA(publicPKey.get(), publicRSA.get()) <= 0) { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Failed to create a public key from private"_s); - return {}; - } - auto impl = CryptoKeyRSA::create(alg, CryptoAlgorithmIdentifier::SHA_1, false, CryptoKeyType::Public, WTFMove(publicPKey), true, CryptoKeyUsageVerify); - Zig::GlobalObject* zigGlobalObject = reinterpret_cast(globalObject); - auto* structure = zigGlobalObject->JSCryptoKeyStructure(); - - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); -} - -static JSC::EncodedJSValue KeyObject__createECFromPrivate(JSC::JSGlobalObject* globalObject, EVP_PKEY* pkey, CryptoKeyEC::NamedCurve namedCurve, WebCore::CryptoAlgorithmIdentifier alg) -{ - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - - EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(pkey); - auto point = ECPointPtr(EC_POINT_dup(EC_KEY_get0_public_key(ec_key), EC_KEY_get0_group(ec_key))); - if (!point) { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Failed to create a public key from private 1"_s); - return {}; - } - auto curve = NID_undef; - - switch (namedCurve) { - case CryptoKeyEC::NamedCurve::P256: - curve = NID_X9_62_prime256v1; - break; - case CryptoKeyEC::NamedCurve::P384: - curve = NID_secp384r1; - break; - case CryptoKeyEC::NamedCurve::P521: - curve = NID_secp521r1; - break; - } - auto publicECKey = ECKeyPtr(EC_KEY_new_by_curve_name(curve)); - if (!publicECKey) { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Failed to create a public key from private 2"_s); - return {}; - } - // OPENSSL_EC_NAMED_CURVE needs to be set to export the key with the curve name, not with the curve parameters. - EC_KEY_set_asn1_flag(publicECKey.get(), OPENSSL_EC_NAMED_CURVE); - if (EC_KEY_set_public_key(publicECKey.get(), point.get()) <= 0) { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Failed to create a public key from private 3"_s); - return {}; - } - auto publicPKey = EvpPKeyPtr(EVP_PKEY_new()); - if (EVP_PKEY_set1_EC_KEY(publicPKey.get(), publicECKey.get()) <= 0) { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Failed to create a public key from private 4"_s); - return {}; - } - auto impl = CryptoKeyEC::create(alg, namedCurve, CryptoKeyType::Public, WTFMove(publicPKey), true, CryptoKeyUsageVerify); - - Zig::GlobalObject* zigGlobalObject = reinterpret_cast(globalObject); - auto* structure = zigGlobalObject->JSCryptoKeyStructure(); - - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); -} - -static JSC::EncodedJSValue KeyObject__createOKPEd25519FromPublicKey(JSC::JSGlobalObject* globalObject, Vector& keyData) -{ - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - - if (keyData.size() != ED25519_PUBLIC_KEY_LEN) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid Ed25519 key material"_s)); - return {}; - } - - auto result = CryptoKeyOKP::create(CryptoAlgorithmIdentifier::Ed25519, CryptoKeyOKP::NamedCurve::Ed25519, CryptoKeyType::Public, WTFMove(keyData), true, CryptoKeyUsageVerify); - if (UNLIKELY(result == nullptr)) { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Failed to create a public key from private"_s); - return {}; - } - auto impl = result.releaseNonNull(); - - Zig::GlobalObject* zigGlobalObject = reinterpret_cast(globalObject); - auto* structure = zigGlobalObject->JSCryptoKeyStructure(); - - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); -} -static JSC::EncodedJSValue KeyObject__createOKPFromKeyMaterial(JSC::JSGlobalObject* globalObject, const WebCore::CryptoKeyOKP::KeyMaterial keyData, CryptoKeyOKP::NamedCurve namedCurve, WebCore::CryptoAlgorithmIdentifier alg) -{ - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - - Vector public_key(ED25519_PUBLIC_KEY_LEN); - - if (namedCurve == CryptoKeyOKP::NamedCurve::Ed25519) { - if (keyData.size() != ED25519_PRIVATE_KEY_LEN + ED25519_PUBLIC_KEY_LEN) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid Ed25519 key material"_s)); - return {}; - } - memcpy(public_key.data(), keyData.data() + ED25519_PRIVATE_KEY_LEN, ED25519_PUBLIC_KEY_LEN); - } else { - X25519_public_from_private(public_key.data(), keyData.data()); - } - auto result = CryptoKeyOKP::create(alg, namedCurve, CryptoKeyType::Public, WTFMove(public_key), true, CryptoKeyUsageVerify); - if (UNLIKELY(result == nullptr)) { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Failed to create a public key from private"_s); - return {}; - } - auto impl = result.releaseNonNull(); - - Zig::GlobalObject* zigGlobalObject = reinterpret_cast(globalObject); - auto* structure = zigGlobalObject->JSCryptoKeyStructure(); - - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); -} - -static JSC::EncodedJSValue KeyObject__createPublicFromPrivate(JSC::JSGlobalObject* globalObject, EVP_PKEY* pkey) -{ - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - - auto pKeyID = EVP_PKEY_id(pkey); - if (pKeyID == EVP_PKEY_RSA || pKeyID == EVP_PKEY_RSA_PSS) { - return KeyObject__createRSAFromPrivate(globalObject, pkey, pKeyID == EVP_PKEY_RSA_PSS ? CryptoAlgorithmIdentifier::RSA_PSS : CryptoAlgorithmIdentifier::RSA_OAEP); - } else if (pKeyID == EVP_PKEY_EC) { - - EC_KEY* ec_key = EVP_PKEY_get1_EC_KEY(pkey); - if (UNLIKELY(ec_key == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid EC key"_s)); - return {}; - } - const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key); - // Get the curve name - int curve_name = EC_GROUP_get_curve_name(ec_group); - if (curve_name == NID_undef) { - EC_KEY_free(ec_key); - throwException(globalObject, scope, createTypeError(globalObject, "Unable to identify EC curve"_s)); - return {}; - } - CryptoKeyEC::NamedCurve curve; - if (curve_name == NID_X9_62_prime256v1) - curve = CryptoKeyEC::NamedCurve::P256; - else if (curve_name == NID_secp384r1) - curve = CryptoKeyEC::NamedCurve::P384; - else if (curve_name == NID_secp521r1) - curve = CryptoKeyEC::NamedCurve::P521; - else { - EC_KEY_free(ec_key); - throwException(globalObject, scope, createTypeError(globalObject, "Unsupported EC curve"_s)); - return {}; - } - EC_KEY_free(ec_key); - return KeyObject__createECFromPrivate(globalObject, pkey, curve, CryptoAlgorithmIdentifier::ECDSA); - } else if (pKeyID == EVP_PKEY_ED25519) { - size_t out_len = 0; - if (!EVP_PKEY_get_raw_public_key(pkey, nullptr, &out_len)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid private key"_s)); - return {}; - } - Vector out(out_len); - if (!EVP_PKEY_get_raw_public_key(pkey, out.data(), &out_len) || out_len != out.size()) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid private key"_s)); - return {}; - } - return KeyObject__createOKPEd25519FromPublicKey(globalObject, out); - } else if (pKeyID == EVP_PKEY_X25519) { - size_t out_len = 0; - if (!EVP_PKEY_get_raw_private_key(pkey, nullptr, &out_len)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid private key"_s)); - return {}; - } - Vector out(out_len); - if (!EVP_PKEY_get_raw_private_key(pkey, out.data(), &out_len) || out_len != out.size()) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid private key"_s)); - return {}; - } - return KeyObject__createOKPFromKeyMaterial(globalObject, out, CryptoKeyOKP::NamedCurve::X25519, CryptoAlgorithmIdentifier::X25519); - } else { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid private key type"_s)); - return {}; - } -} - -JSC_DEFINE_HOST_FUNCTION(KeyObject__createPublicKey, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - ncrypto::ClearErrorOnReturn clearErrorOnReturn; - auto count = callFrame->argumentCount(); - auto& vm = JSC::getVM(globalObject); - - auto scope = DECLARE_THROW_SCOPE(vm); - - if (count < 1) { - auto scope = DECLARE_THROW_SCOPE(vm); - JSC::throwTypeError(globalObject, scope, "createPublicKey requires 1 arguments"_s); - return {}; - } - auto* options = jsDynamicCast(callFrame->argument(0)); - if (!options) { - JSC::throwTypeError(globalObject, scope, "expected options to be a object"_s); - return {}; - } - JSValue keyJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "key"_s))); - if (keyJSValue.isUndefinedOrNull() || keyJSValue.isEmpty()) { - JSC::throwTypeError(globalObject, scope, "key is required"_s); - return {}; - } - Zig::GlobalObject* zigGlobalObject = reinterpret_cast(globalObject); - auto* structure = zigGlobalObject->JSCryptoKeyStructure(); - - void* data = nullptr; - size_t byteLength = 0; - if (auto* key = jsDynamicCast(keyJSValue)) { - auto& wrapped = key->wrapped(); - auto key_type = wrapped.type(); - if (key_type != CryptoKeyType::Private) { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Invalid key object type, expected private"_s); - return {}; - } - auto id = wrapped.keyClass(); - - switch (id) { - case CryptoKeyClass::RSA: { - return KeyObject__createRSAFromPrivate(globalObject, downcast(wrapped).platformKey(), wrapped.algorithmIdentifier()); - } - case CryptoKeyClass::EC: { - auto& impl = downcast(wrapped); - return KeyObject__createECFromPrivate(globalObject, impl.platformKey(), impl.namedCurve(), wrapped.algorithmIdentifier()); - } - case CryptoKeyClass::OKP: { - auto& impl = downcast(wrapped); - return KeyObject__createOKPFromKeyMaterial(globalObject, impl.exportKey(), impl.namedCurve(), wrapped.algorithmIdentifier()); - } - default: { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Invalid key object type, expected private"_s); - return {}; - } - } - } - if (!keyJSValue.isCell()) { - JSC::throwTypeError(globalObject, scope, "expected options to be a object"_s); - return {}; - } - - JSValue formatJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "format"_s))); - if (formatJSValue.isUndefinedOrNull() || formatJSValue.isEmpty()) { - JSC::throwTypeError(globalObject, scope, "format is required"_s); - return {}; - } - if (!formatJSValue.isString()) { - JSC::throwTypeError(globalObject, scope, "format must be a string"_s); - return {}; - } - auto format = formatJSValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - - auto keyJSValueCell = keyJSValue.asCell(); - auto type = keyJSValueCell->type(); - - switch (type) { - - case DataViewType: - case Uint8ArrayType: - case Uint8ClampedArrayType: - case Uint16ArrayType: - case Uint32ArrayType: - case Int8ArrayType: - case Int16ArrayType: - case Int32ArrayType: - case Float16ArrayType: - case Float32ArrayType: - case Float64ArrayType: - case BigInt64ArrayType: - case BigUint64ArrayType: { - JSC::JSArrayBufferView* view = jsCast(keyJSValueCell); - - data = view->vector(); - byteLength = view->length(); - break; - } - case ArrayBufferType: { - auto* jsBuffer = jsDynamicCast(keyJSValueCell); - if (UNLIKELY(!jsBuffer)) { - auto scope = DECLARE_THROW_SCOPE(vm); - throwException(globalObject, scope, createTypeError(globalObject, "ERR_INVALID_ARG_TYPE: expected key to be Buffer or array-like object"_s)); - return {}; - } - auto* buffer = jsBuffer->impl(); - data = buffer->data(); - byteLength = buffer->byteLength(); - break; - } - default: { - if (jsDynamicCast(keyJSValue)) { - if (format != "jwk"_s) { - JSC::throwTypeError(globalObject, scope, "format should be 'jwk' when key type is 'object'"_s); - return {}; - } - auto jwk = WebCore::convertDictionary(*globalObject, keyJSValue); - RETURN_IF_EXCEPTION(scope, {}); - if (jwk.kty == "OKP"_s) { - if (jwk.crv == "Ed25519"_s) { - auto result = CryptoKeyOKP::importPublicJwk(CryptoAlgorithmIdentifier::Ed25519, CryptoKeyOKP::NamedCurve::Ed25519, WTFMove(jwk), true, CryptoKeyUsageVerify); - if (UNLIKELY(result == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid Ed25519 public key"_s)); - return {}; - } - auto impl = result.releaseNonNull(); - if (impl->type() == CryptoKeyType::Private) { - return KeyObject__createOKPFromKeyMaterial(globalObject, impl.get().exportKey(), CryptoKeyOKP::NamedCurve::Ed25519, CryptoAlgorithmIdentifier::Ed25519); - } - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else if (jwk.crv == "X25519"_s) { - auto result = CryptoKeyOKP::importPublicJwk(CryptoAlgorithmIdentifier::X25519, CryptoKeyOKP::NamedCurve::X25519, WTFMove(jwk), true, CryptoKeyUsageDeriveKey); - if (UNLIKELY(result == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid X25519 public key"_s)); - return {}; - } - auto impl = result.releaseNonNull(); - if (impl->type() == CryptoKeyType::Private) { - return KeyObject__createOKPFromKeyMaterial(globalObject, impl.get().exportKey(), CryptoKeyOKP::NamedCurve::X25519, CryptoAlgorithmIdentifier::Ed25519); - } - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else { - throwException(globalObject, scope, createTypeError(globalObject, "Unsupported OKP curve"_s)); - return {}; - } - } else if (jwk.kty == "EC"_s) { - auto result = CryptoKeyEC::importJwk(CryptoAlgorithmIdentifier::ECDSA, jwk.crv, WTFMove(jwk), true, jwk.usages); - if (UNLIKELY(result == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid EC public key"_s)); - return {}; - } - auto impl = result.releaseNonNull(); - if (impl->type() == CryptoKeyType::Private) { - return KeyObject__createECFromPrivate(globalObject, impl.get().platformKey(), impl.get().namedCurve(), CryptoAlgorithmIdentifier::ECDSA); - } - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else if (jwk.kty == "RSA"_s) { - auto result = CryptoKeyRSA::importJwk(CryptoAlgorithmIdentifier::RSA_OAEP, std::nullopt, WTFMove(jwk), true, jwk.usages); - if (UNLIKELY(result == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid RSA public key"_s)); - return {}; - } - auto impl = result.releaseNonNull(); - if (impl->type() == CryptoKeyType::Private) { - return KeyObject__createRSAFromPrivate(globalObject, impl.get().platformKey(), CryptoAlgorithmIdentifier::RSA_OAEP); - } - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else { - throwException(globalObject, scope, createTypeError(globalObject, "Unsupported public key"_s)); - return {}; - } - } - } - } - - if (format == "jwk"_s) { - JSC::throwTypeError(globalObject, scope, "The \"key\" property must be of type object"_s); - return {}; - } - - if (UNLIKELY(!data) || UNLIKELY(!byteLength)) { - throwException(globalObject, scope, createTypeError(globalObject, "ERR_INVALID_ARG_TYPE: expected key to be Buffer or array-like object"_s)); - return {}; - } - - if (format == "pem"_s) { - auto pem = KeyObject__ParsePublicKeyPEM((const char*)data, byteLength); - if (!pem.key) { - // maybe is a private pem - auto bio = BIOPtr(BIO_new_mem_buf(const_cast((char*)data), byteLength)); - JSValue passphraseJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "passphrase"_s))); - KeyPassphrase passphrase(passphraseJSValue, globalObject, scope); - RETURN_IF_EXCEPTION(scope, {}); - auto pkey = EvpPKeyPtr(PEM_read_bio_PrivateKey(bio.get(), nullptr, PasswordCallback, &passphrase)); - if (!pkey) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid PEM data"_s)); - return {}; - } - return KeyObject__createPublicFromPrivate(globalObject, pkey.get()); - } - auto pkey = EvpPKeyPtr(pem.key); - auto pKeyID = EVP_PKEY_id(pem.key); - if (pKeyID == EVP_PKEY_RSA || pKeyID == EVP_PKEY_RSA_PSS) { - if (pem.der_data) { - OPENSSL_clear_free(pem.der_data, pem.der_len); - } - auto impl = CryptoKeyRSA::create(pKeyID == EVP_PKEY_RSA_PSS ? CryptoAlgorithmIdentifier::RSA_PSS : 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 (pKeyID == EVP_PKEY_ED25519) { - auto result = CryptoKeyOKP::importSpki(CryptoAlgorithmIdentifier::Ed25519, CryptoKeyOKP::NamedCurve::Ed25519, Vector(std::span { (uint8_t*)pem.der_data, (size_t)pem.der_len }), true, CryptoKeyUsageVerify); - if (pem.der_data) { - OPENSSL_clear_free(pem.der_data, pem.der_len); - } - if (UNLIKELY(result == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid Ed25519 public key"_s)); - return {}; - } - auto impl = result.releaseNonNull(); - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else if (pKeyID == EVP_PKEY_X25519) { - auto result = CryptoKeyOKP::importSpki(CryptoAlgorithmIdentifier::X25519, CryptoKeyOKP::NamedCurve::X25519, Vector(std::span { (uint8_t*)pem.der_data, (size_t)pem.der_len }), true, CryptoKeyUsageDeriveKey); - if (pem.der_data) { - OPENSSL_clear_free(pem.der_data, pem.der_len); - } - if (UNLIKELY(result == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid X25519 public key"_s)); - return {}; - } - auto impl = result.releaseNonNull(); - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else if (pKeyID == EVP_PKEY_EC) { - EC_KEY* ec_key = EVP_PKEY_get1_EC_KEY(pkey.get()); - if (UNLIKELY(ec_key == nullptr)) { - if (pem.der_data) { - OPENSSL_clear_free(pem.der_data, pem.der_len); - } - throwException(globalObject, scope, createTypeError(globalObject, "Invalid EC public key"_s)); - return {}; - } - const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key); - // Get the curve name - int curve_name = EC_GROUP_get_curve_name(ec_group); - if (curve_name == NID_undef) { - if (pem.der_data) { - OPENSSL_clear_free(pem.der_data, pem.der_len); - } - EC_KEY_free(ec_key); - throwException(globalObject, scope, createTypeError(globalObject, "Unable to identify EC curve"_s)); - return {}; - } - CryptoKeyEC::NamedCurve curve; - if (curve_name == NID_X9_62_prime256v1) - curve = CryptoKeyEC::NamedCurve::P256; - else if (curve_name == NID_secp384r1) - curve = CryptoKeyEC::NamedCurve::P384; - else if (curve_name == NID_secp521r1) - curve = CryptoKeyEC::NamedCurve::P521; - else { - if (pem.der_data) { - OPENSSL_clear_free(pem.der_data, pem.der_len); - } - EC_KEY_free(ec_key); - throwException(globalObject, scope, createTypeError(globalObject, "Unsupported EC curve"_s)); - return {}; - } - auto result = CryptoKeyEC::platformImportSpki(CryptoAlgorithmIdentifier::ECDH, curve, Vector(std::span { (uint8_t*)pem.der_data, (size_t)pem.der_len }), true, CryptoKeyUsageVerify); - if (UNLIKELY(result == nullptr)) { - result = CryptoKeyEC::platformImportSpki(CryptoAlgorithmIdentifier::ECDSA, curve, Vector(std::span { (uint8_t*)pem.der_data, (size_t)pem.der_len }), true, CryptoKeyUsageVerify); - } - if (pem.der_data) { - OPENSSL_clear_free(pem.der_data, pem.der_len); - } - if (UNLIKELY(result == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid EC public key"_s)); - return {}; - } - auto impl = result.releaseNonNull(); - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else { - if (pem.der_data) { - OPENSSL_clear_free(pem.der_data, pem.der_len); - } - throwException(globalObject, scope, createTypeError(globalObject, "Unsupported public key"_s)); - return {}; - } - } - if (format == "der"_s) { - JSValue typeJSValue = options->getIfPropertyExists(globalObject, PropertyName(vm.propertyNames->type)); - WTF::String type = "spki"_s; - if (!typeJSValue.isUndefinedOrNull() && !typeJSValue.isEmpty()) { - if (!typeJSValue.isString()) { - JSC::throwTypeError(globalObject, scope, "type must be a string"_s); - return {}; - } - type = typeJSValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - } - - if (type == "pkcs1"_s) { - // must be RSA - const unsigned char* p = reinterpret_cast(data); - auto pkey = EvpPKeyPtr(d2i_PublicKey(EVP_PKEY_RSA, nullptr, &p, byteLength)); - if (!pkey) { - // maybe is a private RSA key - const unsigned char* p = reinterpret_cast(data); - pkey = EvpPKeyPtr(d2i_PrivateKey(EVP_PKEY_RSA, nullptr, &p, byteLength)); - if (!pkey) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid PKCS#1"_s)); - return {}; - } - - auto pKeyID = EVP_PKEY_id(pkey.get()); - 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::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. - const uint8_t* ptr = reinterpret_cast(data); - auto pkey = EvpPKeyPtr(d2i_PUBKEY(nullptr, &ptr, byteLength)); - if (!pkey) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid public key"_s)); - return {}; - } - auto pKeyID = EVP_PKEY_id(pkey.get()); - - if (pKeyID == EVP_PKEY_RSA || pKeyID == EVP_PKEY_RSA_PSS) { - auto impl = CryptoKeyRSA::create(pKeyID == EVP_PKEY_RSA_PSS ? CryptoAlgorithmIdentifier::RSA_PSS : 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 (pKeyID == EVP_PKEY_ED25519) { - auto result = CryptoKeyOKP::importSpki(CryptoAlgorithmIdentifier::Ed25519, CryptoKeyOKP::NamedCurve::Ed25519, Vector(std::span { (uint8_t*)data, byteLength }), true, CryptoKeyUsageVerify); - if (UNLIKELY(result == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid Ed25519 public key"_s)); - return {}; - } - auto impl = result.releaseNonNull(); - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else if (pKeyID == EVP_PKEY_X25519) { - auto result = CryptoKeyOKP::importSpki(CryptoAlgorithmIdentifier::X25519, CryptoKeyOKP::NamedCurve::X25519, Vector(std::span { (uint8_t*)data, byteLength }), true, CryptoKeyUsageDeriveKey); - if (UNLIKELY(result == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid X25519 public key"_s)); - return {}; - } - auto impl = result.releaseNonNull(); - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else if (pKeyID == EVP_PKEY_EC) { - EC_KEY* ec_key = EVP_PKEY_get1_EC_KEY(pkey.get()); - if (UNLIKELY(ec_key == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid EC public key"_s)); - return {}; - } - const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key); - // Get the curve name - int curve_name = EC_GROUP_get_curve_name(ec_group); - if (curve_name == NID_undef) { - EC_KEY_free(ec_key); - throwException(globalObject, scope, createTypeError(globalObject, "Unable to identify EC curve"_s)); - return {}; - } - CryptoKeyEC::NamedCurve curve; - if (curve_name == NID_X9_62_prime256v1) - curve = CryptoKeyEC::NamedCurve::P256; - else if (curve_name == NID_secp384r1) - curve = CryptoKeyEC::NamedCurve::P384; - else if (curve_name == NID_secp521r1) - curve = CryptoKeyEC::NamedCurve::P521; - else { - EC_KEY_free(ec_key); - throwException(globalObject, scope, createTypeError(globalObject, "Unsupported EC curve"_s)); - return {}; - } - auto result = CryptoKeyEC::platformImportSpki(CryptoAlgorithmIdentifier::ECDH, curve, Vector(std::span { (uint8_t*)data, byteLength }), true, CryptoKeyUsageVerify); - if (UNLIKELY(result == nullptr)) { - result = CryptoKeyEC::platformImportSpki(CryptoAlgorithmIdentifier::ECDSA, curve, Vector(std::span { (uint8_t*)data, byteLength }), true, CryptoKeyUsageVerify); - } - if (UNLIKELY(result == nullptr)) { - throwException(globalObject, scope, createTypeError(globalObject, "Invalid EC public key"_s)); - return {}; - } - auto impl = result.releaseNonNull(); - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, WTFMove(impl))); - } else { - throwException(globalObject, scope, createTypeError(globalObject, "Unsupported public key"_s)); - return {}; - } - } - - JSC::throwTypeError(globalObject, scope, "type should be 'pkcs1' or 'spki'"_s); - return {}; - } - JSC::throwTypeError(globalObject, scope, "format should be 'pem' or 'der'"_s); - return {}; -} - -JSC_DEFINE_HOST_FUNCTION(KeyObject__createSecretKey, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - ncrypto::ClearErrorOnReturn clearErrorOnReturn; - JSValue bufferArg = callFrame->uncheckedArgument(0); - auto& vm = JSC::getVM(lexicalGlobalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - Zig::GlobalObject* globalObject = reinterpret_cast(lexicalGlobalObject); - auto* structure = globalObject->JSCryptoKeyStructure(); - - if (!bufferArg.isCell()) { - throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "ERR_INVALID_ARG_TYPE: expected Buffer or array-like object"_s)); - return {}; - } - - auto bufferArgCell = bufferArg.asCell(); - auto type = bufferArgCell->type(); - - switch (type) { - case DataViewType: - case Uint8ArrayType: - case Uint8ClampedArrayType: - case Uint16ArrayType: - case Uint32ArrayType: - case Int8ArrayType: - case Int16ArrayType: - case Int32ArrayType: - case Float16ArrayType: - case Float32ArrayType: - case Float64ArrayType: - case BigInt64ArrayType: - case BigUint64ArrayType: { - JSC::JSArrayBufferView* view = jsCast(bufferArgCell); - - void* data = view->vector(); - size_t byteLength = view->length(); - if (UNLIKELY(!data)) { - break; - } - auto impl = CryptoKeyHMAC::generateFromBytes(data, byteLength, CryptoAlgorithmIdentifier::HMAC, true, CryptoKeyUsageSign | CryptoKeyUsageVerify).releaseNonNull(); - return JSC::JSValue::encode(JSCryptoKey::create(structure, globalObject, WTFMove(impl))); - } - case ArrayBufferType: { - auto* jsBuffer = jsDynamicCast(bufferArgCell); - if (UNLIKELY(!jsBuffer)) { - break; - } - auto* buffer = jsBuffer->impl(); - void* data = buffer->data(); - size_t byteLength = buffer->byteLength(); - if (UNLIKELY(!data)) { - break; - } - Zig::GlobalObject* globalObject = reinterpret_cast(lexicalGlobalObject); - auto impl = CryptoKeyHMAC::generateFromBytes(data, byteLength, CryptoAlgorithmIdentifier::HMAC, true, CryptoKeyUsageSign | CryptoKeyUsageVerify).releaseNonNull(); - return JSC::JSValue::encode(JSCryptoKey::create(structure, globalObject, WTFMove(impl))); - } - default: { - break; - } - } - - throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "ERR_INVALID_ARG_TYPE: expected Buffer or array-like object"_s)); - return {}; -} - -ExceptionOr> KeyObject__GetBuffer(JSValue bufferArg) -{ - if (!bufferArg.isCell()) { - return Exception { OperationError }; - } - - auto bufferArgCell = bufferArg.asCell(); - auto type = bufferArgCell->type(); - - switch (type) { - case DataViewType: - case Uint8ArrayType: - case Uint8ClampedArrayType: - case Uint16ArrayType: - case Uint32ArrayType: - case Int8ArrayType: - case Int16ArrayType: - case Int32ArrayType: - case Float16ArrayType: - case Float32ArrayType: - case Float64ArrayType: - case BigInt64ArrayType: - case BigUint64ArrayType: { - JSC::JSArrayBufferView* view = jsCast(bufferArgCell); - if (view->isDetached()) { - break; - } - return view->span(); - } - case ArrayBufferType: { - auto* jsBuffer = jsDynamicCast(bufferArgCell); - if (UNLIKELY(!jsBuffer)) { - break; - } - auto* buffer = jsBuffer->impl(); - if (buffer->isDetached()) { - break; - } - return buffer->span(); - } - default: { - break; - } - } - return Exception { OperationError }; -} -JSC_DEFINE_HOST_FUNCTION(KeyObject__Sign, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - ncrypto::ClearErrorOnReturn clearErrorOnReturn; - auto count = callFrame->argumentCount(); - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - - if (count < 3) { - JSC::throwTypeError(globalObject, scope, "sign requires 3 arguments"_s); - return {}; - } - - auto* key = jsDynamicCast(callFrame->argument(0)); - if (!key) { - // No JSCryptoKey instance - JSC::throwTypeError(globalObject, scope, "expected CryptoKey as first argument"_s); - return {}; - } - JSValue bufferArg = callFrame->uncheckedArgument(1); - - auto buffer = KeyObject__GetBuffer(bufferArg); - if (buffer.hasException()) { - JSC::throwTypeError(globalObject, scope, "expected Buffer or array-like object as second argument"_s); - return {}; - } - auto vectorData = buffer.releaseReturnValue(); - auto& wrapped = key->wrapped(); - auto id = wrapped.keyClass(); - - auto hash = WebCore::CryptoAlgorithmIdentifier::SHA_256; - auto algorithm = callFrame->argument(2); - auto customHash = false; - if (!algorithm.isUndefinedOrNull() && !algorithm.isEmpty()) { - customHash = true; - if (!algorithm.isString()) { - JSC::throwTypeError(globalObject, scope, "algorithm is expected to be a string"_s); - return {}; - } - auto algorithm_str = algorithm.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - - auto identifier = CryptoAlgorithmRegistry::singleton().identifier(algorithm_str); - if (UNLIKELY(!identifier)) { - JSC::throwTypeError(globalObject, scope, "digest not allowed"_s); - return {}; - } - - switch (*identifier) { - 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: { - - hash = *identifier; - break; - } - default: { - JSC::throwTypeError(globalObject, scope, "digest not allowed"_s); - return {}; - } - } - } - - switch (id) { - case CryptoKeyClass::HMAC: { - const auto& hmac = downcast(wrapped); - auto result = (customHash) ? WebCore::CryptoAlgorithmHMAC::platformSignWithAlgorithm(hmac, hash, vectorData) : WebCore::CryptoAlgorithmHMAC::platformSign(hmac, vectorData); - if (result.hasException()) { - WebCore::propagateException(*globalObject, scope, result.releaseException()); - return JSC::JSValue::encode(JSC::JSValue {}); - } - auto resultData = result.releaseReturnValue(); - auto* buffer = createBuffer(globalObject, resultData); - return JSC::JSValue::encode(buffer); - } - case CryptoKeyClass::OKP: { - const auto& okpKey = downcast(wrapped); - auto result = WebCore::CryptoAlgorithmEd25519::platformSign(okpKey, vectorData); - if (result.hasException()) { - WebCore::propagateException(*globalObject, scope, result.releaseException()); - return JSC::JSValue::encode(JSC::JSValue {}); - } - auto resultData = result.releaseReturnValue(); - auto* buffer = WebCore::createBuffer(globalObject, resultData); - return JSC::JSValue::encode(buffer); - } - case CryptoKeyClass::EC: { - const auto& ec = downcast(wrapped); - CryptoAlgorithmEcdsaParams params; - params.identifier = CryptoAlgorithmIdentifier::ECDSA; - params.hashIdentifier = hash; - params.encoding = CryptoAlgorithmECDSAEncoding::DER; - - if (count > 3) { - auto encoding = callFrame->argument(3); - if (!encoding.isUndefinedOrNull() && !encoding.isEmpty()) { - if (!encoding.isString()) { - JSC::throwTypeError(globalObject, scope, "dsaEncoding is expected to be a string"_s); - return {}; - } - auto encoding_str = encoding.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - - if (encoding_str == "ieee-p1363"_s) { - params.encoding = CryptoAlgorithmECDSAEncoding::IeeeP1363; - } else if (encoding_str == "der"_s) { - params.encoding = CryptoAlgorithmECDSAEncoding::DER; - } else { - JSC::throwTypeError(globalObject, scope, "invalid dsaEncoding"_s); - return {}; - } - } - } - auto result = WebCore::CryptoAlgorithmECDSA::platformSign(params, ec, vectorData); - if (result.hasException()) { - WebCore::propagateException(*globalObject, scope, result.releaseException()); - return JSC::JSValue::encode(JSC::JSValue {}); - } - auto resultData = result.releaseReturnValue(); - auto* buffer = WebCore::createBuffer(globalObject, resultData); - return JSC::JSValue::encode(buffer); - } - case CryptoKeyClass::RSA: { - const auto& rsa = downcast(wrapped); - CryptoAlgorithmIdentifier restrict_hash; - bool isRestrictedToHash = rsa.isRestrictedToHash(restrict_hash); - if (isRestrictedToHash && hash != restrict_hash) { - JSC::throwTypeError(globalObject, scope, "digest not allowed"_s); - return {}; - } - switch (rsa.algorithmIdentifier()) { - case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: { - auto result = (customHash) ? WebCore::CryptoAlgorithmRSASSA_PKCS1_v1_5::platformSignWithAlgorithm(rsa, hash, vectorData) : CryptoAlgorithmRSASSA_PKCS1_v1_5::platformSign(rsa, vectorData); - if (result.hasException()) { - WebCore::propagateException(*globalObject, scope, result.releaseException()); - return JSC::JSValue::encode(JSC::JSValue {}); - } - auto resultData = result.releaseReturnValue(); - auto* buffer = WebCore::createBuffer(globalObject, resultData); - - return JSC::JSValue::encode(buffer); - } - case CryptoAlgorithmIdentifier::RSA_PSS: { - CryptoAlgorithmRsaPssParams params; - params.padding = RSA_PKCS1_PADDING; - if (count > 4) { - auto padding = callFrame->argument(4); - if (!padding.isUndefinedOrNull() && !padding.isEmpty()) { - if (!padding.isNumber()) { - JSC::throwTypeError(globalObject, scope, "padding is expected to be a number"_s); - return {}; - } - params.padding = padding.toUInt32(globalObject); - } - // requires saltLength - if (params.padding == RSA_PKCS1_PSS_PADDING) { - if (count <= 5) { - JSC::throwTypeError(globalObject, scope, "saltLength is expected to be a number"_s); - return {}; - } - - auto saltLength = callFrame->argument(5); - if (saltLength.isUndefinedOrNull() || saltLength.isEmpty() || !saltLength.isNumber()) { - JSC::throwTypeError(globalObject, scope, "saltLength is expected to be a number"_s); - return {}; - } - params.saltLength = saltLength.toUInt32(globalObject); - } else if (count > 5) { - auto saltLength = callFrame->argument(5); - if (!saltLength.isUndefinedOrNull() && !saltLength.isEmpty() && !saltLength.isNumber()) { - JSC::throwTypeError(globalObject, scope, "saltLength is expected to be a number"_s); - return {}; - } - params.saltLength = saltLength.toUInt32(globalObject); - params.padding = RSA_PKCS1_PSS_PADDING; // if saltLength is provided, padding must be RSA_PKCS1_PSS_PADDING - } - } - params.identifier = CryptoAlgorithmIdentifier::RSA_PSS; - auto result = (customHash) ? WebCore::CryptoAlgorithmRSA_PSS::platformSignWithAlgorithm(params, hash, rsa, vectorData) : CryptoAlgorithmRSA_PSS::platformSign(params, rsa, vectorData); - if (result.hasException()) { - WebCore::propagateException(*globalObject, scope, result.releaseException()); - return JSC::JSValue::encode(JSC::JSValue {}); - } - auto resultData = result.releaseReturnValue(); - auto* buffer = WebCore::createBuffer(globalObject, resultData); - - return JSC::JSValue::encode(buffer); - } - default: { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Sign not supported for this key type"_s); - return {}; - } - } - } - case CryptoKeyClass::AES: { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Sign not supported for AES key type"_s); - return {}; - } - case CryptoKeyClass::Raw: { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Sign not supported for Raw key type"_s); - return {}; - } - default: { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Sign not supported for this key type"_s); - return {}; - } - } -} - -JSC_DEFINE_HOST_FUNCTION(KeyObject__Verify, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - ncrypto::ClearErrorOnReturn clearErrorOnReturn; - auto count = callFrame->argumentCount(); - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - - if (count < 4) { - JSC::throwTypeError(globalObject, scope, "verify requires 4 arguments"_s); - return {}; - } - - auto* key = jsDynamicCast(callFrame->argument(0)); - if (!key) { - // No JSCryptoKey instance - JSC::throwTypeError(globalObject, scope, "expected CryptoKey as first argument"_s); - return {}; - } - JSValue bufferArg = callFrame->uncheckedArgument(1); - auto buffer = KeyObject__GetBuffer(bufferArg); - if (buffer.hasException()) { - JSC::throwTypeError(globalObject, scope, "expected data to be Buffer or array-like object as second argument"_s); - return {}; - } - auto vectorData = buffer.releaseReturnValue(); - - JSValue signatureBufferArg = callFrame->uncheckedArgument(2); - auto signatureBuffer = KeyObject__GetBuffer(signatureBufferArg); - if (signatureBuffer.hasException()) { - JSC::throwTypeError(globalObject, scope, "expected signature to be Buffer or array-like object as second argument"_s); - return {}; - } - auto signatureData = signatureBuffer.releaseReturnValue(); - - auto& wrapped = key->wrapped(); - auto id = wrapped.keyClass(); - - auto hash = WebCore::CryptoAlgorithmIdentifier::SHA_256; - auto customHash = false; - - auto algorithm = callFrame->argument(3); - if (!algorithm.isUndefinedOrNull() && !algorithm.isEmpty()) { - customHash = true; - if (!algorithm.isString()) { - JSC::throwTypeError(globalObject, scope, "algorithm is expected to be a string"_s); - return {}; - } - auto algorithm_str = algorithm.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - - auto identifier = CryptoAlgorithmRegistry::singleton().identifier(algorithm_str); - if (UNLIKELY(!identifier)) { - JSC::throwTypeError(globalObject, scope, "digest not allowed"_s); - return {}; - } - - switch (*identifier) { - 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: { - - hash = *identifier; - break; - } - default: { - JSC::throwTypeError(globalObject, scope, "digest not allowed"_s); - return {}; - } - } - } - - switch (id) { - case CryptoKeyClass::HMAC: { - const auto& hmac = downcast(wrapped); - auto result = (customHash) ? WebCore::CryptoAlgorithmHMAC::platformVerifyWithAlgorithm(hmac, hash, signatureData, vectorData) : WebCore::CryptoAlgorithmHMAC::platformVerify(hmac, signatureData, vectorData); - if (result.hasException()) { - Exception exception = result.releaseException(); - if (exception.code() == WebCore::ExceptionCode::OperationError) { - return JSValue::encode(jsBoolean(false)); - } - WebCore::propagateException(*globalObject, scope, WTFMove(exception)); - return JSC::JSValue::encode(JSC::JSValue {}); - } - return JSC::JSValue::encode(jsBoolean(result.releaseReturnValue())); - } - case CryptoKeyClass::OKP: { - const auto& okpKey = downcast(wrapped); - auto result = WebCore::CryptoAlgorithmEd25519::platformVerify(okpKey, signatureData, vectorData); - if (result.hasException()) { - Exception exception = result.releaseException(); - if (exception.code() == WebCore::ExceptionCode::OperationError) { - return JSValue::encode(jsBoolean(false)); - } - WebCore::propagateException(*globalObject, scope, WTFMove(exception)); - return JSC::JSValue::encode(JSC::JSValue {}); - } - return JSC::JSValue::encode(jsBoolean(result.releaseReturnValue())); - } - case CryptoKeyClass::EC: { - const auto& ec = downcast(wrapped); - CryptoAlgorithmEcdsaParams params; - params.identifier = CryptoAlgorithmIdentifier::ECDSA; - params.hashIdentifier = hash; - params.encoding = CryptoAlgorithmECDSAEncoding::DER; - - if (count > 4) { - auto encoding = callFrame->argument(4); - if (!encoding.isUndefinedOrNull() && !encoding.isEmpty()) { - if (!encoding.isString()) { - JSC::throwTypeError(globalObject, scope, "dsaEncoding is expected to be a string"_s); - return {}; - } - auto encoding_str = encoding.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - - if (encoding_str == "ieee-p1363"_s) { - params.encoding = CryptoAlgorithmECDSAEncoding::IeeeP1363; - } else if (encoding_str == "der"_s) { - params.encoding = CryptoAlgorithmECDSAEncoding::DER; - } else { - JSC::throwTypeError(globalObject, scope, "invalid dsaEncoding"_s); - return {}; - } - } - } - auto result = WebCore::CryptoAlgorithmECDSA::platformVerify(params, ec, signatureData, vectorData); - if (result.hasException()) { - Exception exception = result.releaseException(); - if (exception.code() == WebCore::ExceptionCode::OperationError) { - return JSValue::encode(jsBoolean(false)); - } - WebCore::propagateException(*globalObject, scope, WTFMove(exception)); - return JSC::JSValue::encode(JSC::JSValue {}); - } - return JSC::JSValue::encode(jsBoolean(result.releaseReturnValue())); - } - case CryptoKeyClass::RSA: { - const auto& rsa = downcast(wrapped); - CryptoAlgorithmIdentifier restrict_hash; - bool isRestrictedToHash = rsa.isRestrictedToHash(restrict_hash); - if (isRestrictedToHash && hash != restrict_hash) { - JSC::throwTypeError(globalObject, scope, "digest not allowed"_s); - return {}; - } - switch (rsa.algorithmIdentifier()) { - case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: { - auto result = (customHash) ? WebCore::CryptoAlgorithmRSASSA_PKCS1_v1_5::platformVerifyWithAlgorithm(rsa, hash, signatureData, vectorData) : CryptoAlgorithmRSASSA_PKCS1_v1_5::platformVerify(rsa, signatureData, vectorData); - if (result.hasException()) { - Exception exception = result.releaseException(); - if (exception.code() == WebCore::ExceptionCode::OperationError) { - return JSValue::encode(jsBoolean(false)); - } - WebCore::propagateException(*globalObject, scope, WTFMove(exception)); - return JSC::JSValue::encode(JSC::JSValue {}); - } - return JSC::JSValue::encode(jsBoolean(result.releaseReturnValue())); - } - case CryptoAlgorithmIdentifier::RSA_PSS: { - CryptoAlgorithmRsaPssParams params; - params.padding = RSA_PKCS1_PADDING; - if (count > 5) { - - auto padding = callFrame->argument(5); - if (!padding.isUndefinedOrNull() && !padding.isEmpty()) { - if (!padding.isNumber()) { - JSC::throwTypeError(globalObject, scope, "padding is expected to be a number"_s); - return {}; - } - params.padding = padding.toUInt32(globalObject); - } - // requires saltLength - if (params.padding == RSA_PKCS1_PSS_PADDING) { - if (count <= 6) { - JSC::throwTypeError(globalObject, scope, "saltLength is expected to be a number"_s); - return {}; - } - - auto saltLength = callFrame->argument(6); - if (saltLength.isUndefinedOrNull() || saltLength.isEmpty() || !saltLength.isNumber()) { - JSC::throwTypeError(globalObject, scope, "saltLength is expected to be a number"_s); - return {}; - } - params.saltLength = saltLength.toUInt32(globalObject); - } else if (count > 6) { - auto saltLength = callFrame->argument(6); - if (!saltLength.isUndefinedOrNull() && !saltLength.isEmpty() && !saltLength.isNumber()) { - JSC::throwTypeError(globalObject, scope, "saltLength is expected to be a number"_s); - return {}; - } - params.saltLength = saltLength.toUInt32(globalObject); - params.padding = RSA_PKCS1_PSS_PADDING; // if saltLength is provided, padding must be RSA_PKCS1_PSS_PADDING - } - } - params.identifier = CryptoAlgorithmIdentifier::RSA_PSS; - auto result = (customHash) ? WebCore::CryptoAlgorithmRSA_PSS::platformVerifyWithAlgorithm(params, hash, rsa, signatureData, vectorData) : CryptoAlgorithmRSA_PSS::platformVerify(params, rsa, signatureData, vectorData); - if (result.hasException()) { - Exception exception = result.releaseException(); - if (exception.code() == WebCore::ExceptionCode::OperationError) { - return JSValue::encode(jsBoolean(false)); - } - WebCore::propagateException(*globalObject, scope, WTFMove(exception)); - return JSC::JSValue::encode(JSC::JSValue {}); - } - return JSC::JSValue::encode(jsBoolean(result.releaseReturnValue())); - } - default: { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Verify not supported for RSA key type"_s); - return {}; - } - } - } - case CryptoKeyClass::AES: { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Verify not supported for AES key type"_s); - return {}; - } - case CryptoKeyClass::Raw: { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Verify not supported for Raw key type"_s); - return {}; - } - default: { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Verify not supported for this key type"_s); - return {}; - } - } -} - -JSC_DEFINE_HOST_FUNCTION(KeyObject__Exports, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - ncrypto::ClearErrorOnReturn clearErrorOnReturn; - auto count = callFrame->argumentCount(); - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - - if (count < 2) { - JSC::throwTypeError(globalObject, scope, "exports requires 2 arguments"_s); - return {}; - } - - auto* key = jsDynamicCast(callFrame->argument(0)); - if (!key) { - // No JSCryptoKey instance - JSC::throwTypeError(globalObject, scope, "expected CryptoKey as first argument"_s); - return {}; - } - - auto& wrapped = key->wrapped(); - auto key_type = wrapped.type(); - auto id = wrapped.keyClass(); - if (auto* options = jsDynamicCast(callFrame->argument(1))) { - JSValue formatJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "format"_s))); - JSValue typeJSValue = options->getIfPropertyExists(globalObject, PropertyName(vm.propertyNames->type)); - JSValue passphraseJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "passphrase"_s))); - KeyPassphrase passphrase(passphraseJSValue, globalObject, scope); - RETURN_IF_EXCEPTION(scope, {}); - if (formatJSValue.isUndefinedOrNull() || formatJSValue.isEmpty()) { - JSC::throwTypeError(globalObject, scope, "format is expected to be a string"_s); - return {}; - } - - auto string = formatJSValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - if (string == "jwk"_s && passphrase.hasPassphrase()) { - JSC::throwTypeError(globalObject, scope, "encryption is not supported for jwk format"_s); - return {}; - } - - switch (id) { - case CryptoKeyClass::HMAC: { - const auto& hmac = downcast(wrapped); - if (string == "buffer"_s) { - auto keyData = hmac.key(); - auto* buffer = createBuffer(globalObject, keyData); - - return JSC::JSValue::encode(buffer); - } else if (string == "jwk"_s) { - const JsonWebKey& jwkValue = hmac.exportJwk(); - Zig::GlobalObject* domGlobalObject = reinterpret_cast(globalObject); - return JSC::JSValue::encode(WebCore::convertDictionaryToJS(*globalObject, *domGlobalObject, jwkValue, true)); - } - break; - } - case CryptoKeyClass::AES: { - const auto& aes = downcast(wrapped); - if (string == "buffer"_s) { - auto keyData = aes.key(); - auto* buffer = createBuffer(globalObject, keyData); - - return JSC::JSValue::encode(buffer); - } else if (string == "jwk"_s) { - const JsonWebKey& jwkValue = aes.exportJwk(); - Zig::GlobalObject* domGlobalObject = reinterpret_cast(globalObject); - return JSC::JSValue::encode(WebCore::convertDictionaryToJS(*globalObject, *domGlobalObject, jwkValue, true)); - } - break; - } - case CryptoKeyClass::RSA: { - const auto& rsa = downcast(wrapped); - if (string == "jwk"_s) { - if (rsa.algorithmIdentifier() == CryptoAlgorithmIdentifier::RSA_PSS) { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE: encryption is not supported for jwk format"_s); - return {}; - } - const JsonWebKey& jwkValue = rsa.exportJwk(); - Zig::GlobalObject* domGlobalObject = reinterpret_cast(globalObject); - return JSC::JSValue::encode(WebCore::convertDictionaryToJS(*globalObject, *domGlobalObject, jwkValue, true)); - } else { - WTF::String type = "pkcs1"_s; - - if (!typeJSValue.isUndefinedOrNull() && !typeJSValue.isEmpty()) { - if (!typeJSValue.isString()) { - JSC::throwTypeError(globalObject, scope, "type must be a string"_s); - return {}; - } - type = typeJSValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - } - if (type == "pkcs1"_s) { - if (rsa.algorithmIdentifier() == CryptoAlgorithmIdentifier::RSA_PSS) { - JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE: encryption is not supported for jwk format"_s); - return {}; - } - } - - auto* bio = BIO_new(BIO_s_mem()); - auto* rsaKey = rsa.platformKey(); - auto* rsa_ptr = EVP_PKEY_get0_RSA(rsaKey); - - if (key_type == CryptoKeyType::Public) { - if (string == "pem"_s) { - if (type == "pkcs1"_s) { - if (PEM_write_bio_RSAPublicKey(bio, rsa_ptr) != 1) { - JSC::throwTypeError(globalObject, scope, "Failed to write public key"_s); - BIO_free(bio); - return {}; - } - } else if (type == "spki"_s) { - if (PEM_write_bio_PUBKEY(bio, rsaKey) != 1) { - JSC::throwTypeError(globalObject, scope, "Failed to write public key"_s); - BIO_free(bio); - return {}; - } - } else { - JSC::throwTypeError(globalObject, scope, "type should be 'pkcs1' or 'spki'"_s); - BIO_free(bio); - return {}; - } - - } else if (string == "der"_s) { - if (type == "pkcs1"_s) { - if (i2d_RSAPublicKey_bio(bio, rsa_ptr) != 1) { - JSC::throwTypeError(globalObject, scope, "Failed to write public key"_s); - BIO_free(bio); - return {}; - } - } else if (type == "spki"_s) { - if (i2d_PUBKEY_bio(bio, rsaKey) != 1) { - JSC::throwTypeError(globalObject, scope, "Failed to write public key"_s); - BIO_free(bio); - return {}; - } - } else { - JSC::throwTypeError(globalObject, scope, "type should be 'pkcs1' or 'spki'"_s); - BIO_free(bio); - return {}; - } - } else { - JSC::throwTypeError(globalObject, scope, "format expected to be 'der', 'pem' or 'jwk'"_s); - BIO_free(bio); - return {}; - } - } else { - JSValue cipherJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "cipher"_s))); - - const EVP_CIPHER* cipher = nullptr; - if (!cipherJSValue.isUndefinedOrNull() && !cipherJSValue.isEmpty() && cipherJSValue.isString()) { - auto cipher_wtfstr = cipherJSValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - if (!cipher_wtfstr.isNull()) { - auto cipherOrError = cipher_wtfstr.tryGetUTF8(); - if (!cipherOrError.has_value()) { - JSC::throwTypeError(globalObject, scope, "invalid cipher name"_s); - BIO_free(bio); - return {}; - } else { - auto value = cipherOrError.value(); - auto cipher_str = value.data(); - if (cipher_str != nullptr) { - cipher = EVP_get_cipherbyname(cipher_str); - } - } - } - } - if (passphrase.hasPassphrase()) { - if (!cipher) { - JSC::throwTypeError(globalObject, scope, "cipher is required when passphrase is specified"_s); - BIO_free(bio); - return {}; - } - } - - if (string == "pem"_s) { - if (type == "pkcs1"_s) { - if (PEM_write_bio_RSAPrivateKey(bio, rsa_ptr, cipher, (unsigned char*)passphrase.data(), passphrase.length(), nullptr, nullptr) != 1) { - JSC::throwTypeError(globalObject, scope, "Failed to write private key"_s); - BIO_free(bio); - return {}; - } - } else if (type == "pkcs8"_s) { - if (PEM_write_bio_PKCS8PrivateKey(bio, rsaKey, cipher, passphrase.data(), passphrase.length(), nullptr, nullptr) != 1) { - JSC::throwTypeError(globalObject, scope, "Failed to write private key"_s); - BIO_free(bio); - return {}; - } - } else { - JSC::throwTypeError(globalObject, scope, "type should be 'pkcs1' or 'pkcs8'"_s); - BIO_free(bio); - return {}; - } - } else if (string == "der"_s) { - if (type == "pkcs1"_s) { - if (i2d_RSAPrivateKey_bio(bio, rsa_ptr) != 1) { - JSC::throwTypeError(globalObject, scope, "Failed to write private key"_s); - BIO_free(bio); - return {}; - } - } else if (type == "pkcs8"_s) { - if (i2d_PKCS8PrivateKey_bio(bio, rsaKey, cipher, passphrase.data(), passphrase.length(), nullptr, nullptr) != 1) { - JSC::throwTypeError(globalObject, scope, "Failed to write private key"_s); - BIO_free(bio); - return {}; - } - } else { - JSC::throwTypeError(globalObject, scope, "type should be 'pkcs1' or 'pkcs8'"_s); - BIO_free(bio); - return {}; - } - } else { - JSC::throwTypeError(globalObject, scope, "format expected to be 'der', 'pem' or 'jwk'"_s); - BIO_free(bio); - return {}; - } - } - - BUF_MEM* bptr = nullptr; - BIO_get_mem_ptr(bio, &bptr); - auto length = bptr->length; - if (string == "pem"_s) { - auto str = WTF::String::fromUTF8(std::span { bptr->data, length }); - return JSValue::encode(JSC::jsString(vm, str)); - } - - auto* buffer = createBuffer(globalObject, bptr->data, length); - - BIO_free(bio); - return JSC::JSValue::encode(buffer); - } - } - case CryptoKeyClass::EC: { - const auto& ec = downcast(wrapped); - if (string == "jwk"_s) { - auto result = ec.exportJwk(); - if (result.hasException()) { - WebCore::propagateException(*globalObject, scope, result.releaseException()); - return JSC::JSValue::encode(JSC::JSValue {}); - } - const JsonWebKey& jwkValue = result.releaseReturnValue(); - Zig::GlobalObject* domGlobalObject = reinterpret_cast(globalObject); - return JSC::JSValue::encode(WebCore::convertDictionaryToJS(*globalObject, *domGlobalObject, jwkValue, true)); - } else { - WTF::String type = "spki"_s; - if (!typeJSValue.isUndefinedOrNull() && !typeJSValue.isEmpty()) { - if (!typeJSValue.isString()) { - JSC::throwTypeError(globalObject, scope, "type must be a string"_s); - return {}; - } - type = typeJSValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - } - - auto* bio = BIO_new(BIO_s_mem()); - auto* ecKey = ec.platformKey(); - auto* ec_ptr = EVP_PKEY_get1_EC_KEY(ecKey); - - if (key_type == CryptoKeyType::Public) { - if (string == "pem"_s) { - if (type == "spki"_s) { - if (PEM_write_bio_PUBKEY(bio, ecKey) != 1) { - JSC::throwTypeError(globalObject, scope, "Failed to write public key"_s); - BIO_free(bio); - return {}; - } - } else { - JSC::throwTypeError(globalObject, scope, "type should be 'spki'"_s); - BIO_free(bio); - return {}; - } - - } else if (string == "der"_s) { - if (type == "spki"_s) { - if (i2d_PUBKEY_bio(bio, ecKey) != 1) { - JSC::throwTypeError(globalObject, scope, "Failed to write public key"_s); - BIO_free(bio); - return {}; - } - } else { - JSC::throwTypeError(globalObject, scope, "type should be 'spki'"_s); - BIO_free(bio); - return {}; - } - } else { - JSC::throwTypeError(globalObject, scope, "format expected to be 'der', 'pem' or 'jwk'"_s); - BIO_free(bio); - return {}; - } - } else { - JSValue cipherJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "cipher"_s))); - - const EVP_CIPHER* cipher = nullptr; - if (!cipherJSValue.isUndefinedOrNull() && !cipherJSValue.isEmpty()) { - auto cipher_wtfstr = cipherJSValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - if (!cipher_wtfstr.isNull()) { - auto cipherOrError = cipher_wtfstr.tryGetUTF8(); - if (!cipherOrError.has_value()) { - JSC::throwTypeError(globalObject, scope, "invalid cipher name"_s); - BIO_free(bio); - return {}; - } else { - auto value = cipherOrError.value(); - auto cipher_str = value.data(); - if (cipher_str != nullptr) { - cipher = EVP_get_cipherbyname(cipher_str); - } - } - } - } - - if (passphrase.hasPassphrase()) { - - if (!cipher) { - JSC::throwTypeError(globalObject, scope, "cipher is required when passphrase is specified"_s); - BIO_free(bio); - return {}; - } - } - - if (string == "pem"_s) { - if (type == "sec1"_s) { - if (PEM_write_bio_ECPrivateKey(bio, ec_ptr, cipher, (unsigned char*)passphrase.data(), passphrase.length(), nullptr, nullptr) != 1) { - JSC::throwTypeError(globalObject, scope, "Failed to write private key"_s); - BIO_free(bio); - return {}; - } - } else if (type == "pkcs8"_s) { - if (PEM_write_bio_PKCS8PrivateKey(bio, ecKey, cipher, passphrase.data(), passphrase.length(), nullptr, nullptr) != 1) { - JSC::throwTypeError(globalObject, scope, "Failed to write private key"_s); - BIO_free(bio); - return {}; - } - } else { - JSC::throwTypeError(globalObject, scope, "type should be 'sec1' or 'pkcs8'"_s); - BIO_free(bio); - return {}; - } - } else if (string == "der"_s) { - if (type == "sec1"_s) { - if (i2d_ECPrivateKey_bio(bio, ec_ptr) != 1) { - JSC::throwTypeError(globalObject, scope, "Failed to write private key"_s); - BIO_free(bio); - return {}; - } - } else if (type == "pkcs8"_s) { - if (i2d_PKCS8PrivateKey_bio(bio, ecKey, cipher, passphrase.data(), passphrase.length(), nullptr, nullptr) != 1) { - JSC::throwTypeError(globalObject, scope, "Failed to write private key"_s); - BIO_free(bio); - return {}; - } - } else { - JSC::throwTypeError(globalObject, scope, "type should be 'sec1' or 'pkcs8'"_s); - BIO_free(bio); - return {}; - } - } else { - JSC::throwTypeError(globalObject, scope, "format expected to be 'der', 'pem' or 'jwk'"_s); - BIO_free(bio); - return {}; - } - } - - BUF_MEM* bptr = nullptr; - BIO_get_mem_ptr(bio, &bptr); - auto length = bptr->length; - if (string == "pem"_s) { - auto str = WTF::String::fromUTF8(std::span { bptr->data, length }); - return JSValue::encode(JSC::jsString(vm, str)); - } - - auto* buffer = createBuffer(globalObject, bptr->data, length); - - BIO_free(bio); - return JSC::JSValue::encode(buffer); - } - } - case CryptoKeyClass::OKP: { - const auto& okpKey = downcast(wrapped); - if (string == "jwk"_s) { - auto result = okpKey.exportJwk(); - if (result.hasException()) { - WebCore::propagateException(*globalObject, scope, result.releaseException()); - return JSC::JSValue::encode(JSC::JSValue {}); - } - const JsonWebKey& jwkValue = result.releaseReturnValue(); - Zig::GlobalObject* domGlobalObject = reinterpret_cast(globalObject); - return JSC::JSValue::encode(WebCore::convertDictionaryToJS(*globalObject, *domGlobalObject, jwkValue, true)); - } else { - WTF::String type = "pkcs8"_s; - if (!typeJSValue.isUndefinedOrNull() && !typeJSValue.isEmpty()) { - if (!typeJSValue.isString()) { - JSC::throwTypeError(globalObject, scope, "type must be a string"_s); - return {}; - } - type = typeJSValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - } - - auto keyData = okpKey.exportKey(); - auto* bio = BIO_new(BIO_s_mem()); - - EVP_PKEY* evpKey; - // TODO: CHECK THIS WHEN X488 AND ED448 ARE ADDED - if (okpKey.type() == CryptoKeyType::Private) { - evpKey = EVP_PKEY_new_raw_private_key(okpKey.namedCurve() == CryptoKeyOKP::NamedCurve::X25519 ? EVP_PKEY_X25519 : EVP_PKEY_ED25519, nullptr, keyData.data(), keyData.size()); - JSValue cipherJSValue = options->getIfPropertyExists(globalObject, PropertyName(Identifier::fromString(vm, "cipher"_s))); - - const EVP_CIPHER* cipher = nullptr; - if (!cipherJSValue.isUndefinedOrNull() && !cipherJSValue.isEmpty() && cipherJSValue.isString()) { - auto cipher_wtfstr = cipherJSValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - if (!cipher_wtfstr.isNull()) { - auto cipherOrError = cipher_wtfstr.tryGetUTF8(); - if (!cipherOrError.has_value()) { - JSC::throwTypeError(globalObject, scope, "invalid cipher name"_s); - BIO_free(bio); - EVP_PKEY_free(evpKey); - return {}; - } else { - auto value = cipherOrError.value(); - auto cipher_str = value.data(); - if (cipher_str != nullptr) { - cipher = EVP_get_cipherbyname(cipher_str); - } - } - } - } - - if (passphrase.hasPassphrase()) { - if (!cipher) { - JSC::throwTypeError(globalObject, scope, "cipher is required when passphrase is specified"_s); - BIO_free(bio); - return {}; - } - } - - if (string == "pem"_s) { - if (type == "pkcs8"_s) { - if (PEM_write_bio_PKCS8PrivateKey(bio, evpKey, cipher, passphrase.data(), passphrase.length(), nullptr, nullptr) != 1) { - JSC::throwTypeError(globalObject, scope, "Failed to write private key"_s); - BIO_free(bio); - EVP_PKEY_free(evpKey); - return {}; - } - } else { - JSC::throwTypeError(globalObject, scope, "type should be 'pkcs8'"_s); - BIO_free(bio); - EVP_PKEY_free(evpKey); - return {}; - } - } else if (string == "der"_s) { - if (type == "pkcs8"_s) { - if (i2d_PKCS8PrivateKey_bio(bio, evpKey, cipher, passphrase.data(), passphrase.length(), nullptr, nullptr) != 1) { - JSC::throwTypeError(globalObject, scope, "Failed to write private key"_s); - BIO_free(bio); - EVP_PKEY_free(evpKey); - return {}; - } - } else { - JSC::throwTypeError(globalObject, scope, "type should be 'pkcs8'"_s); - BIO_free(bio); - EVP_PKEY_free(evpKey); - return {}; - } - } else { - JSC::throwTypeError(globalObject, scope, "format expected to be 'der', 'pem' or 'jwk'"_s); - BIO_free(bio); - EVP_PKEY_free(evpKey); - return {}; - } - } else { - evpKey = EVP_PKEY_new_raw_public_key(okpKey.namedCurve() == CryptoKeyOKP::NamedCurve::X25519 ? EVP_PKEY_X25519 : EVP_PKEY_ED25519, nullptr, keyData.data(), keyData.size()); - if (string == "pem"_s) { - if (type == "spki"_s) { - if (PEM_write_bio_PUBKEY(bio, evpKey) != 1) { - JSC::throwTypeError(globalObject, scope, "Failed to write public key"_s); - BIO_free(bio); - EVP_PKEY_free(evpKey); - return {}; - } - } else { - JSC::throwTypeError(globalObject, scope, "type should be 'spki'"_s); - BIO_free(bio); - EVP_PKEY_free(evpKey); - return {}; - } - - } else if (string == "der"_s) { - if (type == "spki"_s) { - if (i2d_PUBKEY_bio(bio, evpKey) != 1) { - JSC::throwTypeError(globalObject, scope, "Failed to write public key"_s); - BIO_free(bio); - EVP_PKEY_free(evpKey); - return {}; - } - } else { - JSC::throwTypeError(globalObject, scope, "type should be 'spki'"_s); - BIO_free(bio); - EVP_PKEY_free(evpKey); - return {}; - } - } else { - JSC::throwTypeError(globalObject, scope, "format expected to be 'der', 'pem' or 'jwk'"_s); - BIO_free(bio); - EVP_PKEY_free(evpKey); - return {}; - } - } - - BUF_MEM* bptr = nullptr; - BIO_get_mem_ptr(bio, &bptr); - auto length = bptr->length; - if (string == "pem"_s) { - auto str = WTF::String::fromUTF8(std::span { bptr->data, length }); - EVP_PKEY_free(evpKey); - return JSValue::encode(JSC::jsString(vm, str)); - } - - auto* buffer = WebCore::createBuffer(globalObject, std::span { bptr->data, length }); - - BIO_free(bio); - EVP_PKEY_free(evpKey); - return JSC::JSValue::encode(buffer); - } - } - case CryptoKeyClass::Raw: { - const auto& raw = downcast(wrapped); - if (string == "buffer"_s) { - auto keyData = raw.key(); - return JSC::JSValue::encode(WebCore::createBuffer(globalObject, keyData)); - } - - JSC::throwTypeError(globalObject, scope, "format is expected to be 'buffer'"_s); - return {}; - } - default: { - JSC::throwTypeError(globalObject, scope, "Invalid Operation"_s); - return {}; - } - } - JSC::throwTypeError(globalObject, scope, "format is expected to be 'buffer' or 'jwk'"_s); - return {}; - } else { - JSC::throwTypeError(globalObject, scope, "expected options to be a object"_s); - return {}; - } -} - -static char* bignum_to_string(const BIGNUM* bn) -{ - char *tmp, *ret; - size_t len; - - // Display large numbers in hex and small numbers in decimal. Converting to - // decimal takes quadratic time and is no more useful than hex for large - // numbers. - if (BN_num_bits(bn) < 32) { - return BN_bn2dec(bn); - } - - tmp = BN_bn2hex(bn); - if (tmp == NULL) { - return NULL; - } - - len = strlen(tmp) + 3; - ret = (char*)OPENSSL_malloc(len); - if (ret == NULL) { - OPENSSL_free(tmp); - return NULL; - } - - // Prepend "0x", but place it after the "-" if negative. - if (tmp[0] == '-') { - OPENSSL_strlcpy(ret, "-0x", len); - OPENSSL_strlcat(ret, tmp + 1, len); - } else { - OPENSSL_strlcpy(ret, "0x", len); - OPENSSL_strlcat(ret, tmp, len); - } - OPENSSL_free(tmp); - return ret; -} - -JSC_DEFINE_HOST_FUNCTION(KeyObject_AsymmetricKeyDetails, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - ncrypto::ClearErrorOnReturn clearErrorOnReturn; - if (auto* key = jsDynamicCast(callFrame->argument(0))) { - auto id = key->wrapped().algorithmIdentifier(); - auto& vm = JSC::getVM(lexicalGlobalObject); - switch (id) { - case CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5: - case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: - case CryptoAlgorithmIdentifier::RSA_OAEP: - case CryptoAlgorithmIdentifier::RSA_PSS: { - auto* obj = JSC::constructEmptyObject(lexicalGlobalObject); - - auto& wrapped = key->wrapped(); - const auto& rsa = downcast(wrapped); - auto* platformKey = rsa.platformKey(); - const BIGNUM* e; // Public Exponent - const BIGNUM* n; // Modulus - const RSA* rsa_key = EVP_PKEY_get0_RSA(platformKey); - if (rsa_key == nullptr) { - return JSValue::encode(JSC::jsUndefined()); - } - - RSA_get0_key(rsa_key, &n, &e, nullptr); - - auto modulus_length = BN_num_bits(n); - obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "modulusLength"_s)), jsNumber(modulus_length), 0); - - auto str = bignum_to_string(e); - obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "publicExponent"_s)), JSC::JSBigInt::stringToBigInt(lexicalGlobalObject, StringView::fromLatin1(str)), 0); - OPENSSL_free(str); - - if (id == CryptoAlgorithmIdentifier::RSA_PSS) { - // Due to the way ASN.1 encoding works, default values are omitted when - // encoding the data structure. However, there are also RSA-PSS keys for - // which no parameters are set. In that case, the ASN.1 RSASSA-PSS-params - // sequence will be missing entirely and RSA_get0_pss_params will return - // nullptr. If parameters are present but all parameters are set to their - // default values, an empty sequence will be stored in the ASN.1 structure. - // In that case, RSA_get0_pss_params does not return nullptr but all fields - // of the returned RSA_PSS_PARAMS will be set to nullptr. - - auto* params = RSA_get0_pss_params(rsa_key); - if (params != nullptr) { - int hash_nid = NID_sha1; - int mgf_nid = NID_mgf1; - int mgf1_hash_nid = NID_sha1; - int64_t salt_length = 20; - - if (params->hashAlgorithm != nullptr) { - hash_nid = OBJ_obj2nid(params->hashAlgorithm->algorithm); - } - auto* hash_srt = OBJ_nid2ln(hash_nid); - obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "hashAlgorithm"_s)), Bun::toJS(lexicalGlobalObject, Bun::toString(hash_srt, strlen(hash_srt))), 0); - if (params->maskGenAlgorithm != nullptr) { - mgf_nid = OBJ_obj2nid(params->maskGenAlgorithm->algorithm); - if (mgf_nid == NID_mgf1) { - mgf1_hash_nid = OBJ_obj2nid(params->maskHash->algorithm); - } - } - - // If, for some reason, the MGF is not MGF1, then the MGF1 hash function - // is intentionally not added to the object. - if (mgf_nid == NID_mgf1) { - auto* mgf1_hash_srt = OBJ_nid2ln(mgf1_hash_nid); - obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "mgf1HashAlgorithm"_s)), Bun::toJS(lexicalGlobalObject, Bun::toString(mgf1_hash_srt, strlen(mgf1_hash_srt))), 0); - } - - if (params->saltLength != nullptr) { - if (ASN1_INTEGER_get_int64(&salt_length, params->saltLength) != 1) { - auto scope = DECLARE_THROW_SCOPE(vm); - throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "Failed to get saltLenght"_s)); - return {}; - } - } - obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "saltLength"_s)), jsNumber(salt_length), 0); - } - } - return JSC::JSValue::encode(obj); - } - case CryptoAlgorithmIdentifier::ECDSA: - case CryptoAlgorithmIdentifier::ECDH: { - auto* obj = JSC::constructEmptyObject(lexicalGlobalObject, lexicalGlobalObject->objectPrototype(), 1); - - auto& wrapped = key->wrapped(); - const auto& ec = downcast(wrapped); - static const NeverDestroyed values[] = { - MAKE_STATIC_STRING_IMPL("prime256v1"), - MAKE_STATIC_STRING_IMPL("secp384r1"), - MAKE_STATIC_STRING_IMPL("secp521r1"), - }; - - WTF::String named_curve; - switch (ec.namedCurve()) { - case CryptoKeyEC::NamedCurve::P256: - named_curve = values[0]; - break; - case CryptoKeyEC::NamedCurve::P384: - named_curve = values[1]; - break; - case CryptoKeyEC::NamedCurve::P521: - named_curve = values[2]; - break; - default: - ASSERT_NOT_REACHED(); - named_curve = WTF::emptyString(); - } - - obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "namedCurve"_s)), JSC::jsString(vm, named_curve), 0); - return JSC::JSValue::encode(obj); - } - case CryptoAlgorithmIdentifier::X25519: - case CryptoAlgorithmIdentifier::Ed25519: { - auto* obj = JSC::constructEmptyObject(lexicalGlobalObject, lexicalGlobalObject->objectPrototype(), 1); - auto& wrapped = key->wrapped(); - const auto& okp = downcast(wrapped); - auto* globalObject = defaultGlobalObject(lexicalGlobalObject); - auto& commonStrings = globalObject->commonStrings(); - JSString* namedCurveString = okp.namedCurve() == CryptoKeyOKP::NamedCurve::X25519 ? commonStrings.x25519String(lexicalGlobalObject) : commonStrings.ed25519String(lexicalGlobalObject); - obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "namedCurve"_s)), namedCurveString, 0); - return JSC::JSValue::encode(obj); - } - default: - return JSC::JSValue::encode(JSC::jsUndefined()); - } - } - return JSC::JSValue::encode(JSC::jsUndefined()); -} - -JSC_DEFINE_HOST_FUNCTION(KeyObject__generateKeyPairSync, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - ncrypto::ClearErrorOnReturn clearErrorOnReturn; - auto count = callFrame->argumentCount(); - auto& vm = JSC::getVM(lexicalGlobalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - - if (count < 1) { - JSC::throwTypeError(lexicalGlobalObject, scope, "generateKeyPairSync requires 1 arguments"_s); - return {}; - } - - auto type = callFrame->argument(0); - if (type.isUndefinedOrNull() || type.isEmpty() || !type.isString()) { - JSC::throwTypeError(lexicalGlobalObject, scope, "type is expected to be a string"_s); - return {}; - } - auto type_str = type.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, {}); - - Zig::GlobalObject* zigGlobalObject = reinterpret_cast(lexicalGlobalObject); - auto* structure = zigGlobalObject->JSCryptoKeyStructure(); - if (type_str == "rsa"_s) { - if (count == 1) { - JSC::throwTypeError(lexicalGlobalObject, scope, "options.modulusLength are required for rsa"_s); - return {}; - } - auto* options = jsDynamicCast(callFrame->argument(1)); - if (options == nullptr) { - JSC::throwTypeError(lexicalGlobalObject, scope, "options is expected to be a object"_s); - return {}; - } - auto modulusLengthJS = options->getIfPropertyExists(lexicalGlobalObject, PropertyName(Identifier::fromString(vm, "modulusLength"_s))); - if (!modulusLengthJS.isNumber()) { - JSC::throwTypeError(lexicalGlobalObject, scope, "options.modulusLength is expected to be a number"_s); - return {}; - } - auto publicExponentJS = options->getIfPropertyExists(lexicalGlobalObject, PropertyName(Identifier::fromString(vm, "publicExponent"_s))); - uint32_t publicExponent = 0x10001; - if (publicExponentJS.isNumber()) { - publicExponent = publicExponentJS.toUInt32(lexicalGlobalObject); - } else if (!publicExponentJS.isUndefinedOrNull() && !publicExponentJS.isEmpty()) { - JSC::throwTypeError(lexicalGlobalObject, scope, "options.publicExponent is expected to be a number"_s); - return {}; - } - - uint8_t publicExponentArray[4]; - publicExponentArray[0] = (uint8_t)(publicExponent >> 24); - publicExponentArray[1] = (uint8_t)(publicExponent >> 16); - publicExponentArray[2] = (uint8_t)(publicExponent >> 8); - publicExponentArray[3] = (uint8_t)publicExponent; - - int modulusLength = modulusLengthJS.toUInt32(lexicalGlobalObject); - auto returnValue = JSC::JSValue {}; - auto keyPairCallback = [&](CryptoKeyPair&& pair) { - pair.publicKey->setUsagesBitmap(pair.publicKey->usagesBitmap() & CryptoKeyUsageVerify); - pair.privateKey->setUsagesBitmap(pair.privateKey->usagesBitmap() & CryptoKeyUsageSign); - - auto obj = JSC::constructEmptyObject(lexicalGlobalObject, lexicalGlobalObject->objectPrototype(), 2); - obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "publicKey"_s)), JSCryptoKey::create(structure, zigGlobalObject, pair.publicKey.releaseNonNull()), 0); - obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "privateKey"_s)), JSCryptoKey::create(structure, zigGlobalObject, pair.privateKey.releaseNonNull()), 0); - returnValue = obj; - }; - auto failureCallback = [&]() { - // TODO: include what error was thrown in the message - throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "Failed to generate key pair"_s)); - }; - // this is actually sync - CryptoKeyRSA::generatePair(CryptoAlgorithmIdentifier::RSA_OAEP, CryptoAlgorithmIdentifier::SHA_1, false, modulusLength, Vector(std::span { (uint8_t*)&publicExponentArray, 4 }), true, CryptoKeyUsageEncrypt | CryptoKeyUsageDecrypt, WTFMove(keyPairCallback), WTFMove(failureCallback), zigGlobalObject->scriptExecutionContext()); - return JSValue::encode(returnValue); - } - if (type_str == "rsa-pss"_s) { - if (count == 1) { - JSC::throwTypeError(lexicalGlobalObject, scope, "options.modulusLength are required for rsa"_s); - return {}; - } - auto* options = jsDynamicCast(callFrame->argument(1)); - if (options == nullptr) { - JSC::throwTypeError(lexicalGlobalObject, scope, "options is expected to be a object"_s); - return {}; - } - auto modulusLengthJS = options->getIfPropertyExists(lexicalGlobalObject, PropertyName(Identifier::fromString(vm, "modulusLength"_s))); - if (!modulusLengthJS.isNumber()) { - JSC::throwTypeError(lexicalGlobalObject, scope, "options.modulusLength is expected to be a number"_s); - return {}; - } - auto publicExponentJS = options->getIfPropertyExists(lexicalGlobalObject, PropertyName(Identifier::fromString(vm, "publicExponent"_s))); - uint32_t publicExponent = 0x10001; - if (publicExponentJS.isNumber()) { - publicExponent = publicExponentJS.toUInt32(lexicalGlobalObject); - } else if (!publicExponentJS.isUndefinedOrNull() && !publicExponentJS.isEmpty()) { - JSC::throwTypeError(lexicalGlobalObject, scope, "options.publicExponent is expected to be a number"_s); - return {}; - } - uint8_t publicExponentArray[4]; - publicExponentArray[0] = (uint8_t)(publicExponent >> 24); - publicExponentArray[1] = (uint8_t)(publicExponent >> 16); - publicExponentArray[2] = (uint8_t)(publicExponent >> 8); - publicExponentArray[3] = (uint8_t)publicExponent; - - int modulusLength = modulusLengthJS.toUInt32(lexicalGlobalObject); - auto returnValue = JSC::JSValue {}; - auto keyPairCallback = [&](CryptoKeyPair&& pair) { - pair.publicKey->setUsagesBitmap(pair.publicKey->usagesBitmap() & CryptoKeyUsageVerify); - pair.privateKey->setUsagesBitmap(pair.privateKey->usagesBitmap() & CryptoKeyUsageSign); - - auto obj = JSC::constructEmptyObject(lexicalGlobalObject, lexicalGlobalObject->objectPrototype(), 2); - obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "publicKey"_s)), JSCryptoKey::create(structure, zigGlobalObject, pair.publicKey.releaseNonNull()), 0); - obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "privateKey"_s)), JSCryptoKey::create(structure, zigGlobalObject, pair.privateKey.releaseNonNull()), 0); - returnValue = obj; - }; - - auto hashAlgoJS = options->getIfPropertyExists(lexicalGlobalObject, PropertyName(Identifier::fromString(vm, "hashAlgorithm"_s))); - auto hasHash = false; - auto hash = CryptoAlgorithmIdentifier::SHA_1; - if (!hashAlgoJS.isUndefinedOrNull() && !hashAlgoJS.isEmpty()) { - if (!hashAlgoJS.isString()) { - JSC::throwTypeError(lexicalGlobalObject, scope, "options.hashAlgorithm is expected to be a string"_s); - return {}; - } - hasHash = true; - auto hashAlgo = hashAlgoJS.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, {}); - - auto identifier = CryptoAlgorithmRegistry::singleton().identifier(hashAlgo); - if (UNLIKELY(!identifier)) { - JSC::throwTypeError(lexicalGlobalObject, scope, "options.hashAlgorithm is invalid"_s); - return {}; - } - - switch (*identifier) { - 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: { - - hash = *identifier; - break; - } - default: { - JSC::throwTypeError(lexicalGlobalObject, scope, "options.hashAlgorithm is invalid"_s); - return {}; - } - } - } - - // TODO: @cirospaciari is saltLength supposed to be used here? - // auto saltLengthJS = options->getIfPropertyExists(lexicalGlobalObject, PropertyName(Identifier::fromString(vm, "hashAlgorithm"_s))); - - auto failureCallback = [&]() { - // TODO: include what error was thrown in the message - throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "Failed to generate key pair"_s)); - }; - // this is actually sync - CryptoKeyRSA::generatePair(CryptoAlgorithmIdentifier::RSA_PSS, hash, hasHash, modulusLength, Vector(std::span { (uint8_t*)&publicExponentArray, 4 }), true, CryptoKeyUsageEncrypt | CryptoKeyUsageDecrypt, WTFMove(keyPairCallback), WTFMove(failureCallback), zigGlobalObject->scriptExecutionContext()); - return JSValue::encode(returnValue); - } else if (type_str == "ec"_s) { - if (count == 1) { - JSC::throwTypeError(lexicalGlobalObject, scope, "options.namedCurve is required for ec"_s); - return {}; - } - auto* options = jsDynamicCast(callFrame->argument(1)); - if (options == nullptr) { - JSC::throwTypeError(lexicalGlobalObject, scope, "options is expected to be a object"_s); - return {}; - } - auto namedCurveJS = options->getIfPropertyExists(lexicalGlobalObject, PropertyName(Identifier::fromString(vm, "namedCurve"_s))); - if (namedCurveJS.isUndefinedOrNull() || namedCurveJS.isEmpty() || !namedCurveJS.isString()) { - JSC::throwTypeError(lexicalGlobalObject, scope, "namedCurve is expected to be a string"_s); - return {}; - } - auto namedCurve = namedCurveJS.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, {}); - if (namedCurve == "P-384"_s || namedCurve == "p384"_s || namedCurve == "secp384r1"_s) { - namedCurve = "P-384"_s; - } else if (namedCurve == "P-256"_s || namedCurve == "p256"_s || namedCurve == "prime256v1"_s) { - namedCurve = "P-256"_s; - } else if (namedCurve == "P-521"_s || namedCurve == "p521"_s || namedCurve == "secp521r1"_s) { - namedCurve = "P-521"_s; - } else { - return Bun::ERR::CRYPTO_JWK_UNSUPPORTED_CURVE(scope, lexicalGlobalObject, namedCurve); - } - - auto result = CryptoKeyEC::generatePair(CryptoAlgorithmIdentifier::ECDSA, namedCurve, true, CryptoKeyUsageSign | CryptoKeyUsageVerify); - if (result.hasException()) { - WebCore::propagateException(*lexicalGlobalObject, scope, result.releaseException()); - return JSC::JSValue::encode(JSC::JSValue {}); - } - auto pair = result.releaseReturnValue(); - auto obj = JSC::constructEmptyObject(lexicalGlobalObject, lexicalGlobalObject->objectPrototype(), 2); - obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "publicKey"_s)), JSCryptoKey::create(structure, zigGlobalObject, pair.publicKey.releaseNonNull()), 0); - obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "privateKey"_s)), JSCryptoKey::create(structure, zigGlobalObject, pair.privateKey.releaseNonNull()), 0); - return JSValue::encode(obj); - } else if (type_str == "ed25519"_s) { - auto result = CryptoKeyOKP::generatePair(CryptoAlgorithmIdentifier::Ed25519, CryptoKeyOKP::NamedCurve::Ed25519, true, CryptoKeyUsageSign | CryptoKeyUsageVerify); - if (result.hasException()) { - WebCore::propagateException(*lexicalGlobalObject, scope, result.releaseException()); - return JSC::JSValue::encode(JSC::JSValue {}); - } - auto pair = result.releaseReturnValue(); - auto obj = JSC::constructEmptyObject(lexicalGlobalObject, lexicalGlobalObject->objectPrototype(), 2); - obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "publicKey"_s)), JSCryptoKey::create(structure, zigGlobalObject, pair.publicKey.releaseNonNull()), 0); - obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "privateKey"_s)), JSCryptoKey::create(structure, zigGlobalObject, pair.privateKey.releaseNonNull()), 0); - return JSValue::encode(obj); - } else if (type_str == "x25519"_s) { - auto result = CryptoKeyOKP::generatePair(CryptoAlgorithmIdentifier::X25519, CryptoKeyOKP::NamedCurve::X25519, true, CryptoKeyUsageDeriveKey | CryptoKeyUsageDeriveBits | CryptoKeyUsageSign | CryptoKeyUsageVerify); - if (result.hasException()) { - WebCore::propagateException(*lexicalGlobalObject, scope, result.releaseException()); - return JSC::JSValue::encode(JSC::JSValue {}); - } - auto pair = result.releaseReturnValue(); - auto obj = JSC::constructEmptyObject(lexicalGlobalObject, lexicalGlobalObject->objectPrototype(), 2); - obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "publicKey"_s)), JSCryptoKey::create(structure, zigGlobalObject, pair.publicKey.releaseNonNull()), 0); - obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "privateKey"_s)), JSCryptoKey::create(structure, zigGlobalObject, pair.privateKey.releaseNonNull()), 0); - return JSValue::encode(obj); - } else { - throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "algorithm should be 'rsa', 'rsa-pss', 'ec', 'x25519' or 'ed25519'"_s)); - return {}; - } - return JSValue::encode(JSC::jsUndefined()); -} -JSC_DEFINE_HOST_FUNCTION(KeyObject__generateKeySync, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - ncrypto::ClearErrorOnReturn clearErrorOnReturn; - auto count = callFrame->argumentCount(); - auto& vm = JSC::getVM(lexicalGlobalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - if (count < 2) { - JSC::throwTypeError(lexicalGlobalObject, scope, "generateKeySync requires 2 arguments"_s); - return {}; - } - - auto type = callFrame->argument(0); - if (type.isUndefinedOrNull() || type.isEmpty() || !type.isString()) { - JSC::throwTypeError(lexicalGlobalObject, scope, "type is expected to be a string"_s); - return {}; - } - - auto type_str = type.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, {}); - - if (type_str == "hmac"_s) { - Zig::GlobalObject* zigGlobalObject = reinterpret_cast(lexicalGlobalObject); - auto* structure = zigGlobalObject->JSCryptoKeyStructure(); - size_t lengthBits = 0; - auto length = callFrame->argument(1); - if (!length.isNumber()) { - JSC::throwTypeError(lexicalGlobalObject, scope, "length is expected to be a number"_s); - return {}; - } - lengthBits = length.toUInt32(lexicalGlobalObject); - auto result = CryptoKeyHMAC::generate(lengthBits, WebCore::CryptoAlgorithmIdentifier::HMAC, true, CryptoKeyUsageSign | CryptoKeyUsageVerify); - if (UNLIKELY(result == nullptr)) { - throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "Invalid length"_s)); - return {}; - } - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, result.releaseNonNull())); - } else if (type_str == "aes"_s) { - Zig::GlobalObject* zigGlobalObject = reinterpret_cast(lexicalGlobalObject); - auto* structure = zigGlobalObject->JSCryptoKeyStructure(); - size_t lengthBits = 0; - if (count > 1) { - auto length = callFrame->argument(1); - if (!length.isNumber()) { - JSC::throwTypeError(lexicalGlobalObject, scope, "length is expected to be a number"_s); - return {}; - } - lengthBits = length.toUInt32(lexicalGlobalObject); - } - - auto result = CryptoKeyAES::generate(WebCore::CryptoAlgorithmIdentifier::AES_CBC, lengthBits, true, CryptoKeyUsageSign | CryptoKeyUsageVerify); - if (UNLIKELY(result == nullptr)) { - throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "Invalid length"_s)); - return {}; - } - return JSC::JSValue::encode(JSCryptoKey::create(structure, zigGlobalObject, result.releaseNonNull())); - } else { - throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "algorithm should be 'aes' or 'hmac'"_s)); - return {}; - } -} - -JSC_DEFINE_HOST_FUNCTION(KeyObject__AsymmetricKeyType, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - ncrypto::ClearErrorOnReturn clearErrorOnReturn; - auto* globalObject = defaultGlobalObject(lexicalGlobalObject); - auto& commonStrings = globalObject->commonStrings(); - - // TODO: Look into DSA and DH - if (auto* key = jsDynamicCast(callFrame->argument(0))) { - auto id = key->wrapped().algorithmIdentifier(); - switch (id) { - case CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5: - case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: - case CryptoAlgorithmIdentifier::RSA_OAEP: - return JSC::JSValue::encode(commonStrings.rsaString(globalObject)); - case CryptoAlgorithmIdentifier::RSA_PSS: - return JSC::JSValue::encode(commonStrings.rsaPssString(globalObject)); - case CryptoAlgorithmIdentifier::ECDSA: - case CryptoAlgorithmIdentifier::ECDH: - return JSC::JSValue::encode(commonStrings.ecString(globalObject)); - case CryptoAlgorithmIdentifier::Ed25519: - case CryptoAlgorithmIdentifier::X25519: { - const auto& okpKey = downcast(key->wrapped()); - return JSC::JSValue::encode(okpKey.namedCurve() == CryptoKeyOKP::NamedCurve::X25519 ? commonStrings.x25519String(globalObject) : commonStrings.ed25519String(globalObject)); - } - default: - return JSC::JSValue::encode(JSC::jsUndefined()); - } - } - return JSC::JSValue::encode(JSC::jsUndefined()); -} - -static Vector GetRawKeyFromSecret(WebCore::CryptoKey& key) -{ - auto id = key.keyClass(); - switch (id) { - case CryptoKeyClass::HMAC: { - const auto& hmac = downcast(key); - return hmac.key(); - } - case CryptoKeyClass::AES: { - const auto& aes = downcast(key); - return aes.key(); - } - case CryptoKeyClass::Raw: { - const auto& raw = downcast(key); - return raw.key(); - } - default: { - Vector empty; - return empty; - } - } -} -AsymmetricKeyValue::~AsymmetricKeyValue() -{ - if (key && owned) { - EVP_PKEY_free(key); - } -} - -AsymmetricKeyValue::AsymmetricKeyValue(WebCore::CryptoKey& cryptoKey) -{ - auto id = cryptoKey.algorithmIdentifier(); - owned = false; - key = nullptr; - - switch (id) { - case CryptoAlgorithmIdentifier::RSAES_PKCS1_v1_5: - case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: - case CryptoAlgorithmIdentifier::RSA_OAEP: - case CryptoAlgorithmIdentifier::RSA_PSS: - key = downcast(cryptoKey).platformKey(); - break; - case CryptoAlgorithmIdentifier::ECDSA: - case CryptoAlgorithmIdentifier::ECDH: - key = downcast(cryptoKey).platformKey(); - break; - case CryptoAlgorithmIdentifier::X25519: - case CryptoAlgorithmIdentifier::Ed25519: { - const auto& okpKey = downcast(cryptoKey); - auto keyData = okpKey.exportKey(); - if (okpKey.type() == CryptoKeyType::Private) { - key = EVP_PKEY_new_raw_private_key(okpKey.namedCurve() == CryptoKeyOKP::NamedCurve::X25519 ? EVP_PKEY_X25519 : EVP_PKEY_ED25519, nullptr, keyData.data(), keyData.size()); - owned = true; - break; - } else { - auto* evp_key = EVP_PKEY_new_raw_public_key(okpKey.namedCurve() == CryptoKeyOKP::NamedCurve::X25519 ? EVP_PKEY_X25519 : EVP_PKEY_ED25519, nullptr, keyData.data(), keyData.size()); - key = evp_key; - owned = true; - break; - } - } - case CryptoAlgorithmIdentifier::AES_CTR: - case CryptoAlgorithmIdentifier::AES_CBC: - case CryptoAlgorithmIdentifier::AES_GCM: - case CryptoAlgorithmIdentifier::AES_CFB: - case CryptoAlgorithmIdentifier::AES_KW: - case CryptoAlgorithmIdentifier::HMAC: - case CryptoAlgorithmIdentifier::SHA_1: - case CryptoAlgorithmIdentifier::SHA_224: - case CryptoAlgorithmIdentifier::SHA_256: - case CryptoAlgorithmIdentifier::SHA_384: - case CryptoAlgorithmIdentifier::SHA_512: - case CryptoAlgorithmIdentifier::HKDF: - case CryptoAlgorithmIdentifier::PBKDF2: - case CryptoAlgorithmIdentifier::None: - key = nullptr; - break; - } -} - -JSC_DEFINE_HOST_FUNCTION(KeyObject__Equals, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - ncrypto::ClearErrorOnReturn clearErrorOnReturn; - if (auto* key = jsDynamicCast(callFrame->argument(0))) { - if (auto* key2 = jsDynamicCast(callFrame->argument(1))) { - auto& wrapped = key->wrapped(); - auto& wrapped2 = key2->wrapped(); - auto key_type = wrapped.type(); - if (key_type != wrapped2.type()) { - return JSC::JSValue::encode(jsBoolean(false)); - } - - if (key_type == CryptoKeyType::Secret) { - auto keyData = GetRawKeyFromSecret(wrapped); - auto keyData2 = GetRawKeyFromSecret(wrapped2); - auto size = keyData.size(); - - if (size != keyData2.size()) { - return JSC::JSValue::encode(jsBoolean(false)); - } - return JSC::JSValue::encode(jsBoolean(CRYPTO_memcmp(keyData.data(), keyData2.data(), size) == 0)); - } - AsymmetricKeyValue first(wrapped); - AsymmetricKeyValue second(wrapped2); - - int ok = !first.key || !second.key ? -2 : EVP_PKEY_cmp(first.key, second.key); - - if (ok == -2) { - auto& vm = JSC::getVM(lexicalGlobalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "ERR_CRYPTO_UNSUPPORTED_OPERATION"_s)); - return {}; - } - return JSC::JSValue::encode(jsBoolean(ok == 1)); - } - } - return JSC::JSValue::encode(jsBoolean(false)); -} - -JSC_DEFINE_HOST_FUNCTION(KeyObject__SymmetricKeySize, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - if (auto* key = jsDynamicCast(callFrame->argument(0))) { - auto& wrapped = key->wrapped(); - auto size = getSymmetricKeySize(wrapped); - if (size.has_value() && size.value() > 0) { - return JSC::JSValue::encode(jsNumber(size.value())); - } - } - - return JSC::JSValue::encode(JSC::jsUndefined()); -} - -std::optional getSymmetricKeySize(const WebCore::CryptoKey& key) -{ - auto id = key.keyClass(); - switch (id) { - case CryptoKeyClass::HMAC: { - const auto& hmac = downcast(key); - return hmac.key().size(); - } - case CryptoKeyClass::AES: { - const auto& aes = downcast(key); - return aes.key().size(); - } - case CryptoKeyClass::Raw: { - const auto& raw = downcast(key); - return raw.key().size(); - } - default: { - return std::nullopt; - } - } -} - -const uint8_t* getSymmetricKeyData(const WebCore::CryptoKey& key) -{ - auto id = key.keyClass(); - switch (id) { - case CryptoKeyClass::HMAC: { - const auto& hmac = downcast(key); - return hmac.key().data(); - } - case CryptoKeyClass::AES: { - const auto& aes = downcast(key); - return aes.key().data(); - } - case CryptoKeyClass::Raw: { - const auto& raw = downcast(key); - return raw.key().data(); - } - default: { - return nullptr; - } - } -} -std::optional> getSymmetricKey(const WebCore::CryptoKey& key) -{ - auto id = key.keyClass(); - switch (id) { - case CryptoKeyClass::HMAC: { - const auto& hmac = downcast(key); - return hmac.key().span(); - } - case CryptoKeyClass::AES: { - const auto& aes = downcast(key); - return aes.key().span(); - } - case CryptoKeyClass::Raw: { - const auto& raw = downcast(key); - return raw.key().span(); - } - default: { - return std::nullopt; - } - } -} - -static EncodedJSValue doAsymmetricCipher(JSGlobalObject* globalObject, CallFrame* callFrame, bool encrypt) -{ - auto count = callFrame->argumentCount(); - auto& vm = JSC::getVM(globalObject); - 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(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(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, {}); - - 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 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(jsOaepLabelCell); - oaepLabel = std::optional { jsBufferView->unsharedImpl() }; - } else if (jsOaepLabelType == ArrayBufferType) { - auto* jsBuffer = jsDynamicCast(jsOaepLabelCell); - oaepLabel = std::optional { 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(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); -} - -static EncodedJSValue doAsymmetricSign(JSGlobalObject* globalObject, CallFrame* callFrame, bool encrypt) -{ - auto& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - - if (callFrame->argumentCount() != 3) { - return Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_MISSING_ARGS, - "expected three arguments"_s); - } - - auto* jsCryptoKey = jsDynamicCast(callFrame->uncheckedArgument(0)); - if (!jsCryptoKey) { - return Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_TYPE, - "expected CryptoKey as first argument"_s); - } - auto& cryptoKey = jsCryptoKey->wrapped(); - - // We should only sign with private keys, and verify with public keys. - if ((encrypt && cryptoKey.type() != CryptoKeyType::Private) - || (!encrypt && cryptoKey.type() != CryptoKeyType::Public) - // We may classify the key as RSA_OAEP, but it can still be used for signing. RSA_PSS relies - // on an incompatible scheme, and must be used via the generic crypto.sign function. - || (cryptoKey.algorithmIdentifier() != CryptoAlgorithmIdentifier::RSA_OAEP - && cryptoKey.algorithmIdentifier() != CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5)) { - return Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_VALUE, - "unsupported key type for asymmetric signing"_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 padding = RSA_PKCS1_PADDING; - auto jsPadding = callFrame->uncheckedArgument(2); - 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 && padding != RSA_NO_PADDING) { - return Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_VALUE, - "unsupported padding"_s); - } - } - - const auto& rsaKey = downcast(cryptoKey); - auto operation = encrypt ? CryptoAlgorithmRSASSA_PKCS1_v1_5::platformSignNoAlgorithm - : CryptoAlgorithmRSASSA_PKCS1_v1_5::platformVerifyRecover; - auto result = operation(rsaKey, padding, buffer); - 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__privateEncrypt, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - return doAsymmetricSign(globalObject, callFrame, true); -} - -JSC_DEFINE_HOST_FUNCTION(KeyObject__publicDecrypt, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - return doAsymmetricSign(globalObject, callFrame, false); -} - -JSValue createKeyObjectBinding(Zig::GlobalObject* globalObject) -{ - VM& vm = globalObject->vm(); - auto* obj = constructEmptyObject(globalObject); - obj->putDirect( - vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "symmetricKeySize"_s)), JSC::JSFunction::create(vm, globalObject, 1, "symmetricKeySize"_s, KeyObject__SymmetricKeySize, ImplementationVisibility::Public, NoIntrinsic), 0); - obj->putDirect( - vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "asymmetricKeyType"_s)), JSC::JSFunction::create(vm, globalObject, 1, "asymmetricKeyType"_s, KeyObject__AsymmetricKeyType, ImplementationVisibility::Public, NoIntrinsic), 0); - obj->putDirect( - vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "asymmetricKeyDetails"_s)), JSC::JSFunction::create(vm, globalObject, 1, "asymmetricKeyDetails"_s, KeyObject_AsymmetricKeyDetails, ImplementationVisibility::Public, NoIntrinsic), 0); - obj->putDirect( - vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "equals"_s)), JSC::JSFunction::create(vm, globalObject, 2, "equals"_s, KeyObject__Equals, ImplementationVisibility::Public, NoIntrinsic), 0); - obj->putDirect( - vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "exports"_s)), JSC::JSFunction::create(vm, globalObject, 2, "exports"_s, KeyObject__Exports, ImplementationVisibility::Public, NoIntrinsic), 0); - - obj->putDirect( - vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "createSecretKey"_s)), JSC::JSFunction::create(vm, globalObject, 1, "createSecretKey"_s, KeyObject__createSecretKey, ImplementationVisibility::Public, NoIntrinsic), 0); - - obj->putDirect( - vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "createPublicKey"_s)), JSC::JSFunction::create(vm, globalObject, 1, "createPublicKey"_s, KeyObject__createPublicKey, ImplementationVisibility::Public, NoIntrinsic), 0); - - obj->putDirect( - vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "createPrivateKey"_s)), JSC::JSFunction::create(vm, globalObject, 1, "createPrivateKey"_s, KeyObject__createPrivateKey, ImplementationVisibility::Public, NoIntrinsic), 0); - - obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "generateKeySync"_s)), JSC::JSFunction::create(vm, globalObject, 2, "generateKeySync"_s, KeyObject__generateKeySync, ImplementationVisibility::Public, NoIntrinsic), 0); - - obj->putDirect(vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "generateKeyPairSync"_s)), JSC::JSFunction::create(vm, globalObject, 2, "generateKeyPairSync"_s, KeyObject__generateKeyPairSync, ImplementationVisibility::Public, NoIntrinsic), 0); - - 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); - - obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "privateEncrypt"_s)), - JSFunction::create(vm, globalObject, 2, "privateEncrypt"_s, KeyObject__privateEncrypt, ImplementationVisibility::Public, NoIntrinsic), 0); - obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "publicDecrypt"_s)), - JSFunction::create(vm, globalObject, 2, "publicDecrypt"_s, KeyObject__publicDecrypt, ImplementationVisibility::Public, NoIntrinsic), 0); - - obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "X509Certificate"_s)), - globalObject->m_JSX509CertificateClassStructure.constructor(globalObject)); - return obj; -} - -} // namespace WebCore diff --git a/src/bun.js/bindings/KeyObject.h b/src/bun.js/bindings/KeyObject.h deleted file mode 100644 index 9e878f7d8d..0000000000 --- a/src/bun.js/bindings/KeyObject.h +++ /dev/null @@ -1,18 +0,0 @@ - -#pragma once - -#include "root.h" -#include "helpers.h" -#include "ExceptionOr.h" -#include "CryptoKey.h" - -namespace WebCore { - -ExceptionOr> KeyObject__GetBuffer(JSC::JSValue bufferArg); -JSC::JSValue createKeyObjectBinding(Zig::GlobalObject* globalObject); - -std::optional getSymmetricKeySize(const CryptoKey& key); -const uint8_t* getSymmetricKeyData(const CryptoKey& key); -std::optional> getSymmetricKey(const CryptoKey& key); - -} // namespace WebCore diff --git a/src/bun.js/bindings/NodeValidator.cpp b/src/bun.js/bindings/NodeValidator.cpp index 79d1169f53..15dab6aa44 100644 --- a/src/bun.js/bindings/NodeValidator.cpp +++ b/src/bun.js/bindings/NodeValidator.cpp @@ -485,7 +485,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_validateUint32, (JSC::JSGlobalObject * globa auto positive = callFrame->argument(2); return V::validateUint32(scope, globalObject, value, name, positive); } -JSC::EncodedJSValue V::validateUint32(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, JSValue name, JSValue positive) +JSC::EncodedJSValue V::validateUint32(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, JSValue name, JSValue positive, uint32_t* out) { if (!value.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "number"_s, value); if (positive.isUndefined()) positive = jsBoolean(false); @@ -498,9 +498,13 @@ JSC::EncodedJSValue V::validateUint32(JSC::ThrowScope& scope, JSC::JSGlobalObjec auto max = std::numeric_limits().max(); if (value_num < min || value_num > max) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, min, max, value); + if (out) { + *out = static_cast(std::round(value_num)); + } + return JSValue::encode(jsUndefined()); } -JSC::EncodedJSValue V::validateUint32(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name, JSValue positive) +JSC::EncodedJSValue V::validateUint32(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name, JSValue positive, uint32_t* out) { if (!value.isNumber()) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "number"_s, value); if (positive.isUndefined()) positive = jsBoolean(false); @@ -513,6 +517,10 @@ JSC::EncodedJSValue V::validateUint32(JSC::ThrowScope& scope, JSC::JSGlobalObjec auto max = std::numeric_limits().max(); if (value_num < min || value_num > max) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, min, max, value); + if (out) { + *out = static_cast(std::round(value_num)); + } + return JSValue::encode(jsUndefined()); } @@ -643,7 +651,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_validateOneOf, (JSC::JSGlobalObject * global return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "values"_s, "Array"_s, arrayValue); } -JSC::EncodedJSValue V::validateOneOf(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, ASCIILiteral name, JSValue value, const WTF::Vector& oneOf) +JSC::EncodedJSValue V::validateOneOf(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, ASCIILiteral name, JSValue value, std::span oneOf) { if (!value.isString()) { return Bun::ERR::INVALID_ARG_VALUE(scope, globalObject, name, "must be one of: "_s, value, oneOf); @@ -663,6 +671,25 @@ JSC::EncodedJSValue V::validateOneOf(JSC::ThrowScope& scope, JSC::JSGlobalObject return Bun::ERR::INVALID_ARG_VALUE(scope, globalObject, name, "must be one of: "_s, value, oneOf); } +JSC::EncodedJSValue V::validateOneOf(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, ASCIILiteral name, JSValue value, std::span oneOf, int32_t* out) +{ + if (!value.isInt32()) { + return Bun::ERR::INVALID_ARG_VALUE(scope, globalObject, name, "must be one of: "_s, value, oneOf); + } + + int32_t value_num = value.asInt32(); + for (int32_t oneOfNum : oneOf) { + if (value_num == oneOfNum) { + if (out) { + *out = oneOfNum; + } + return JSValue::encode(jsUndefined()); + } + } + + return Bun::ERR::INVALID_ARG_VALUE(scope, globalObject, name, "must be one of: "_s, value, oneOf); +} + JSC_DEFINE_HOST_FUNCTION(jsFunction_validateObject, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { auto& vm = globalObject->vm(); diff --git a/src/bun.js/bindings/NodeValidator.h b/src/bun.js/bindings/NodeValidator.h index d96e4646c1..2f6d76f65c 100644 --- a/src/bun.js/bindings/NodeValidator.h +++ b/src/bun.js/bindings/NodeValidator.h @@ -42,12 +42,13 @@ JSC::EncodedJSValue validateString(JSC::ThrowScope& scope, JSC::JSGlobalObject* JSC::EncodedJSValue validateArray(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, JSValue name, JSValue minLength); JSC::EncodedJSValue validateArray(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name, JSValue minLength); JSC::EncodedJSValue validateArrayBufferView(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name); -JSC::EncodedJSValue validateUint32(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, JSValue name, JSValue positive); -JSC::EncodedJSValue validateUint32(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name, JSValue positive); +JSC::EncodedJSValue validateUint32(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, JSValue name, JSValue positive, uint32_t* out = nullptr); +JSC::EncodedJSValue validateUint32(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name, JSValue positive, uint32_t* out = nullptr); JSC::EncodedJSValue validateInt32(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, JSValue name, JSValue min, JSValue max); JSC::EncodedJSValue validateInt32(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name, JSValue min, JSValue max, int32_t* out = nullptr); JSC::EncodedJSValue validateFunction(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name); -JSC::EncodedJSValue validateOneOf(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, ASCIILiteral name, JSValue value, const WTF::Vector& oneOf); +JSC::EncodedJSValue validateOneOf(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, ASCIILiteral name, JSValue value, std::span oneOf); +JSC::EncodedJSValue validateOneOf(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, ASCIILiteral name, JSValue value, std::span oneOf, int32_t* out = nullptr); JSC::EncodedJSValue validateObject(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name); JSC::EncodedJSValue validateBoolean(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name); diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 94714cfc70..3e49584426 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -168,6 +168,10 @@ #include "JSDiffieHellmanGroup.h" #include "JSECDH.h" #include "JSCipher.h" +#include "JSKeyObject.h" +#include "JSSecretKeyObject.h" +#include "JSPublicKeyObject.h" +#include "JSPrivateKeyObject.h" #include "JSS3File.h" #include "S3Error.h" #include "ProcessBindingBuffer.h" @@ -2916,6 +2920,26 @@ void GlobalObject::finishCreation(VM& vm) setupCipherClassStructure(init); }); + m_JSKeyObjectClassStructure.initLater( + [](LazyClassStructure::Initializer& init) { + setupKeyObjectClassStructure(init); + }); + + m_JSSecretKeyObjectClassStructure.initLater( + [](LazyClassStructure::Initializer& init) { + setupSecretKeyObjectClassStructure(init); + }); + + m_JSPublicKeyObjectClassStructure.initLater( + [](LazyClassStructure::Initializer& init) { + setupPublicKeyObjectClassStructure(init); + }); + + m_JSPrivateKeyObjectClassStructure.initLater( + [](LazyClassStructure::Initializer& init) { + setupPrivateKeyObjectClassStructure(init); + }); + m_lazyStackCustomGetterSetter.initLater( [](const Initializer& init) { init.set(CustomGetterSetter::create(init.vm, errorInstanceLazyStackCustomGetter, errorInstanceLazyStackCustomSetter)); @@ -4100,6 +4124,10 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) thisObject->m_JSHmacClassStructure.visit(visitor); thisObject->m_JSHashClassStructure.visit(visitor); thisObject->m_JSCipherClassStructure.visit(visitor); + thisObject->m_JSKeyObjectClassStructure.visit(visitor); + thisObject->m_JSSecretKeyObjectClassStructure.visit(visitor); + thisObject->m_JSPublicKeyObjectClassStructure.visit(visitor); + thisObject->m_JSPrivateKeyObjectClassStructure.visit(visitor); thisObject->m_statValues.visit(visitor); thisObject->m_bigintStatValues.visit(visitor); thisObject->m_statFsValues.visit(visitor); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 60e3940bc6..9e4c533816 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -204,6 +204,10 @@ public: JSC::JSValue FileSinkPrototype() const { return m_JSFileSinkClassStructure.prototypeInitializedOnMainThread(this); } JSC::JSValue JSReadableFileSinkControllerPrototype() const { return m_JSFileSinkControllerPrototype.getInitializedOnMainThread(this); } + JSC::Structure* KeyObjectStructure() const { return m_JSKeyObjectClassStructure.getInitializedOnMainThread(this); } + JSC::JSObject* KeyObject() const { return m_JSKeyObjectClassStructure.constructorInitializedOnMainThread(this); } + JSC::JSValue KeyObjectPrototype() const { return m_JSKeyObjectClassStructure.prototypeInitializedOnMainThread(this); } + JSC::Structure* JSBufferStructure() const { return m_JSBufferClassStructure.getInitializedOnMainThread(this); } JSC::JSObject* JSBufferConstructor() const { return m_JSBufferClassStructure.constructorInitializedOnMainThread(this); } JSC::JSValue JSBufferPrototype() const { return m_JSBufferClassStructure.prototypeInitializedOnMainThread(this); } @@ -568,6 +572,10 @@ public: LazyClassStructure m_JSHashClassStructure; LazyClassStructure m_JSECDHClassStructure; LazyClassStructure m_JSCipherClassStructure; + LazyClassStructure m_JSKeyObjectClassStructure; + LazyClassStructure m_JSSecretKeyObjectClassStructure; + LazyClassStructure m_JSPublicKeyObjectClassStructure; + LazyClassStructure m_JSPrivateKeyObjectClassStructure; /** * WARNING: You must update visitChildrenImpl() if you add a new field. diff --git a/src/bun.js/bindings/headers-handwritten.h b/src/bun.js/bindings/headers-handwritten.h index 1fe5690be7..9106a6c17e 100644 --- a/src/bun.js/bindings/headers-handwritten.h +++ b/src/bun.js/bindings/headers-handwritten.h @@ -400,6 +400,7 @@ extern "C" int64_t Bun__encoding__constructFromUTF16(void*, const UChar* ptr, si extern "C" void Bun__EventLoop__runCallback1(JSC::JSGlobalObject* global, JSC::EncodedJSValue callback, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue arg1); extern "C" void Bun__EventLoop__runCallback2(JSC::JSGlobalObject* global, JSC::EncodedJSValue callback, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue arg1, JSC::EncodedJSValue arg2); +extern "C" void Bun__EventLoop__runCallback3(JSC::JSGlobalObject* global, JSC::EncodedJSValue callback, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue arg1, JSC::EncodedJSValue arg2, JSC::EncodedJSValue arg3); /// @note throws a JS exception and returns false if a stack overflow occurs template diff --git a/src/bun.js/bindings/ncrypto.cpp b/src/bun.js/bindings/ncrypto.cpp index 79fc665471..0719f65d44 100644 --- a/src/bun.js/bindings/ncrypto.cpp +++ b/src/bun.js/bindings/ncrypto.cpp @@ -253,7 +253,7 @@ DataPointer DataPointer::resize(size_t len) { size_t actual_len = std::min(len_, len); auto buf = release(); - if (actual_len == len_) return DataPointer(buf); + if (actual_len == len_) return DataPointer(buf.data, actual_len); buf.data = OPENSSL_realloc(buf.data, actual_len); buf.len = actual_len; return DataPointer(buf); @@ -442,6 +442,14 @@ int BignumPointer::operator<=>(const BIGNUM* other) const noexcept return BN_cmp(bn_.get(), other); } +DataPointer BignumPointer::toHex(const BIGNUM* bn) +{ + if (bn == nullptr) return {}; + char* hex = BN_bn2hex(bn); + if (!hex) return {}; + return DataPointer(hex, strlen(hex)); +} + DataPointer BignumPointer::toHex() const { if (!bn_) return {}; @@ -4421,6 +4429,15 @@ int Ec::getCurve() const return EC_GROUP_get_curve_name(getGroup()); } +int Ec::GetCurveIdFromName(const char* name) +{ + int nid = EC_curve_nist2nid(name); + if (nid == NID_undef) { + nid = OBJ_sn2nid(name); + } + return nid; +} + // ============================================================================ EVPMDCtxPointer::EVPMDCtxPointer() diff --git a/src/bun.js/bindings/ncrypto.h b/src/bun.js/bindings/ncrypto.h index 0251a40a24..8d3010ec86 100644 --- a/src/bun.js/bindings/ncrypto.h +++ b/src/bun.js/bindings/ncrypto.h @@ -478,6 +478,8 @@ public: const EC_GROUP* getGroup() const; int getCurve() const; + static int GetCurveIdFromName(const char* name); + inline operator bool() const { return ec_ != nullptr; } inline operator OSSL3_CONST EC_KEY*() const { return ec_; } @@ -503,6 +505,16 @@ public: // has been allocated from the heap. static size_t GetSecureHeapUsed(); + static DataPointer FromSpan(std::span span) + { + if (span.empty()) return {}; + if (auto dp = Alloc(span.size())) { + memcpy(dp.get(), span.data(), span.size()); + return dp; + } + return {}; + } + enum class InitSecureHeapResult { FAILED, UNABLE_TO_MEMORY_MAP, @@ -652,6 +664,7 @@ public: size_t byteLength() const; + static DataPointer toHex(const BIGNUM* bn); DataPointer toHex() const; DataPointer encode() const; DataPointer encodePadded(size_t size) const; @@ -844,6 +857,20 @@ public: SEC1, }; + static WTF::ASCIILiteral EncodingName(PKEncodingType type) + { + switch (type) { + case PKEncodingType::PKCS1: + return "pkcs1"_s; + case PKEncodingType::PKCS8: + return "pkcs8"_s; + case PKEncodingType::SPKI: + return "spki"_s; + case PKEncodingType::SEC1: + return "sec1"_s; + } + } + enum class PKFormatType { DER, PEM, diff --git a/src/bun.js/bindings/node/crypto/CryptoDhJob.cpp b/src/bun.js/bindings/node/crypto/CryptoDhJob.cpp new file mode 100644 index 0000000000..bf529e76d5 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoDhJob.cpp @@ -0,0 +1,168 @@ +#include "CryptoDhJob.h" +#include "NodeValidator.h" +#include "JSKeyObject.h" +#include "ErrorCode.h" + +using namespace JSC; +using namespace ncrypto; + +namespace Bun { + +extern "C" void Bun__DhJobCtx__deinit(DhJobCtx* ctx) +{ + ctx->deinit(); +} +void DhJobCtx::deinit() +{ + delete this; +} + +extern "C" void Bun__DhJobCtx__runTask(DhJobCtx* ctx, JSGlobalObject* globalObject) +{ + ctx->runTask(globalObject); +} +void DhJobCtx::runTask(JSGlobalObject* globalObject) +{ + auto dp = DHPointer::stateless(m_privateKey->asymmetricKey, m_publicKey->asymmetricKey); + if (!dp) { + return; + } + + WTF::Vector result; + if (!result.tryGrow(dp.size())) { + return; + } + + m_result = ByteSource::allocated(dp.release()); +} + +extern "C" void Bun__DhJobCtx__runFromJS(DhJobCtx* ctx, JSGlobalObject* globalObject, EncodedJSValue callback) +{ + ctx->runFromJS(globalObject, JSValue::decode(callback)); +} +void DhJobCtx::runFromJS(JSGlobalObject* lexicalGlobalObject, JSValue callback) +{ + VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + if (!m_result) { + JSObject* err = createError(lexicalGlobalObject, ErrorCode::ERR_CRYPTO_OPERATION_FAILED, "diffieHellman failed"_s); + Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(callback), JSValue::encode(jsUndefined()), JSValue::encode(err)); + return; + } + + JSValue result = WebCore::createBuffer(lexicalGlobalObject, m_result.span()); + + Bun__EventLoop__runCallback2( + lexicalGlobalObject, + JSValue::encode(callback), + JSValue::encode(jsUndefined()), + JSValue::encode(jsNull()), + JSValue::encode(result)); +} + +extern "C" DhJob* Bun__DhJob__create(JSGlobalObject* globalObject, DhJobCtx* ctx, EncodedJSValue callback); +DhJob* DhJob::create(JSGlobalObject* globalObject, DhJobCtx&& ctx, JSValue callback) +{ + DhJobCtx* ctxCopy = new DhJobCtx(WTFMove(ctx)); + return Bun__DhJob__create(globalObject, ctxCopy, JSValue::encode(callback)); +} + +extern "C" void Bun__DhJob__schedule(DhJob* job); +void DhJob::schedule() +{ + Bun__DhJob__schedule(this); +} + +extern "C" void Bun__DhJob__createAndSchedule(JSGlobalObject* globalObject, DhJobCtx* ctx, EncodedJSValue callback); +void DhJob::createAndSchedule(JSGlobalObject* globalObject, DhJobCtx&& ctx, JSValue callback) +{ + DhJobCtx* ctxCopy = new DhJobCtx(WTFMove(ctx)); + Bun__DhJob__createAndSchedule(globalObject, ctxCopy, JSValue::encode(callback)); +} + +std::optional DhJobCtx::fromJS(JSGlobalObject* globalObject, ThrowScope& scope, JSC::JSObject* options) +{ + VM& vm = globalObject->vm(); + + JSValue privateKeyValue = options->get(globalObject, Identifier::fromString(vm, "privateKey"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + JSValue publicKeyValue = options->get(globalObject, Identifier::fromString(vm, "publicKey"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + JSKeyObject* privateKeyObject = jsDynamicCast(privateKeyValue); + if (!privateKeyObject) { + ERR::INVALID_ARG_VALUE(scope, globalObject, "options.privateKey"_s, privateKeyValue); + return std::nullopt; + } + + JSKeyObject* publicKeyObject = jsDynamicCast(publicKeyValue); + if (!publicKeyObject) { + ERR::INVALID_ARG_VALUE(scope, globalObject, "options.publicKey"_s, publicKeyValue); + return std::nullopt; + } + + const KeyObject& privateKey = privateKeyObject->handle(); + const KeyObject& publicKey = publicKeyObject->handle(); + + if (privateKey.type() != CryptoKeyType::Private) { + ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, privateKey.type(), "private"_s); + return std::nullopt; + } + + if (publicKey.type() != CryptoKeyType::Public && publicKey.type() != CryptoKeyType::Private) { + ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, publicKey.type(), "public or private"_s); + return std::nullopt; + } + + static constexpr auto supportedKeyTypes = std::to_array({ EVP_PKEY_DH, + EVP_PKEY_EC, + EVP_PKEY_X448, + EVP_PKEY_X25519 }); + + int privateKeyType = privateKey.asymmetricKey().id(); + int publicKeyType = publicKey.asymmetricKey().id(); + + if (privateKeyType != publicKeyType || std::ranges::find(supportedKeyTypes, privateKeyType) == supportedKeyTypes.end()) { + ERR::INVALID_ARG_VALUE(scope, globalObject, "options.privateKey"_s, privateKeyValue, "must be a supported key type"_s); + return std::nullopt; + } + + return DhJobCtx(privateKey.data(), publicKey.data()); +} + +JSC_DEFINE_HOST_FUNCTION(jsDiffieHellman, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSValue optionsValue = callFrame->argument(0); + V::validateObject(scope, lexicalGlobalObject, optionsValue, "options"_s); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + JSObject* options = optionsValue.getObject(); + + JSValue callbackValue = callFrame->argument(1); + if (!callbackValue.isUndefined()) { + V::validateFunction(scope, lexicalGlobalObject, callbackValue, "callback"_s); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + } + + std::optional ctx = DhJobCtx::fromJS(lexicalGlobalObject, scope, options); + ASSERT(ctx.has_value() == !scope.exception()); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + if (!callbackValue.isUndefined()) { + DhJob::createAndSchedule(lexicalGlobalObject, WTFMove(*ctx), callbackValue); + return JSValue::encode(jsUndefined()); + } + + ctx->runTask(lexicalGlobalObject); + + if (!ctx->m_result) { + return ERR::CRYPTO_OPERATION_FAILED(scope, lexicalGlobalObject, "diffieHellman operation failed"_s); + } + + return JSValue::encode(WebCore::createBuffer(lexicalGlobalObject, ctx->m_result.span())); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/CryptoDhJob.h b/src/bun.js/bindings/node/crypto/CryptoDhJob.h new file mode 100644 index 0000000000..cb2f04e83a --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoDhJob.h @@ -0,0 +1,48 @@ +#pragma once + +#include "root.h" +#include "KeyObject.h" +#include "CryptoUtil.h" + +namespace Bun { + +JSC_DECLARE_HOST_FUNCTION(jsDiffieHellman); + +struct DhJobCtx { + WTF_MAKE_TZONE_ALLOCATED(DhJobCtx); + +public: + DhJobCtx(RefPtr&& privateKey, RefPtr&& publicKey) + : m_privateKey(WTFMove(privateKey)) + , m_publicKey(WTFMove(publicKey)) + { + } + + DhJobCtx(DhJobCtx&& other) + : m_privateKey(WTFMove(other.m_privateKey)) + , m_publicKey(WTFMove(other.m_publicKey)) + , m_result(WTFMove(other.m_result)) + { + } + + ~DhJobCtx() = default; + + static std::optional fromJS(JSC::JSGlobalObject*, JSC::ThrowScope&, JSC::JSObject* options); + + void runTask(JSC::JSGlobalObject*); + void runFromJS(JSC::JSGlobalObject*, JSC::JSValue callback); + void deinit(); + + RefPtr m_privateKey; + RefPtr m_publicKey; + + ByteSource m_result; +}; + +struct DhJob { + static DhJob* create(JSC::JSGlobalObject*, DhJobCtx&&, JSC::JSValue callback); + static void createAndSchedule(JSC::JSGlobalObject*, DhJobCtx&&, JSC::JSValue callback); + void schedule(); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/CryptoGenDhKeyPair.cpp b/src/bun.js/bindings/node/crypto/CryptoGenDhKeyPair.cpp new file mode 100644 index 0000000000..ac09bc022f --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoGenDhKeyPair.cpp @@ -0,0 +1,201 @@ +#include "CryptoGenDhKeyPair.h" +#include "ErrorCode.h" +#include "NodeValidator.h" +#include "CryptoUtil.h" + +using namespace Bun; +using namespace JSC; + +extern "C" void Bun__DhKeyPairJobCtx__deinit(DhKeyPairJobCtx* ctx) +{ + ctx->deinit(); +} + +void DhKeyPairJobCtx::deinit() +{ + delete this; +} + +extern "C" void Bun__DhKeyPairJobCtx__runTask(DhKeyPairJobCtx* ctx, JSGlobalObject* globalObject) +{ + ncrypto::EVPKeyCtxPointer keyCtx = ctx->setup(); + if (!keyCtx) { + return; + } + ctx->runTask(globalObject, keyCtx); +} + +extern "C" void Bun__DhKeyPairJobCtx__runFromJS(DhKeyPairJobCtx* ctx, JSGlobalObject* globalObject, EncodedJSValue callback) +{ + ctx->runFromJS(globalObject, JSValue::decode(callback)); +} + +extern "C" DhKeyPairJob* Bun__DhKeyPairJob__create(JSGlobalObject* globalObject, DhKeyPairJobCtx* ctx, EncodedJSValue callback); +DhKeyPairJob* DhKeyPairJob::create(JSGlobalObject* globalObject, DhKeyPairJobCtx&& ctx, JSValue callback) +{ + DhKeyPairJobCtx* ctxCopy = new DhKeyPairJobCtx(WTFMove(ctx)); + return Bun__DhKeyPairJob__create(globalObject, ctxCopy, JSValue::encode(callback)); +} + +extern "C" void Bun__DhKeyPairJob__schedule(DhKeyPairJob* job); +void DhKeyPairJob::schedule() +{ + Bun__DhKeyPairJob__schedule(this); +} + +extern "C" void Bun__DhKeyPairJob__createAndSchedule(JSGlobalObject* globalObject, DhKeyPairJobCtx* ctx, EncodedJSValue callback); +void DhKeyPairJob::createAndSchedule(JSGlobalObject* globalObject, DhKeyPairJobCtx&& ctx, JSValue callback) +{ + DhKeyPairJobCtx* ctxCopy = new DhKeyPairJobCtx(WTFMove(ctx)); + Bun__DhKeyPairJob__createAndSchedule(globalObject, ctxCopy, JSValue::encode(callback)); +} + +ncrypto::EVPKeyCtxPointer DhKeyPairJobCtx::setup() +{ + ncrypto::EVPKeyPointer keyParams; + + if (ncrypto::BignumPointer* primeFixedValue = std::get_if(&m_prime)) { + auto prime = primeFixedValue->clone(); + auto bnG = ncrypto::BignumPointer::New(); + if (!prime || !bnG || !bnG.setWord(m_generator)) { + m_opensslError = ERR_get_error(); + return {}; + } + auto dh = ncrypto::DHPointer::New(WTFMove(prime), WTFMove(bnG)); + if (!dh) { + m_opensslError = ERR_get_error(); + return {}; + } + + keyParams = ncrypto::EVPKeyPointer::NewDH(WTFMove(dh)); + } else if (std::get_if(&m_prime)) { + auto paramCtx = ncrypto::EVPKeyCtxPointer::NewFromID(EVP_PKEY_DH); + + int* primeLength = std::get_if(&m_prime); + if (!paramCtx.initForParamgen() || !paramCtx.setDhParameters(*primeLength, m_generator)) { + m_opensslError = ERR_get_error(); + return {}; + } + + keyParams = paramCtx.paramgen(); + } + + if (!keyParams) { + m_opensslError = ERR_get_error(); + return {}; + } + + ncrypto::EVPKeyCtxPointer ctx = keyParams.newCtx(); + if (!ctx.initForKeygen()) { + m_opensslError = ERR_get_error(); + return {}; + } + + return ctx; +} + +std::optional DhKeyPairJobCtx::fromJS(JSGlobalObject* globalObject, ThrowScope& scope, const GCOwnedDataScope& typeView, JSValue optionsValue, const KeyEncodingConfig& config) +{ + VM& vm = globalObject->vm(); + + V::validateObject(scope, globalObject, optionsValue, "options"_s); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + JSValue groupValue = optionsValue.get(globalObject, Identifier::fromString(vm, "group"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + JSValue primeLengthValue = optionsValue.get(globalObject, Identifier::fromString(vm, "primeLength"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + JSValue primeValue = optionsValue.get(globalObject, Identifier::fromString(vm, "prime"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + JSValue generatorValue = optionsValue.get(globalObject, Identifier::fromString(vm, "generator"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + if (!groupValue.isUndefinedOrNull()) { + if (!primeValue.isUndefinedOrNull()) { + ERR::INCOMPATIBLE_OPTION_PAIR(scope, globalObject, "group"_s, "prime"_s); + return std::nullopt; + } + if (!primeLengthValue.isUndefinedOrNull()) { + ERR::INCOMPATIBLE_OPTION_PAIR(scope, globalObject, "group"_s, "primeLength"_s); + return std::nullopt; + } + if (!generatorValue.isUndefinedOrNull()) { + ERR::INCOMPATIBLE_OPTION_PAIR(scope, globalObject, "group"_s, "generator"_s); + return std::nullopt; + } + + V::validateString(scope, globalObject, groupValue, "options.group"_s); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + JSString* groupString = groupValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + GCOwnedDataScope groupView = groupString->view(globalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + auto group = ncrypto::DHPointer::FromGroup(groupView); + if (!group) { + ERR::CRYPTO_UNKNOWN_DH_GROUP(scope, globalObject); + return std::nullopt; + } + + return DhKeyPairJobCtx( + WTFMove(group), + config); + } + + std::optional primeLength = std::nullopt; + ncrypto::BignumPointer prime; + int32_t generator = 2; + + if (!primeValue.isUndefinedOrNull()) { + if (!primeLengthValue.isUndefinedOrNull()) { + ERR::INCOMPATIBLE_OPTION_PAIR(scope, globalObject, "prime"_s, "primeLength"_s); + return std::nullopt; + } + + if (JSArrayBufferView* view = jsDynamicCast(primeValue)) { + prime = ncrypto::BignumPointer(reinterpret_cast(view->vector()), view->byteLength()); + if (UNLIKELY(!prime)) { + ERR::OUT_OF_RANGE(scope, globalObject, "prime is too big"_s); + return std::nullopt; + } + + // TODO: delete this case? validateBuffer allows Buffer, TypeArray, and DataView + } else if (JSArrayBuffer* buffer = jsDynamicCast(primeValue)) { + auto impl = buffer->impl(); + prime = ncrypto::BignumPointer(reinterpret_cast(impl->data()), impl->byteLength()); + if (UNLIKELY(!prime)) { + ERR::OUT_OF_RANGE(scope, globalObject, "prime is too big"_s); + return std::nullopt; + } + } else { + ERR::INVALID_ARG_TYPE(scope, globalObject, "options.prime"_s, "Buffer, TypedArray, or DataView"_s, primeValue); + return std::nullopt; + } + } else if (!primeLengthValue.isUndefinedOrNull()) { + int32_t length; + V::validateInt32(scope, globalObject, primeLengthValue, "options.primeLength"_s, jsNumber(0), jsUndefined(), &length); + RETURN_IF_EXCEPTION(scope, std::nullopt); + primeLength = length; + } else { + ERR::MISSING_OPTION(scope, globalObject, "At least one of the group, prime, or primeLength options"_s); + return std::nullopt; + } + + if (!generatorValue.isUndefinedOrNull()) { + V::validateInt32(scope, globalObject, generatorValue, "options.generator"_s, jsNumber(0), jsUndefined(), &generator); + RETURN_IF_EXCEPTION(scope, std::nullopt); + } + + if (primeLength) { + return DhKeyPairJobCtx( + *primeLength, + generator, + config); + } + + return DhKeyPairJobCtx( + prime, + generator, + config); +} diff --git a/src/bun.js/bindings/node/crypto/CryptoGenDhKeyPair.h b/src/bun.js/bindings/node/crypto/CryptoGenDhKeyPair.h new file mode 100644 index 0000000000..5ed7e1a3ef --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoGenDhKeyPair.h @@ -0,0 +1,46 @@ +#pragma once + +#include "root.h" +#include "ncrypto.h" +#include "CryptoGenKeyPair.h" + +namespace Bun { + +struct DhKeyPairJobCtx : KeyPairJobCtx { + WTF_MAKE_TZONE_ALLOCATED(DhKeyPairJobCtx); + +public: + DhKeyPairJobCtx(ncrypto::DHPointer&& group, const KeyEncodingConfig& config) + : KeyPairJobCtx(config.publicKeyEncoding, config.privateKeyEncoding) + , m_prime(WTFMove(group)) + { + } + + DhKeyPairJobCtx(int primeLength, uint32_t generator, const KeyEncodingConfig& config) + : KeyPairJobCtx(config.publicKeyEncoding, config.privateKeyEncoding) + , m_prime(primeLength) + , m_generator(generator) + { + } + + DhKeyPairJobCtx(ncrypto::BignumPointer&& prime, const KeyEncodingConfig& config) + : KeyPairJobCtx(config.publicKeyEncoding, config.privateKeyEncoding) + , m_prime(WTFMove(prime)) + { + } + + void deinit(); + ncrypto::EVPKeyCtxPointer setup(); + static std::optional fromJS(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, const JSC::GCOwnedDataScope& typeView, JSC::JSValue optionsValue, const KeyEncodingConfig& config); + + std::variant m_prime; + uint32_t m_generator; +}; + +struct DhKeyPairJob { + static DhKeyPairJob* create(JSC::JSGlobalObject*, DhKeyPairJobCtx&&, JSC::JSValue callback); + static void createAndSchedule(JSC::JSGlobalObject*, DhKeyPairJobCtx&&, JSC::JSValue callback); + void schedule(); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/CryptoGenDsaKeyPair.cpp b/src/bun.js/bindings/node/crypto/CryptoGenDsaKeyPair.cpp new file mode 100644 index 0000000000..2eb41bae02 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoGenDsaKeyPair.cpp @@ -0,0 +1,103 @@ +#include "CryptoGenDsaKeyPair.h" +#include "CryptoUtil.h" +#include "NodeValidator.h" + +using namespace Bun; +using namespace JSC; + +extern "C" void Bun__DsaKeyPairJobCtx__deinit(DsaKeyPairJobCtx* ctx) +{ + ctx->deinit(); +} + +void DsaKeyPairJobCtx::deinit() +{ + delete this; +} + +extern "C" void Bun__DsaKeyPairJobCtx__runTask(DsaKeyPairJobCtx* ctx, JSGlobalObject* globalObject) +{ + ncrypto::EVPKeyCtxPointer keyCtx = ctx->setup(); + if (!keyCtx) { + return; + } + ctx->runTask(globalObject, keyCtx); +} + +extern "C" void Bun__DsaKeyPairJobCtx__runFromJS(DsaKeyPairJobCtx* ctx, JSGlobalObject* globalObject, EncodedJSValue callback) +{ + ctx->runFromJS(globalObject, JSValue::decode(callback)); +} + +extern "C" DsaKeyPairJob* Bun__DsaKeyPairJob__create(JSGlobalObject* globalObject, DsaKeyPairJobCtx* ctx, EncodedJSValue callback); +DsaKeyPairJob* DsaKeyPairJob::create(JSGlobalObject* globalObject, DsaKeyPairJobCtx&& ctx, JSValue callback) +{ + DsaKeyPairJobCtx* ctxCopy = new DsaKeyPairJobCtx(WTFMove(ctx)); + return Bun__DsaKeyPairJob__create(globalObject, ctxCopy, JSValue::encode(callback)); +} + +extern "C" void Bun__DsaKeyPairJob__schedule(DsaKeyPairJob* job); +void DsaKeyPairJob::schedule() +{ + Bun__DsaKeyPairJob__schedule(this); +} + +extern "C" void Bun__DsaKeyPairJob__createAndSchedule(JSGlobalObject* globalObject, DsaKeyPairJobCtx* ctx, EncodedJSValue callback); +void DsaKeyPairJob::createAndSchedule(JSGlobalObject* globalObject, DsaKeyPairJobCtx&& ctx, JSValue callback) +{ + DsaKeyPairJobCtx* ctxCopy = new DsaKeyPairJobCtx(WTFMove(ctx)); + Bun__DsaKeyPairJob__createAndSchedule(globalObject, ctxCopy, JSValue::encode(callback)); +} + +ncrypto::EVPKeyCtxPointer DsaKeyPairJobCtx::setup() +{ + ncrypto::EVPKeyCtxPointer paramCtx = ncrypto::EVPKeyCtxPointer::NewFromID(EVP_PKEY_DSA); + + if (!paramCtx || paramCtx.initForParamgen() || !paramCtx.setDsaParameters(m_modulusLength, m_divisorLength)) { + m_opensslError = ERR_get_error(); + return {}; + } + + auto keyParams = paramCtx.paramgen(); + if (!keyParams) { + m_opensslError = ERR_get_error(); + return {}; + } + + ncrypto::EVPKeyCtxPointer keyCtx = keyParams.newCtx(); + if (!keyCtx.initForKeygen()) { + m_opensslError = ERR_get_error(); + return {}; + } + + return keyCtx; +} + +std::optional DsaKeyPairJobCtx::fromJS(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, const JSC::GCOwnedDataScope& typeView, JSC::JSValue optionsValue, const KeyEncodingConfig& config) +{ + VM& vm = globalObject->vm(); + + V::validateObject(scope, globalObject, optionsValue, "options"_s); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + JSValue modulusLengthValue = optionsValue.get(globalObject, Identifier::fromString(vm, "modulusLength"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + uint32_t modulusLength; + V::validateUint32(scope, globalObject, modulusLengthValue, "options.modulusLength"_s, jsUndefined(), &modulusLength); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + JSValue divisorLengthValue = optionsValue.get(globalObject, Identifier::fromString(vm, "divisorLength"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + std::optional divisorLength = std::nullopt; + if (!divisorLengthValue.isUndefinedOrNull()) { + int32_t length; + V::validateInt32(scope, globalObject, divisorLengthValue, "options.divisorLength"_s, jsNumber(0), jsUndefined(), &length); + RETURN_IF_EXCEPTION(scope, std::nullopt); + divisorLength = length; + } + + return DsaKeyPairJobCtx( + modulusLength, + divisorLength, + config); +} diff --git a/src/bun.js/bindings/node/crypto/CryptoGenDsaKeyPair.h b/src/bun.js/bindings/node/crypto/CryptoGenDsaKeyPair.h new file mode 100644 index 0000000000..3d82fe05f2 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoGenDsaKeyPair.h @@ -0,0 +1,34 @@ +#pragma once + +#include "root.h" +#include "ncrypto.h" +#include "CryptoGenKeyPair.h" + +namespace Bun { + +struct DsaKeyPairJobCtx : KeyPairJobCtx { + WTF_MAKE_TZONE_ALLOCATED(DsaKeyPairJobCtx); + +public: + DsaKeyPairJobCtx(uint32_t modulusLength, std::optional divisorLength, const KeyEncodingConfig& config) + : KeyPairJobCtx(config.publicKeyEncoding, config.privateKeyEncoding) + , m_modulusLength(modulusLength) + , m_divisorLength(divisorLength) + { + } + + void deinit(); + ncrypto::EVPKeyCtxPointer setup(); + static std::optional fromJS(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, const JSC::GCOwnedDataScope& typeView, JSC::JSValue optionsValue, const KeyEncodingConfig& config); + + uint32_t m_modulusLength; + std::optional m_divisorLength; +}; + +struct DsaKeyPairJob { + static DsaKeyPairJob* create(JSC::JSGlobalObject*, DsaKeyPairJobCtx&&, JSC::JSValue callback); + static void createAndSchedule(JSC::JSGlobalObject*, DsaKeyPairJobCtx&&, JSC::JSValue callback); + void schedule(); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/CryptoGenEcKeyPair.cpp b/src/bun.js/bindings/node/crypto/CryptoGenEcKeyPair.cpp new file mode 100644 index 0000000000..854a55457c --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoGenEcKeyPair.cpp @@ -0,0 +1,143 @@ +#include "CryptoGenEcKeyPair.h" +#include "CryptoUtil.h" +#include "NodeValidator.h" +#include "ncrypto.h" + +using namespace Bun; +using namespace JSC; + +extern "C" void Bun__EcKeyPairJobCtx__deinit(EcKeyPairJobCtx* ctx) +{ + ctx->deinit(); +} + +void EcKeyPairJobCtx::deinit() +{ + delete this; +} + +extern "C" void Bun__EcKeyPairJobCtx__runTask(EcKeyPairJobCtx* ctx, JSGlobalObject* globalObject) +{ + ncrypto::EVPKeyCtxPointer keyCtx = ctx->setup(); + if (!keyCtx) { + return; + } + ctx->runTask(globalObject, keyCtx); +} + +extern "C" void Bun__EcKeyPairJobCtx__runFromJS(EcKeyPairJobCtx* ctx, JSGlobalObject* globalObject, EncodedJSValue callback) +{ + ctx->runFromJS(globalObject, JSValue::decode(callback)); +} + +extern "C" EcKeyPairJob* Bun__EcKeyPairJob__create(JSGlobalObject* globalObject, EcKeyPairJobCtx* ctx, EncodedJSValue callback); +EcKeyPairJob* EcKeyPairJob::create(JSGlobalObject* globalObject, EcKeyPairJobCtx&& ctx, JSValue callback) +{ + EcKeyPairJobCtx* ctxCopy = new EcKeyPairJobCtx(WTFMove(ctx)); + return Bun__EcKeyPairJob__create(globalObject, ctxCopy, JSValue::encode(callback)); +} + +extern "C" void Bun__EcKeyPairJob__schedule(EcKeyPairJob* job); +void EcKeyPairJob::schedule() +{ + Bun__EcKeyPairJob__schedule(this); +} + +extern "C" void Bun__EcKeyPairJob__createAndSchedule(JSGlobalObject* globalObject, EcKeyPairJobCtx* ctx, EncodedJSValue callback); +void EcKeyPairJob::createAndSchedule(JSGlobalObject* globalObject, EcKeyPairJobCtx&& ctx, JSValue callback) +{ + EcKeyPairJobCtx* ctxCopy = new EcKeyPairJobCtx(WTFMove(ctx)); + Bun__EcKeyPairJob__createAndSchedule(globalObject, ctxCopy, JSValue::encode(callback)); +} + +ncrypto::EVPKeyCtxPointer EcKeyPairJobCtx::setup() +{ + ncrypto::EVPKeyCtxPointer keyCtx; + switch (m_curveNid) { + case EVP_PKEY_ED25519: + case EVP_PKEY_ED448: + case EVP_PKEY_X25519: + case EVP_PKEY_X448: + keyCtx = ncrypto::EVPKeyCtxPointer::NewFromID(m_curveNid); + break; + default: { + auto paramCtx = ncrypto::EVPKeyCtxPointer::NewFromID(EVP_PKEY_EC); + if (!paramCtx.initForParamgen() || !paramCtx.setEcParameters(m_curveNid, m_paramEncoding)) { + m_opensslError = ERR_get_error(); + return {}; + } + + auto keyParams = paramCtx.paramgen(); + if (!keyParams) { + m_opensslError = ERR_get_error(); + return {}; + } + + keyCtx = keyParams.newCtx(); + } + } + + if (!keyCtx.initForKeygen()) { + m_opensslError = ERR_get_error(); + return {}; + } + + return keyCtx; +} + +std::optional EcKeyPairJobCtx::fromJS(JSGlobalObject* globalObject, ThrowScope& scope, const GCOwnedDataScope& typeView, JSValue optionsValue, const KeyEncodingConfig& config) +{ + VM& vm = globalObject->vm(); + + V::validateObject(scope, globalObject, optionsValue, "options"_s); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + JSValue namedCurveValue = optionsValue.get(globalObject, Identifier::fromString(vm, "namedCurve"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + V::validateString(scope, globalObject, namedCurveValue, "options.namedCurve"_s); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + JSValue paramEncodingValue = optionsValue.get(globalObject, Identifier::fromString(vm, "paramEncoding"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + int paramEncoding; + + if (paramEncodingValue.isUndefinedOrNull()) { + paramEncoding = OPENSSL_EC_NAMED_CURVE; + } else if (paramEncodingValue.isString()) { + JSString* paramEncodingString = paramEncodingValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + GCOwnedDataScope paramEncodingView = paramEncodingString->view(globalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + if (paramEncodingView == "named"_s) { + paramEncoding = OPENSSL_EC_NAMED_CURVE; + } else if (paramEncodingView == "explicit"_s) { + paramEncoding = OPENSSL_EC_EXPLICIT_CURVE; + } else { + ERR::INVALID_ARG_VALUE(scope, globalObject, "options.paramEncoding"_s, paramEncodingValue); + return std::nullopt; + } + } else { + ERR::INVALID_ARG_VALUE(scope, globalObject, "options.paramEncoding"_s, paramEncodingValue); + return std::nullopt; + } + + JSString* namedCurveString = namedCurveValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + GCOwnedDataScope namedCurveView = namedCurveString->view(globalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + CString curveName = namedCurveView->utf8(); + + int curveNid = ncrypto::Ec::GetCurveIdFromName(curveName.data()); + if (curveNid == NID_undef) { + ERR::CRYPTO_INVALID_CURVE(scope, globalObject); + return std::nullopt; + } + + return EcKeyPairJobCtx( + curveNid, + paramEncoding, + config); +} diff --git a/src/bun.js/bindings/node/crypto/CryptoGenEcKeyPair.h b/src/bun.js/bindings/node/crypto/CryptoGenEcKeyPair.h new file mode 100644 index 0000000000..87dca842bf --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoGenEcKeyPair.h @@ -0,0 +1,34 @@ +#pragma once + +#include "root.h" +#include "ncrypto.h" +#include "CryptoGenKeyPair.h" + +namespace Bun { + +struct EcKeyPairJobCtx : KeyPairJobCtx { + WTF_MAKE_TZONE_ALLOCATED(EcKeyPairJobCtx); + +public: + EcKeyPairJobCtx(int curveNid, int paramEncoding, const KeyEncodingConfig& config) + : KeyPairJobCtx(config.publicKeyEncoding, config.privateKeyEncoding) + , m_curveNid(curveNid) + , m_paramEncoding(paramEncoding) + { + } + + void deinit(); + ncrypto::EVPKeyCtxPointer setup(); + static std::optional fromJS(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, const JSC::GCOwnedDataScope& typeView, JSC::JSValue optionsValue, const KeyEncodingConfig& config); + + int m_curveNid; + int m_paramEncoding; +}; + +struct EcKeyPairJob { + static EcKeyPairJob* create(JSC::JSGlobalObject*, EcKeyPairJobCtx&&, JSC::JSValue callback); + static void createAndSchedule(JSC::JSGlobalObject*, EcKeyPairJobCtx&&, JSC::JSValue callback); + void schedule(); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/CryptoGenKeyPair.cpp b/src/bun.js/bindings/node/crypto/CryptoGenKeyPair.cpp new file mode 100644 index 0000000000..f681752337 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoGenKeyPair.cpp @@ -0,0 +1,317 @@ +#include "CryptoGenKeyPair.h" +#include "helpers.h" +#include "NodeValidator.h" +#include "CryptoUtil.h" +#include "BunProcess.h" +#include "JSPublicKeyObject.h" +#include "JSPrivateKeyObject.h" +#include +#include "openssl/ec.h" +#include "CryptoGenRsaKeyPair.h" +#include "CryptoGenDsaKeyPair.h" +#include "CryptoGenEcKeyPair.h" +#include "CryptoGenNidKeyPair.h" +#include "CryptoGenDhKeyPair.h" + +using namespace JSC; + +namespace Bun { + +void KeyPairJobCtx::runTask(JSGlobalObject* globalObject, ncrypto::EVPKeyCtxPointer& keyCtx) +{ + EVP_PKEY* pkey = nullptr; + if (!EVP_PKEY_keygen(keyCtx.get(), &pkey)) { + m_opensslError = ERR_get_error(); + return; + } + + ncrypto::EVPKeyPointer key = ncrypto::EVPKeyPointer(pkey); + m_keyObj = KeyObject::create(CryptoKeyType::Private, WTFMove(key)); +} + +void KeyPairJobCtx::runFromJS(JSGlobalObject* lexicalGlobalObject, JSValue callback) +{ + VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + auto exceptionCallback = [lexicalGlobalObject, callback](JSValue exceptionValue) { + Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(callback), JSValue::encode(jsUndefined()), JSValue::encode(exceptionValue)); + }; + + if (!m_keyObj.data()) { + JSValue err = createCryptoError(lexicalGlobalObject, scope, m_opensslError, "key generation failed"_s); + Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(callback), JSValue::encode(jsUndefined()), JSValue::encode(err)); + return; + } + + JSValue publicKeyValue = m_keyObj.exportPublic(lexicalGlobalObject, scope, m_publicKeyEncoding); + if (scope.exception()) { + JSValue exceptionValue = scope.exception(); + scope.clearException(); + exceptionCallback(exceptionValue); + return; + } + + JSValue privateKeyValue = m_keyObj.exportPrivate(lexicalGlobalObject, scope, m_privateKeyEncoding); + if (scope.exception()) { + JSValue exceptionValue = scope.exception(); + scope.clearException(); + exceptionCallback(exceptionValue); + return; + } + + Bun__EventLoop__runCallback3( + lexicalGlobalObject, + JSValue::encode(callback), + JSValue::encode(jsUndefined()), + JSValue::encode(jsNull()), + JSValue::encode(publicKeyValue), + JSValue::encode(privateKeyValue)); +} + +KeyEncodingConfig parseKeyEncodingConfig(JSGlobalObject* globalObject, ThrowScope& scope, JSValue keyTypeValue, JSValue optionsValue) +{ + ncrypto::EVPKeyPointer::PublicKeyEncodingConfig publicKeyEncoding = {}; + ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig privateKeyEncoding = {}; + + JSValue publicKeyEncodingValue = jsUndefined(); + JSValue privateKeyEncodingValue = jsUndefined(); + + if (optionsValue.isObject()) { + publicKeyEncodingValue = optionsValue.get(globalObject, Identifier::fromString(globalObject->vm(), "publicKeyEncoding"_s)); + RETURN_IF_EXCEPTION(scope, {}); + + privateKeyEncodingValue = optionsValue.get(globalObject, Identifier::fromString(globalObject->vm(), "privateKeyEncoding"_s)); + RETURN_IF_EXCEPTION(scope, {}); + } + + if (publicKeyEncodingValue.isUndefinedOrNull()) { + // defaults and output key object + publicKeyEncoding.output_key_object = true; + } else if (JSObject* publicKeyEncodingObj = publicKeyEncodingValue.getObject()) { + parsePublicKeyEncoding(globalObject, scope, publicKeyEncodingObj, keyTypeValue, "publicKeyEncoding"_s, publicKeyEncoding); + RETURN_IF_EXCEPTION(scope, {}); + } else { + ERR::INVALID_ARG_VALUE(scope, globalObject, "options.publicKeyEncoding"_s, publicKeyEncodingValue); + return {}; + } + + if (privateKeyEncodingValue.isUndefinedOrNull()) { + // defaults and output key object + privateKeyEncoding.output_key_object = true; + } else if (JSObject* privateKeyEncodingObj = privateKeyEncodingValue.getObject()) { + parsePrivateKeyEncoding(globalObject, scope, privateKeyEncodingObj, keyTypeValue, "privateKeyEncoding"_s, privateKeyEncoding); + RETURN_IF_EXCEPTION(scope, {}); + } else { + ERR::INVALID_ARG_VALUE(scope, globalObject, "options.privateKeyEncoding"_s, privateKeyEncodingValue); + return {}; + } + + return { + publicKeyEncoding, + privateKeyEncoding, + }; +} + +JSC_DEFINE_HOST_FUNCTION(jsGenerateKeyPair, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSValue typeValue = callFrame->argument(0); + JSValue optionsValue = callFrame->argument(1); + JSValue callbackValue = callFrame->argument(2); + + if (optionsValue.isCallable()) { + callbackValue = optionsValue; + optionsValue = jsUndefined(); + } + + V::validateFunction(scope, globalObject, callbackValue, "callback"_s); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + V::validateString(scope, globalObject, typeValue, "type"_s); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + KeyEncodingConfig config = parseKeyEncodingConfig(globalObject, scope, typeValue, optionsValue); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + if (!optionsValue.isUndefined()) { + V::validateObject(scope, globalObject, optionsValue, "options"_s); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + } + + JSString* typeString = typeValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + GCOwnedDataScope typeView = typeString->view(globalObject); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + if (typeView == "rsa"_s || typeView == "rsa-pss"_s) { + std::optional ctx = RsaKeyPairJobCtx::fromJS(globalObject, scope, typeView, optionsValue, config); + ASSERT(ctx.has_value() == !scope.exception()); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + RsaKeyPairJob::createAndSchedule(globalObject, WTFMove(*ctx), callbackValue); + return JSValue::encode(jsUndefined()); + } + if (typeView == "dsa"_s) { + std::optional ctx = DsaKeyPairJobCtx::fromJS(globalObject, scope, typeView, optionsValue, config); + ASSERT(ctx.has_value() == !scope.exception()); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + DsaKeyPairJob::createAndSchedule(globalObject, WTFMove(*ctx), callbackValue); + return JSValue::encode(jsUndefined()); + } + if (typeView == "ec"_s) { + std::optional ctx = EcKeyPairJobCtx::fromJS(globalObject, scope, typeView, optionsValue, config); + ASSERT(ctx.has_value() == !scope.exception()); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + EcKeyPairJob::createAndSchedule(globalObject, WTFMove(*ctx), callbackValue); + return JSValue::encode(jsUndefined()); + } + // TODO: should just get `id` here + if (typeView == "ed25519"_s || typeView == "ed448"_s || typeView == "x25519"_s || typeView == "x448"_s) { + std::optional ctx = NidKeyPairJobCtx::fromJS(globalObject, scope, typeView, optionsValue, config); + ASSERT(ctx.has_value() == !scope.exception()); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + NidKeyPairJob::createAndSchedule(globalObject, WTFMove(*ctx), callbackValue); + return JSValue::encode(jsUndefined()); + } + if (typeView == "dh"_s) { + std::optional ctx = DhKeyPairJobCtx::fromJS(globalObject, scope, typeView, optionsValue, config); + ASSERT(ctx.has_value() == !scope.exception()); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + DhKeyPairJob::createAndSchedule(globalObject, WTFMove(*ctx), callbackValue); + return JSValue::encode(jsUndefined()); + } + + return ERR::INVALID_ARG_VALUE(scope, globalObject, "type"_s, typeValue, "must be a supported key type"_s); +} + +JSC_DEFINE_HOST_FUNCTION(jsGenerateKeyPairSync, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSValue typeValue = callFrame->argument(0); + JSValue optionsValue = callFrame->argument(1); + + V::validateString(scope, globalObject, typeValue, "type"_s); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + KeyEncodingConfig config = parseKeyEncodingConfig(globalObject, scope, typeValue, optionsValue); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + if (!optionsValue.isUndefined()) { + V::validateObject(scope, globalObject, optionsValue, "options"_s); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + } + + JSString* typeString = typeValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + GCOwnedDataScope typeView = typeString->view(globalObject); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + JSObject* result = JSC::constructEmptyObject(globalObject); + JSValue publicKeyValue = jsUndefined(); + JSValue privateKeyValue = jsUndefined(); + + if (typeView == "rsa"_s || typeView == "rsa-pss"_s) { + std::optional ctx = RsaKeyPairJobCtx::fromJS(globalObject, scope, typeView, optionsValue, config); + ASSERT(ctx.has_value() == !scope.exception()); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + ncrypto::EVPKeyCtxPointer keyCtx = ctx->setup(); + if (!keyCtx) { + throwCryptoError(globalObject, scope, ctx->err()); + return JSValue::encode({}); + } + ctx->runTask(globalObject, keyCtx); + if (!ctx->m_keyObj.data()) { + throwCryptoError(globalObject, scope, ctx->err()); + return JSValue::encode({}); + } + publicKeyValue = ctx->m_keyObj.exportPublic(globalObject, scope, ctx->m_publicKeyEncoding); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + privateKeyValue = ctx->m_keyObj.exportPrivate(globalObject, scope, ctx->m_privateKeyEncoding); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + } else if (typeView == "dsa"_s) { + auto ctx = DsaKeyPairJobCtx::fromJS(globalObject, scope, typeView, optionsValue, config); + ASSERT(ctx.has_value() == !scope.exception()); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + ncrypto::EVPKeyCtxPointer keyCtx = ctx->setup(); + if (!keyCtx) { + throwCryptoError(globalObject, scope, ctx->err()); + return JSValue::encode({}); + } + ctx->runTask(globalObject, keyCtx); + if (!ctx->m_keyObj.data()) { + throwCryptoError(globalObject, scope, ctx->err()); + return JSValue::encode({}); + } + publicKeyValue = ctx->m_keyObj.exportPublic(globalObject, scope, ctx->m_publicKeyEncoding); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + privateKeyValue = ctx->m_keyObj.exportPrivate(globalObject, scope, ctx->m_privateKeyEncoding); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + } else if (typeView == "ec"_s) { + auto ctx = EcKeyPairJobCtx::fromJS(globalObject, scope, typeView, optionsValue, config); + ASSERT(ctx.has_value() == !scope.exception()); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + ncrypto::EVPKeyCtxPointer keyCtx = ctx->setup(); + if (!keyCtx) { + throwCryptoError(globalObject, scope, ctx->err()); + return JSValue::encode({}); + } + ctx->runTask(globalObject, keyCtx); + if (!ctx->m_keyObj.data()) { + throwCryptoError(globalObject, scope, ctx->err()); + return JSValue::encode({}); + } + publicKeyValue = ctx->m_keyObj.exportPublic(globalObject, scope, ctx->m_publicKeyEncoding); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + privateKeyValue = ctx->m_keyObj.exportPrivate(globalObject, scope, ctx->m_privateKeyEncoding); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + } else if (typeView == "ed25519"_s || typeView == "ed448"_s || typeView == "x25519"_s || typeView == "x448"_s) { + auto ctx = NidKeyPairJobCtx::fromJS(globalObject, scope, typeView, optionsValue, config); + ASSERT(ctx.has_value() == !scope.exception()); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + ncrypto::EVPKeyCtxPointer keyCtx = ctx->setup(); + if (!keyCtx) { + throwCryptoError(globalObject, scope, ctx->err()); + return JSValue::encode({}); + } + ctx->runTask(globalObject, keyCtx); + if (!ctx->m_keyObj.data()) { + throwCryptoError(globalObject, scope, ctx->err()); + return JSValue::encode({}); + } + publicKeyValue = ctx->m_keyObj.exportPublic(globalObject, scope, ctx->m_publicKeyEncoding); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + privateKeyValue = ctx->m_keyObj.exportPrivate(globalObject, scope, ctx->m_privateKeyEncoding); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + } else if (typeView == "dh"_s) { + auto ctx = DhKeyPairJobCtx::fromJS(globalObject, scope, typeView, optionsValue, config); + ASSERT(ctx.has_value() == !scope.exception()); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + ncrypto::EVPKeyCtxPointer keyCtx = ctx->setup(); + if (!keyCtx) { + throwCryptoError(globalObject, scope, ctx->err()); + return JSValue::encode({}); + } + ctx->runTask(globalObject, keyCtx); + if (!ctx->m_keyObj.data()) { + throwCryptoError(globalObject, scope, ctx->err()); + return JSValue::encode({}); + } + publicKeyValue = ctx->m_keyObj.exportPublic(globalObject, scope, ctx->m_publicKeyEncoding); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + privateKeyValue = ctx->m_keyObj.exportPrivate(globalObject, scope, ctx->m_privateKeyEncoding); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + } else { + return ERR::INVALID_ARG_VALUE(scope, globalObject, "type"_s, typeValue, "must be a supported key type"_s); + } + + result->putDirect(vm, Identifier::fromString(vm, "publicKey"_s), publicKeyValue); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + result->putDirect(vm, Identifier::fromString(vm, "privateKey"_s), privateKeyValue); + return JSValue::encode(result); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/CryptoGenKeyPair.h b/src/bun.js/bindings/node/crypto/CryptoGenKeyPair.h new file mode 100644 index 0000000000..3bc4c5a244 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoGenKeyPair.h @@ -0,0 +1,41 @@ +#pragma once + +#include "root.h" +#include "ncrypto.h" +#include "KeyObject.h" + +namespace Bun { + +JSC_DECLARE_HOST_FUNCTION(jsGenerateKeyPair); +JSC_DECLARE_HOST_FUNCTION(jsGenerateKeyPairSync); + +struct KeyEncodingConfig { + ncrypto::EVPKeyPointer::PublicKeyEncodingConfig publicKeyEncoding; + ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig privateKeyEncoding; +}; + +struct KeyPairJobCtx { +public: + KeyPairJobCtx(ncrypto::EVPKeyPointer::PublicKeyEncodingConfig publicKeyEncoding, ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig privateKeyEncoding) + : m_publicKeyEncoding(publicKeyEncoding) + , m_privateKeyEncoding(privateKeyEncoding) + { + } + + void runTask(JSC::JSGlobalObject* globalObject, ncrypto::EVPKeyCtxPointer& ctx); + void runFromJS(JSC::JSGlobalObject* globalObject, JSC::JSValue callback); + void deinit(); + + int err() const { return m_opensslError; }; + + ncrypto::EVPKeyPointer::PublicKeyEncodingConfig m_publicKeyEncoding; + ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig m_privateKeyEncoding; + + // keyObj is set after work is done + KeyObject m_keyObj; + int m_opensslError = 0; +}; + +KeyEncodingConfig parseKeyEncodingConfig(JSC::JSGlobalObject*, JSC::ThrowScope&, JSC::JSValue keyTypeValue, JSC::JSValue optionsValue); + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/CryptoGenNidKeyPair.cpp b/src/bun.js/bindings/node/crypto/CryptoGenNidKeyPair.cpp new file mode 100644 index 0000000000..6d7a3c4bbd --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoGenNidKeyPair.cpp @@ -0,0 +1,81 @@ +#include "CryptoGenNidKeyPair.h" +#include "CryptoUtil.h" +#include "NodeValidator.h" +#include "ErrorCode.h" + +using namespace Bun; +using namespace JSC; + +extern "C" void Bun__NidKeyPairJobCtx__deinit(NidKeyPairJobCtx* ctx) +{ + ctx->deinit(); +} + +void NidKeyPairJobCtx::deinit() +{ + delete this; +} + +extern "C" void Bun__NidKeyPairJobCtx__runTask(NidKeyPairJobCtx* ctx, JSGlobalObject* globalObject) +{ + ncrypto::EVPKeyCtxPointer keyCtx = ctx->setup(); + if (!keyCtx) { + return; + } + ctx->runTask(globalObject, keyCtx); +} + +extern "C" void Bun__NidKeyPairJobCtx__runFromJS(NidKeyPairJobCtx* ctx, JSGlobalObject* globalObject, EncodedJSValue callback) +{ + ctx->runFromJS(globalObject, JSValue::decode(callback)); +} + +extern "C" NidKeyPairJob* Bun__NidKeyPairJob__create(JSGlobalObject* globalObject, NidKeyPairJobCtx* ctx, EncodedJSValue callback); +NidKeyPairJob* NidKeyPairJob::create(JSGlobalObject* globalObject, NidKeyPairJobCtx&& ctx, JSValue callback) +{ + NidKeyPairJobCtx* ctxCopy = new NidKeyPairJobCtx(WTFMove(ctx)); + return Bun__NidKeyPairJob__create(globalObject, ctxCopy, JSValue::encode(callback)); +} + +extern "C" void Bun__NidKeyPairJob__schedule(NidKeyPairJob* job); +void NidKeyPairJob::schedule() +{ + Bun__NidKeyPairJob__schedule(this); +} + +extern "C" void Bun__NidKeyPairJob__createAndSchedule(JSGlobalObject* globalObject, NidKeyPairJobCtx* ctx, EncodedJSValue callback); +void NidKeyPairJob::createAndSchedule(JSGlobalObject* globalObject, NidKeyPairJobCtx&& ctx, JSValue callback) +{ + NidKeyPairJobCtx* ctxCopy = new NidKeyPairJobCtx(WTFMove(ctx)); + Bun__NidKeyPairJob__createAndSchedule(globalObject, ctxCopy, JSValue::encode(callback)); +} + +ncrypto::EVPKeyCtxPointer NidKeyPairJobCtx::setup() +{ + ncrypto::EVPKeyCtxPointer ctx = ncrypto::EVPKeyCtxPointer::NewFromID(m_id); + if (!ctx.initForKeygen()) { + m_opensslError = ERR_get_error(); + return {}; + } + return ctx; +} + +std::optional NidKeyPairJobCtx::fromJS(JSGlobalObject* globalObject, ThrowScope& scope, const GCOwnedDataScope& typeView, JSValue optionsValue, const KeyEncodingConfig& config) +{ + int id; + if (typeView == "ed25519"_s) { + id = EVP_PKEY_ED25519; + } else if (typeView == "ed448"_s) { + id = EVP_PKEY_ED448; + } else if (typeView == "x25519"_s) { + id = EVP_PKEY_X25519; + } else if (typeView == "x448"_s) { + id = EVP_PKEY_X448; + } else { + UNREACHABLE(); + } + + return NidKeyPairJobCtx( + id, + config); +} diff --git a/src/bun.js/bindings/node/crypto/CryptoGenNidKeyPair.h b/src/bun.js/bindings/node/crypto/CryptoGenNidKeyPair.h new file mode 100644 index 0000000000..4f4e7207dc --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoGenNidKeyPair.h @@ -0,0 +1,32 @@ +#pragma once + +#include "root.h" +#include "ncrypto.h" +#include "CryptoGenKeyPair.h" + +namespace Bun { + +struct NidKeyPairJobCtx : KeyPairJobCtx { + WTF_MAKE_TZONE_ALLOCATED(NidKeyPairJobCtx); + +public: + NidKeyPairJobCtx(int id, const KeyEncodingConfig& config) + : KeyPairJobCtx(config.publicKeyEncoding, config.privateKeyEncoding) + , m_id(id) + { + } + + void deinit(); + ncrypto::EVPKeyCtxPointer setup(); + static std::optional fromJS(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, const JSC::GCOwnedDataScope& typeView, JSC::JSValue optionsValue, const KeyEncodingConfig& config); + + int m_id; +}; + +struct NidKeyPairJob { + static NidKeyPairJob* create(JSC::JSGlobalObject*, NidKeyPairJobCtx&&, JSC::JSValue callback); + static void createAndSchedule(JSC::JSGlobalObject*, NidKeyPairJobCtx&&, JSC::JSValue callback); + void schedule(); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/CryptoGenRsaKeyPair.cpp b/src/bun.js/bindings/node/crypto/CryptoGenRsaKeyPair.cpp new file mode 100644 index 0000000000..c09a531fda --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoGenRsaKeyPair.cpp @@ -0,0 +1,234 @@ +#include "CryptoGenRsaKeyPair.h" +#include "ErrorCode.h" +#include "NodeValidator.h" +#include "CryptoUtil.h" +#include "BunProcess.h" + +using namespace Bun; +using namespace JSC; + +extern "C" void Bun__RsaKeyPairJobCtx__deinit(RsaKeyPairJobCtx* ctx) +{ + ctx->deinit(); +} + +void RsaKeyPairJobCtx::deinit() +{ + delete this; +} + +extern "C" void Bun__RsaKeyPairJobCtx__runTask(RsaKeyPairJobCtx* ctx, JSGlobalObject* globalObject) +{ + ncrypto::EVPKeyCtxPointer keyCtx = ctx->setup(); + if (!keyCtx) { + return; + } + ctx->runTask(globalObject, keyCtx); +} + +extern "C" void Bun__RsaKeyPairJobCtx__runFromJS(RsaKeyPairJobCtx* ctx, JSGlobalObject* globalObject, EncodedJSValue callback) +{ + ctx->runFromJS(globalObject, JSValue::decode(callback)); +} + +extern "C" RsaKeyPairJob* Bun__RsaKeyPairJob__create(JSGlobalObject* globalObject, RsaKeyPairJobCtx* ctx, EncodedJSValue callback); +RsaKeyPairJob* RsaKeyPairJob::create(JSGlobalObject* globalObject, RsaKeyPairJobCtx&& ctx, JSValue callback) +{ + RsaKeyPairJobCtx* ctxCopy = new RsaKeyPairJobCtx(WTFMove(ctx)); + return Bun__RsaKeyPairJob__create(globalObject, ctxCopy, JSValue::encode(callback)); +} + +extern "C" void Bun__RsaKeyPairJob__schedule(RsaKeyPairJob* job); +void RsaKeyPairJob::schedule() +{ + Bun__RsaKeyPairJob__schedule(this); +} + +extern "C" void Bun__RsaKeyPairJob__createAndSchedule(JSGlobalObject* globalObject, RsaKeyPairJobCtx* ctx, EncodedJSValue callback); +void RsaKeyPairJob::createAndSchedule(JSGlobalObject* globalObject, RsaKeyPairJobCtx&& ctx, JSValue callback) +{ + RsaKeyPairJobCtx* ctxCopy = new RsaKeyPairJobCtx(WTFMove(ctx)); + Bun__RsaKeyPairJob__createAndSchedule(globalObject, ctxCopy, JSValue::encode(callback)); +} + +ncrypto::EVPKeyCtxPointer RsaKeyPairJobCtx::setup() +{ + ncrypto::EVPKeyCtxPointer ctx = ncrypto::EVPKeyCtxPointer::NewFromID(m_variant == RsaKeyVariant::RSA_PSS ? EVP_PKEY_RSA_PSS : EVP_PKEY_RSA); + if (!ctx || !ctx.initForKeygen() || !ctx.setRsaKeygenBits(m_modulusLength)) { + m_opensslError = ERR_get_error(); + return {}; + } + + if (m_exponent != ncrypto::EVPKeyCtxPointer::kDefaultRsaExponent) { + auto bn = ncrypto::BignumPointer::New(); + if (!bn.setWord(m_exponent) || !ctx.setRsaKeygenPubExp(WTFMove(bn))) { + m_opensslError = ERR_get_error(); + return {}; + } + } + + if (m_variant == RsaKeyVariant::RSA_PSS) { + if (m_md && !ctx.setRsaPssKeygenMd(m_md)) { + m_opensslError = ERR_get_error(); + return {}; + } + + auto& mgf1Md = m_mgfMd; + if (!mgf1Md && m_md) { + mgf1Md = m_md; + } + + if (mgf1Md && !ctx.setRsaPssKeygenMgf1Md(mgf1Md)) { + m_opensslError = ERR_get_error(); + return {}; + } + + int saltLength = m_saltLength; + if (saltLength < 0 && m_md) { + saltLength = m_md.size(); + } + + if (saltLength >= 0 && !ctx.setRsaPssSaltlen(saltLength)) { + m_opensslError = ERR_get_error(); + return {}; + } + } + + return ctx; +} + +std::optional RsaKeyPairJobCtx::fromJS(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, const JSC::GCOwnedDataScope& typeView, JSC::JSValue optionsValue, const KeyEncodingConfig& encodingConfig) +{ + VM& vm = globalObject->vm(); + + V::validateObject(scope, globalObject, optionsValue, "options"_s); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + JSValue modulusLengthValue = optionsValue.get(globalObject, Identifier::fromString(vm, "modulusLength"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + uint32_t modulusLength; + V::validateUint32(scope, globalObject, modulusLengthValue, "options.modulusLength"_s, jsUndefined(), &modulusLength); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + JSValue publicExponentValue = optionsValue.get(globalObject, Identifier::fromString(vm, "publicExponent"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + uint32_t publicExponent = 0x10001; + if (!publicExponentValue.isUndefinedOrNull()) { + V::validateUint32(scope, globalObject, publicExponentValue, "options.publicExponent"_s, jsUndefined(), &publicExponent); + RETURN_IF_EXCEPTION(scope, std::nullopt); + } + + if (typeView == "rsa"_s) { + return RsaKeyPairJobCtx( + RsaKeyVariant::RSA_SSA_PKCS1_v1_5, + modulusLength, + publicExponent, + encodingConfig); + } + + JSValue hashValue = optionsValue.get(globalObject, Identifier::fromString(vm, "hash"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + JSValue mgf1HashValue = optionsValue.get(globalObject, Identifier::fromString(vm, "mgf1Hash"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + JSValue hashAlgorithmValue = optionsValue.get(globalObject, Identifier::fromString(vm, "hashAlgorithm"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + JSValue mgf1HashAlgorithmValue = optionsValue.get(globalObject, Identifier::fromString(vm, "mgf1HashAlgorithm"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + JSValue saltLengthValue = optionsValue.get(globalObject, Identifier::fromString(vm, "saltLength"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + JSString* hashString = nullptr; + GCOwnedDataScope hashView(hashString, WTF::nullStringView()); + JSString* hashAlgorithmString = nullptr; + GCOwnedDataScope hashAlgorithmView(hashAlgorithmString, WTF::nullStringView()); + JSString* mgf1HashString = nullptr; + GCOwnedDataScope mgf1HashView(mgf1HashString, WTF::nullStringView()); + JSString* mgf1HashAlgorithmString = nullptr; + GCOwnedDataScope mgf1HashAlgorithmView(mgf1HashAlgorithmString, WTF::nullStringView()); + std::optional saltLength = std::nullopt; + + if (!saltLengthValue.isUndefined()) { + int32_t length; + V::validateInt32(scope, globalObject, saltLengthValue, "options.saltLength"_s, jsNumber(0), jsUndefined(), &length); + RETURN_IF_EXCEPTION(scope, std::nullopt); + saltLength = length; + } + if (!hashAlgorithmValue.isUndefined()) { + V::validateString(scope, globalObject, hashAlgorithmValue, "options.hashAlgorithm"_s); + RETURN_IF_EXCEPTION(scope, std::nullopt); + hashString = hashAlgorithmValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + hashView = hashString->view(globalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + } + if (!mgf1HashAlgorithmValue.isUndefined()) { + V::validateString(scope, globalObject, mgf1HashAlgorithmValue, "options.mgf1HashAlgorithm"_s); + RETURN_IF_EXCEPTION(scope, std::nullopt); + mgf1HashAlgorithmString = mgf1HashAlgorithmValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + mgf1HashView = mgf1HashAlgorithmString->view(globalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + } + if (!hashValue.isUndefined()) { + Bun::Process::emitWarning(globalObject, jsString(vm, makeString("\"options.hash\" is deprecated, use \"options.hashAlgorithm\" instead."_s)), jsString(vm, makeString("DeprecationWarning"_s)), jsString(vm, makeString("DEP0154"_s)), jsUndefined()); + V::validateString(scope, globalObject, hashValue, "options.hash"_s); + RETURN_IF_EXCEPTION(scope, std::nullopt); + hashString = hashValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + hashView = hashString->view(globalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + if (!hashAlgorithmView->isNull() && hashAlgorithmView != hashView) { + ERR::INVALID_ARG_VALUE(scope, globalObject, "options.hash"_s, hashValue); + return std::nullopt; + } + } + if (!mgf1HashValue.isUndefined()) { + Bun::Process::emitWarning(globalObject, jsString(vm, makeString("\"options.mgf1Hash\" is deprecated, use \"options.mgf1HashAlgorithm\" instead."_s)), jsString(vm, makeString("DeprecationWarning"_s)), jsString(vm, makeString("DEP0154"_s)), jsUndefined()); + V::validateString(scope, globalObject, mgf1HashValue, "options.mgf1Hash"_s); + RETURN_IF_EXCEPTION(scope, std::nullopt); + mgf1HashString = mgf1HashValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + mgf1HashView = mgf1HashString->view(globalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + if (!mgf1HashAlgorithmView->isNull() && mgf1HashAlgorithmView != mgf1HashView) { + ERR::INVALID_ARG_VALUE(scope, globalObject, "options.mgf1Hash"_s, mgf1HashValue); + return std::nullopt; + } + } + + GCOwnedDataScope hash = hashAlgorithmView->isNull() ? hashView : hashAlgorithmView; + GCOwnedDataScope mgf1Hash = mgf1HashAlgorithmView->isNull() ? mgf1HashView : mgf1HashAlgorithmView; + + ncrypto::Digest md = nullptr; + ncrypto::Digest mgf1Md = nullptr; + + if (!hash->isNull()) { + md = ncrypto::Digest::FromName(hash); + if (!md) { + ERR::CRYPTO_INVALID_DIGEST(scope, globalObject, hash); + return std::nullopt; + } + } + + if (!mgf1Hash->isNull()) { + mgf1Md = ncrypto::Digest::FromName(mgf1Hash); + if (!mgf1Md) { + ERR::CRYPTO_INVALID_DIGEST(scope, globalObject, "Invalid MGF1 digest: ", mgf1Hash); + return std::nullopt; + } + } + + if (saltLength && *saltLength < 0) { + ERR::OUT_OF_RANGE(scope, globalObject, "salt length is out of range"_s); + return std::nullopt; + } + + return RsaKeyPairJobCtx( + RsaKeyVariant::RSA_PSS, + modulusLength, + publicExponent, + saltLength, + md, + mgf1Md, + encodingConfig); +} diff --git a/src/bun.js/bindings/node/crypto/CryptoGenRsaKeyPair.h b/src/bun.js/bindings/node/crypto/CryptoGenRsaKeyPair.h new file mode 100644 index 0000000000..69f16eb51b --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoGenRsaKeyPair.h @@ -0,0 +1,68 @@ +#pragma once + +#include "root.h" +#include "ncrypto.h" +#include "CryptoGenKeyPair.h" + +namespace Bun { + +enum class RsaKeyVariant { + RSA_SSA_PKCS1_v1_5, + RSA_PSS, + RSA_OAEP, +}; + +struct RsaKeyPairJobCtx : KeyPairJobCtx { + WTF_MAKE_TZONE_ALLOCATED(RsaKeyPairJobCtx); + +public: + RsaKeyPairJobCtx(RsaKeyVariant variant, uint32_t modulusLength, uint32_t exponent, const KeyEncodingConfig& encodingConfig) + : KeyPairJobCtx(encodingConfig.publicKeyEncoding, encodingConfig.privateKeyEncoding) + , m_variant(variant) + , m_modulusLength(modulusLength) + , m_exponent(exponent) + { + } + + RsaKeyPairJobCtx(RsaKeyVariant variant, uint32_t modulusLength, uint32_t exponent, std::optional saltLength, ncrypto::Digest md, ncrypto::Digest mgfMd, const KeyEncodingConfig& encodingConfig) + : KeyPairJobCtx(encodingConfig.publicKeyEncoding, encodingConfig.privateKeyEncoding) + , m_variant(variant) + , m_modulusLength(modulusLength) + , m_exponent(exponent) + , m_saltLength(saltLength.value_or(-1)) + , m_md(md) + , m_mgfMd(mgfMd) + { + } + + RsaKeyPairJobCtx(RsaKeyPairJobCtx&& other) + : KeyPairJobCtx(other.m_publicKeyEncoding, other.m_privateKeyEncoding) + , m_variant(other.m_variant) + , m_modulusLength(other.m_modulusLength) + , m_exponent(other.m_exponent) + , m_saltLength(other.m_saltLength) + , m_md(other.m_md) + , m_mgfMd(other.m_mgfMd) + { + } + + void deinit(); + ncrypto::EVPKeyCtxPointer setup(); + static std::optional fromJS(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, const JSC::GCOwnedDataScope& typeView, JSC::JSValue optionsValue, const KeyEncodingConfig& config); + + RsaKeyVariant m_variant; + uint32_t m_modulusLength; + uint32_t m_exponent; + + int32_t m_saltLength; + ncrypto::Digest m_md = nullptr; + ncrypto::Digest m_mgfMd = nullptr; +}; + +struct RsaKeyPairJob { + static RsaKeyPairJob* create(JSC::JSGlobalObject*, RsaKeyPairJobCtx&&, JSC::JSValue callback); + static void createAndSchedule(JSC::JSGlobalObject*, RsaKeyPairJobCtx&&, JSC::JSValue callback); + void schedule(); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/CryptoHkdf.cpp b/src/bun.js/bindings/node/crypto/CryptoHkdf.cpp index f4613bd5b4..d9f0e83c01 100644 --- a/src/bun.js/bindings/node/crypto/CryptoHkdf.cpp +++ b/src/bun.js/bindings/node/crypto/CryptoHkdf.cpp @@ -1,20 +1,20 @@ #include "CryptoHkdf.h" #include "NodeValidator.h" #include "CryptoUtil.h" -#include "KeyObject.h" #include "JSCryptoKey.h" #include "CryptoKey.h" -#include "AsymmetricKeyValue.h" #include "JSBuffer.h" #include "ErrorCode.h" #include "BunString.h" +#include "JSKeyObject.h" using namespace JSC; -using namespace Bun; using namespace WebCore; using namespace ncrypto; -HkdfJobCtx::HkdfJobCtx(Digest digest, size_t length, WTF::Vector&& key, WTF::Vector&& info, WTF::Vector&& salt) +namespace Bun { + +HkdfJobCtx::HkdfJobCtx(Digest digest, size_t length, KeyObject&& key, WTF::Vector&& info, WTF::Vector&& salt) : m_digest(digest) , m_length(length) , m_key(WTFMove(key)) @@ -43,9 +43,11 @@ extern "C" void Bun__HkdfJobCtx__runTask(HkdfJobCtx* ctx, JSGlobalObject* lexica } void HkdfJobCtx::runTask(JSGlobalObject* lexicalGlobalObject) { + auto key = m_key.symmetricKey().span(); + auto keyBuf = ncrypto::Buffer { - .data = m_key.data(), - .len = m_key.size(), + .data = key.data(), + .len = key.size(), }; auto infoBuf = ncrypto::Buffer { .data = m_info.data(), @@ -129,79 +131,45 @@ void HkdfJob::createAndSchedule(JSGlobalObject* globalObject, HkdfJobCtx&& ctx, } // similar to prepareSecretKey -void prepareKey(JSGlobalObject* globalObject, ThrowScope& scope, Vector& out, JSValue key) +KeyObject prepareKey(JSGlobalObject* globalObject, ThrowScope& scope, JSValue key) { - VM& vm = globalObject->vm(); - - // Handle KeyObject (if not bufferOnly) - if (key.isObject()) { - JSObject* obj = key.getObject(); - auto& names = WebCore::builtinNames(vm); - - // Check for BunNativePtr on the object - if (auto val = obj->getIfPropertyExists(globalObject, names.bunNativePtrPrivateName())) { - if (auto* cryptoKey = jsDynamicCast(val.asCell())) { - - JSValue typeValue = obj->get(globalObject, vm.propertyNames->type); - RETURN_IF_EXCEPTION(scope, ); - - auto wrappedKey = cryptoKey->protectedWrapped(); - - if (!typeValue.isString()) { - Bun::ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, typeValue, "secret"_s); - return; - } - - WTF::String typeString = typeValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, ); - - if (wrappedKey->type() != CryptoKeyType::Secret || typeString != "secret"_s) { - Bun::ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, typeValue, "secret"_s); - return; - } - - auto keyData = getSymmetricKey(wrappedKey); - - if (UNLIKELY(!keyData)) { - Bun::ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, typeValue, "secret"_s); - return; - } - - out.append(keyData.value()); - return; - } - } + if (JSKeyObject* keyObject = jsDynamicCast(key)) { + // Node doesn't check for CryptoKeyType::Secret, so we don't either + return keyObject->handle(); } // Handle string or buffer if (key.isString()) { JSString* keyString = key.toString(globalObject); - RETURN_IF_EXCEPTION(scope, ); - + RETURN_IF_EXCEPTION(scope, {}); auto keyView = keyString->view(globalObject); - RETURN_IF_EXCEPTION(scope, ); + RETURN_IF_EXCEPTION(scope, {}); - JSValue buffer = JSValue::decode(WebCore::constructFromEncoding(globalObject, keyView, WebCore::BufferEncodingType::utf8)); + BufferEncodingType encoding = BufferEncodingType::utf8; + JSValue buffer = JSValue::decode(WebCore::constructFromEncoding(globalObject, keyView, encoding)); auto* view = jsDynamicCast(buffer); - out.append(view->span()); - return; + + Vector copy; + copy.append(view->span()); + return KeyObject::create(WTFMove(copy)); } // Handle ArrayBuffer types if (auto* view = jsDynamicCast(key)) { - out.append(view->span()); - return; + Vector copy; + copy.append(view->span()); + return KeyObject::create(WTFMove(copy)); } if (auto* buf = jsDynamicCast(key)) { - out.append(buf->impl()->span()); - return; + auto* impl = buf->impl(); + Vector copy; + copy.append(impl->span()); + return KeyObject::create(WTFMove(copy)); } - // If we got here, the key is not a valid type - WTF::String expectedTypes - = "string, SecretKeyObject, ArrayBuffer, TypedArray, DataView, or Buffer"_s; - Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "ikm"_s, expectedTypes, key); + ERR::INVALID_ARG_TYPE(scope, globalObject, "ikm"_s, "string or an instance of SecretKeyObject, ArrayBuffer, TypedArray, DataView, or Buffer"_s, key); + return {}; } void copyBufferOrString(JSGlobalObject* lexicalGlobalObject, ThrowScope& scope, JSValue value, const WTF::ASCIILiteral& name, WTF::Vector& buffer) @@ -235,8 +203,7 @@ std::optional HkdfJobCtx::fromJS(JSGlobalObject* lexicalGlobalObject // TODO(dylan-conway): All of these don't need to copy for sync mode - WTF::Vector keyData; - prepareKey(lexicalGlobalObject, scope, keyData, keyValue); + KeyObject keyObject = prepareKey(lexicalGlobalObject, scope, keyValue); RETURN_IF_EXCEPTION(scope, std::nullopt); WTF::Vector salt; @@ -269,7 +236,7 @@ std::optional HkdfJobCtx::fromJS(JSGlobalObject* lexicalGlobalObject return std::nullopt; } - return HkdfJobCtx(hash, length, WTFMove(keyData), WTFMove(info), WTFMove(salt)); + return HkdfJobCtx(hash, length, WTFMove(keyObject), WTFMove(info), WTFMove(salt)); } JSC_DEFINE_HOST_FUNCTION(jsHkdf, (JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) @@ -316,3 +283,5 @@ JSC_DEFINE_HOST_FUNCTION(jsHkdfSync, (JSGlobalObject * lexicalGlobalObject, JSC: return JSValue::encode(JSC::JSArrayBuffer::create(vm, globalObject->arrayBufferStructure(), buf.releaseNonNull())); } + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/CryptoHkdf.h b/src/bun.js/bindings/node/crypto/CryptoHkdf.h index 6c40d6c959..8689cea671 100644 --- a/src/bun.js/bindings/node/crypto/CryptoHkdf.h +++ b/src/bun.js/bindings/node/crypto/CryptoHkdf.h @@ -4,10 +4,9 @@ #include "helpers.h" #include "ncrypto.h" #include "CryptoUtil.h" +#include "KeyObject.h" -using namespace JSC; -using namespace Bun; -using namespace ncrypto; +namespace Bun { JSC_DECLARE_HOST_FUNCTION(jsHkdf); JSC_DECLARE_HOST_FUNCTION(jsHkdfSync); @@ -19,19 +18,19 @@ struct HkdfJobCtx { Async, }; - HkdfJobCtx(Digest digest, size_t length, WTF::Vector&& key, WTF::Vector&& info, WTF::Vector&& salt); + HkdfJobCtx(ncrypto::Digest digest, size_t length, KeyObject&& key, WTF::Vector&& info, WTF::Vector&& salt); HkdfJobCtx(HkdfJobCtx&&); ~HkdfJobCtx(); - static std::optional fromJS(JSGlobalObject*, CallFrame*, ThrowScope&, Mode); + static std::optional fromJS(JSC::JSGlobalObject*, JSC::CallFrame*, JSC::ThrowScope&, Mode); - void runTask(JSGlobalObject*); - void runFromJS(JSGlobalObject*, JSValue callback); + void runTask(JSC::JSGlobalObject*); + void runFromJS(JSC::JSGlobalObject*, JSC::JSValue callback); void deinit(); ncrypto::Digest m_digest; size_t m_length; - WTF::Vector m_key; + KeyObject m_key; WTF::Vector m_info; WTF::Vector m_salt; @@ -41,7 +40,9 @@ struct HkdfJobCtx { }; struct HkdfJob { - static HkdfJob* create(JSGlobalObject*, HkdfJobCtx&&, JSValue callback); - static void createAndSchedule(JSGlobalObject*, HkdfJobCtx&&, JSValue callback); + static HkdfJob* create(JSC::JSGlobalObject*, HkdfJobCtx&&, JSC::JSValue callback); + static void createAndSchedule(JSC::JSGlobalObject*, HkdfJobCtx&&, JSC::JSValue callback); void schedule(); }; + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/CryptoKeygen.cpp b/src/bun.js/bindings/node/crypto/CryptoKeygen.cpp new file mode 100644 index 0000000000..324fda5e45 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoKeygen.cpp @@ -0,0 +1,186 @@ +#include "CryptoKeygen.h" +#include "JSSecretKeyObject.h" +#include "CryptoUtil.h" +#include "helpers.h" +#include "NodeValidator.h" + +using namespace JSC; +using namespace WebCore; + +namespace Bun { + +SecretKeyJobCtx::SecretKeyJobCtx(size_t length) + : m_length(length) +{ +} + +SecretKeyJobCtx::SecretKeyJobCtx(SecretKeyJobCtx&& other) + : m_length(other.m_length) +{ +} + +extern "C" void Bun__SecretKeyJobCtx__runTask(SecretKeyJobCtx* ctx, JSGlobalObject* lexicalGlobalObject) +{ + ctx->runTask(lexicalGlobalObject); +} +void SecretKeyJobCtx::runTask(JSGlobalObject* lexicalGlobalObject) +{ + Vector key; + key.grow(m_length); + + if (!ncrypto::CSPRNG(key.data(), key.size())) { + return; + } + + m_result = WTFMove(key); +} + +extern "C" void Bun__SecretKeyJobCtx__runFromJS(SecretKeyJobCtx* ctx, JSGlobalObject* lexicalGlobalObject, JSC::JSValue callback) +{ + ctx->runFromJS(lexicalGlobalObject, callback); +} +void SecretKeyJobCtx::runFromJS(JSGlobalObject* lexicalGlobalObject, JSC::JSValue callback) +{ + VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + if (!m_result) { + JSObject* err = createError(lexicalGlobalObject, ErrorCode::ERR_CRYPTO_OPERATION_FAILED, "key generation failed"_s); + Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(callback), JSValue::encode(jsUndefined()), JSValue::encode(err)); + return; + } + + KeyObject keyObject = KeyObject::create(WTFMove(*m_result)); + + Structure* structure = globalObject->m_JSSecretKeyObjectClassStructure.get(lexicalGlobalObject); + JSSecretKeyObject* secretKey = JSSecretKeyObject::create(vm, structure, lexicalGlobalObject, WTFMove(keyObject)); + + Bun__EventLoop__runCallback2(lexicalGlobalObject, + JSValue::encode(callback), + JSValue::encode(jsUndefined()), + JSValue::encode(jsNull()), + JSValue::encode(secretKey)); +} + +extern "C" void Bun__SecretKeyJobCtx__deinit(SecretKeyJobCtx* ctx) +{ + ctx->deinit(); +} +void SecretKeyJobCtx::deinit() +{ + delete this; +} + +extern "C" SecretKeyJob* Bun__SecretKeyJob__create(JSC::JSGlobalObject*, SecretKeyJobCtx*, EncodedJSValue callback); +SecretKeyJob* SecretKeyJob::create(JSC::JSGlobalObject* lexicalGlobalObject, size_t length, JSC::JSValue callback) +{ + SecretKeyJobCtx* ctx = new SecretKeyJobCtx(length); + return Bun__SecretKeyJob__create(lexicalGlobalObject, ctx, JSValue::encode(callback)); +} + +extern "C" void Bun__SecretKeyJob__schedule(SecretKeyJob* job); +void SecretKeyJob::schedule() +{ + Bun__SecretKeyJob__schedule(this); +} + +extern "C" void Bun__SecretKeyJob__createAndSchedule(JSC::JSGlobalObject*, SecretKeyJobCtx*, EncodedJSValue callback); +void SecretKeyJob::createAndSchedule(JSC::JSGlobalObject* lexicalGlobalObject, SecretKeyJobCtx&& ctx, JSC::JSValue callback) +{ + SecretKeyJobCtx* ctxCopy = new SecretKeyJobCtx(WTFMove(ctx)); + return Bun__SecretKeyJob__createAndSchedule(lexicalGlobalObject, ctxCopy, JSValue::encode(callback)); +} + +std::optional SecretKeyJobCtx::fromJS(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, JSC::JSValue typeValue, JSC::JSValue optionsValue) +{ + VM& vm = globalObject->vm(); + + V::validateString(scope, globalObject, typeValue, "type"_s); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + V::validateObject(scope, globalObject, optionsValue, "options"_s); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + JSString* typeString = typeValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + GCOwnedDataScope typeView = typeString->view(globalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + + if (typeView == "hmac"_s) { + int32_t length; + JSValue lengthValue = optionsValue.get(globalObject, Identifier::fromString(vm, "length"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + V::validateInteger(scope, globalObject, lengthValue, "options.length"_s, jsNumber(8), jsNumber(std::numeric_limits::max()), &length); + RETURN_IF_EXCEPTION(scope, std::nullopt); + return SecretKeyJobCtx(length / CHAR_BIT); + } + + if (typeView == "aes"_s) { + int32_t length; + JSValue lengthValue = optionsValue.get(globalObject, Identifier::fromString(vm, "length"_s)); + RETURN_IF_EXCEPTION(scope, std::nullopt); + V::validateOneOf(scope, globalObject, "options.length"_s, lengthValue, std::array { 128, 192, 256 }, &length); + RETURN_IF_EXCEPTION(scope, std::nullopt); + return SecretKeyJobCtx(length / CHAR_BIT); + } + + ERR::INVALID_ARG_VALUE(scope, globalObject, "type"_s, typeValue, "must be a supported key type"_s); + return std::nullopt; +} + +JSC_DEFINE_HOST_FUNCTION(jsGenerateKey, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSValue typeValue = callFrame->argument(0); + JSValue optionsValue = callFrame->argument(1); + JSValue callbackValue = callFrame->argument(2); + + if (optionsValue.isCallable()) { + callbackValue = optionsValue; + optionsValue = jsUndefined(); + } + + V::validateFunction(scope, lexicalGlobalObject, callbackValue, "callback"_s); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + std::optional ctx = SecretKeyJobCtx::fromJS(lexicalGlobalObject, scope, typeValue, optionsValue); + ASSERT(ctx.has_value() == !scope.exception()); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + SecretKeyJob::createAndSchedule(lexicalGlobalObject, WTFMove(ctx.value()), callbackValue); + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsGenerateKeySync, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSValue typeValue = callFrame->argument(0); + JSValue optionsValue = callFrame->argument(1); + + std::optional ctx = SecretKeyJobCtx::fromJS(lexicalGlobalObject, scope, typeValue, optionsValue); + ASSERT(ctx.has_value() == !scope.exception()); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + ctx->runTask(lexicalGlobalObject); + + if (!ctx->m_result) { + return ERR::CRYPTO_OPERATION_FAILED(scope, lexicalGlobalObject, "key generation failed"_s); + } + + auto& result = ctx->m_result.value(); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + KeyObject keyObject = KeyObject::create(WTFMove(result)); + Structure* structure = globalObject->m_JSSecretKeyObjectClassStructure.get(lexicalGlobalObject); + JSSecretKeyObject* secretKey = JSSecretKeyObject::create(vm, structure, lexicalGlobalObject, WTFMove(keyObject)); + + return JSValue::encode(secretKey); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/CryptoKeygen.h b/src/bun.js/bindings/node/crypto/CryptoKeygen.h new file mode 100644 index 0000000000..e5ab150fcf --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoKeygen.h @@ -0,0 +1,37 @@ +#pragma once + +#include "root.h" +#include "ncrypto.h" + +namespace Bun { + +struct SecretKeyJobCtx { + SecretKeyJobCtx(size_t length); + SecretKeyJobCtx(SecretKeyJobCtx&&); + ~SecretKeyJobCtx() = default; + + void runTask(JSC::JSGlobalObject* lexicalGlobalObject); + void runFromJS(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue callback); + void deinit(); + + static std::optional fromJS(JSC::JSGlobalObject*, JSC::ThrowScope&, JSC::JSValue typeValue, JSC::JSValue optionsValue); + JSC::JSValue result() const; + + size_t m_length; + + std::optional> m_result { std::nullopt }; + + WTF_MAKE_TZONE_ALLOCATED(SecretKeyJobCtx); +}; + +struct SecretKeyJob { + static SecretKeyJob* create(JSC::JSGlobalObject*, size_t length, JSC::JSValue callback); + static void createAndSchedule(JSC::JSGlobalObject*, SecretKeyJobCtx&&, JSC::JSValue callback); + + void schedule(); +}; + +JSC_DECLARE_HOST_FUNCTION(jsGenerateKey); +JSC_DECLARE_HOST_FUNCTION(jsGenerateKeySync); + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/CryptoKeys.cpp b/src/bun.js/bindings/node/crypto/CryptoKeys.cpp new file mode 100644 index 0000000000..8b90e6b89e --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoKeys.cpp @@ -0,0 +1,101 @@ +#include "CryptoKeys.h" +#include "NodeValidator.h" +#include "ErrorCode.h" +#include "CryptoUtil.h" +#include "JSSecretKeyObject.h" +#include "JSPublicKeyObject.h" +#include "JSPrivateKeyObject.h" + +using namespace JSC; + +namespace Bun { + +JSC_DEFINE_HOST_FUNCTION(jsCreateSecretKey, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + JSValue keyValue = callFrame->argument(0); + JSValue encodingValue = callFrame->argument(1); + + KeyObject keyObject = KeyObject::prepareSecretKey(lexicalGlobalObject, scope, keyValue, encodingValue, true); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + Structure* structure = globalObject->m_JSSecretKeyObjectClassStructure.get(lexicalGlobalObject); + JSSecretKeyObject* secretKey = JSSecretKeyObject::create(vm, structure, lexicalGlobalObject, WTFMove(keyObject)); + + return JSValue::encode(secretKey); +} + +JSC_DEFINE_HOST_FUNCTION(jsCreatePublicKey, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + JSValue keyValue = callFrame->argument(0); + + auto prepareResult = KeyObject::prepareAsymmetricKey(lexicalGlobalObject, scope, keyValue, KeyObject::PrepareAsymmetricKeyMode::CreatePublic); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + KeyObject keyObject; + + if (prepareResult.keyData) { + RefPtr keyData = *prepareResult.keyData; + keyObject = KeyObject::create(CryptoKeyType::Public, WTFMove(keyData)); + } else { + keyObject = KeyObject::getPublicOrPrivateKey( + globalObject, + scope, + prepareResult.keyDataView, + CryptoKeyType::Public, + prepareResult.formatType, + prepareResult.encodingType, + prepareResult.cipher, + WTFMove(prepareResult.passphrase)); + RETURN_IF_EXCEPTION(scope, {}); + } + + Structure* structure = globalObject->m_JSPublicKeyObjectClassStructure.get(lexicalGlobalObject); + JSPublicKeyObject* publicKey = JSPublicKeyObject::create(vm, structure, lexicalGlobalObject, WTFMove(keyObject)); + + return JSValue::encode(publicKey); +} + +JSC_DEFINE_HOST_FUNCTION(jsCreatePrivateKey, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + JSValue keyValue = callFrame->argument(0); + + auto prepareResult = KeyObject::prepareAsymmetricKey(lexicalGlobalObject, scope, keyValue, KeyObject::PrepareAsymmetricKeyMode::CreatePrivate); + RETURN_IF_EXCEPTION(scope, {}); + + KeyObject keyObject; + + if (prepareResult.keyData) { + auto keyData = *prepareResult.keyData; + keyObject = KeyObject::create(CryptoKeyType::Private, WTFMove(keyData)); + } else { + keyObject = KeyObject::getPublicOrPrivateKey( + globalObject, + scope, + prepareResult.keyDataView, + CryptoKeyType::Private, + prepareResult.formatType, + prepareResult.encodingType, + prepareResult.cipher, + WTFMove(prepareResult.passphrase)); + RETURN_IF_EXCEPTION(scope, {}); + } + + Structure* structure = globalObject->m_JSPrivateKeyObjectClassStructure.get(lexicalGlobalObject); + JSPrivateKeyObject* privateKey = JSPrivateKeyObject::create(vm, structure, lexicalGlobalObject, WTFMove(keyObject)); + + return JSValue::encode(privateKey); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/CryptoKeys.h b/src/bun.js/bindings/node/crypto/CryptoKeys.h new file mode 100644 index 0000000000..32505f32ac --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoKeys.h @@ -0,0 +1,11 @@ +#pragma once + +#include "root.h" + +namespace Bun { + +JSC_DECLARE_HOST_FUNCTION(jsCreateSecretKey); +JSC_DECLARE_HOST_FUNCTION(jsCreatePublicKey); +JSC_DECLARE_HOST_FUNCTION(jsCreatePrivateKey); + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/CryptoPrimes.cpp b/src/bun.js/bindings/node/crypto/CryptoPrimes.cpp index fa0bd1d1be..3051640da7 100644 --- a/src/bun.js/bindings/node/crypto/CryptoPrimes.cpp +++ b/src/bun.js/bindings/node/crypto/CryptoPrimes.cpp @@ -1,10 +1,14 @@ #include "CryptoPrimes.h" -#include "KeyObject.h" #include "ErrorCode.h" #include "helpers.h" #include "CryptoUtil.h" #include "NodeValidator.h" +namespace Bun { + +using namespace ncrypto; +using namespace JSC; + CheckPrimeJobCtx::CheckPrimeJobCtx(ncrypto::BignumPointer candidate, int32_t checks) : m_candidate(WTFMove(candidate)) , m_checks(checks) @@ -79,7 +83,7 @@ JSC_DEFINE_HOST_FUNCTION(jsCheckPrimeSync, (JSC::JSGlobalObject * lexicalGlobalO RETURN_IF_EXCEPTION(scope, {}); } - auto* candidateView = getArrayBufferOrView(lexicalGlobalObject, scope, candidateValue, "candidate"_s, jsUndefined()); + auto candidateView = getArrayBufferOrView2(lexicalGlobalObject, scope, candidateValue, "candidate"_s, jsUndefined()); RETURN_IF_EXCEPTION(scope, {}); JSValue optionsValue = callFrame->argument(1); @@ -100,7 +104,7 @@ JSC_DEFINE_HOST_FUNCTION(jsCheckPrimeSync, (JSC::JSGlobalObject * lexicalGlobalO } } - ncrypto::BignumPointer candidate = ncrypto::BignumPointer(reinterpret_cast(candidateView->vector()), candidateView->byteLength()); + ncrypto::BignumPointer candidate = ncrypto::BignumPointer(candidateView->data(), candidateView->size()); if (!candidate) { throwCryptoError(lexicalGlobalObject, scope, ERR_get_error(), "BignumPointer"_s); return JSValue::encode({}); @@ -125,10 +129,8 @@ JSC_DEFINE_HOST_FUNCTION(jsCheckPrime, (JSC::JSGlobalObject * lexicalGlobalObjec RETURN_IF_EXCEPTION(scope, JSValue::encode({})); } - auto* candidateView = jsDynamicCast(candidateValue); - if (!candidateView) { - return ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "candidate"_s, "ArrayBuffer, TypedArray, Buffer, DataView, or bigint"_s, candidateValue); - } + auto candidateView = getArrayBufferOrView2(lexicalGlobalObject, scope, candidateValue, "candidate"_s, jsUndefined()); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); JSValue optionsValue = callFrame->argument(1); JSValue callback = callFrame->argument(2); @@ -157,7 +159,7 @@ JSC_DEFINE_HOST_FUNCTION(jsCheckPrime, (JSC::JSGlobalObject * lexicalGlobalObjec } } - ncrypto::BignumPointer candidate = ncrypto::BignumPointer(reinterpret_cast(candidateView->vector()), candidateView->byteLength()); + ncrypto::BignumPointer candidate = ncrypto::BignumPointer(candidateView->data(), candidateView->size()); if (!candidate) { throwCryptoError(lexicalGlobalObject, scope, ERR_get_error(), "BignumPointer"_s); return JSValue::encode({}); @@ -198,42 +200,31 @@ extern "C" void Bun__GeneratePrimeJobCtx__runFromJS(GeneratePrimeJobCtx* ctx, JS { ctx->runFromJS(lexicalGlobalObject, JSValue::decode(callback)); } -void GeneratePrimeJobCtx::runFromJS(JSGlobalObject* lexicalGlobalObject, JSValue callback) +void GeneratePrimeJobCtx::runFromJS(JSGlobalObject* globalObject, JSValue callback) { - auto& vm = lexicalGlobalObject->vm(); + auto& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - if (m_bigint) { - ncrypto::DataPointer primeHex = m_prime.toHex(); - if (!primeHex) { - JSObject* err = createOutOfMemoryError(lexicalGlobalObject); - Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(callback), JSValue::encode(jsUndefined()), JSValue::encode(err)); - return; - } - - JSValue result = JSBigInt::parseInt(lexicalGlobalObject, vm, primeHex.span(), 16, JSBigInt::ErrorParseMode::IgnoreExceptions, JSBigInt::ParseIntSign::Unsigned); - if (result.isEmpty()) { - JSObject* err = createError(lexicalGlobalObject, ErrorCode::ERR_CRYPTO_OPERATION_FAILED, "could not generate prime"_s); - Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(callback), JSValue::encode(jsUndefined()), JSValue::encode(err)); - return; - } - - Bun__EventLoop__runCallback2(lexicalGlobalObject, JSValue::encode(callback), JSValue::encode(jsUndefined()), JSValue::encode(jsUndefined()), JSValue::encode(result)); + JSValue result = GeneratePrimeJob::result(globalObject, scope, m_prime, m_bigint); + ASSERT(result.isEmpty() == !!scope.exception()); + if (scope.exception()) { + auto* err = scope.exception(); + scope.clearException(); + Bun__EventLoop__runCallback1( + globalObject, + JSValue::encode(callback), + JSValue::encode(jsUndefined()), + JSValue::encode(err)); return; } - auto* globalObject = defaultGlobalObject(lexicalGlobalObject); - - JSC::JSUint8Array* result = JSC::JSUint8Array::createUninitialized(lexicalGlobalObject, globalObject->JSBufferSubclassStructure(), m_prime.byteLength()); - if (!result) { - JSObject* err = createOutOfMemoryError(lexicalGlobalObject); - Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(callback), JSValue::encode(jsUndefined()), JSValue::encode(err)); - return; - } - - ncrypto::BignumPointer::EncodePaddedInto(m_prime.get(), reinterpret_cast(result->vector()), result->byteLength()); - - Bun__EventLoop__runCallback2(lexicalGlobalObject, JSValue::encode(callback), JSValue::encode(jsUndefined()), JSValue::encode(jsUndefined()), JSValue::encode(result)); + Bun__EventLoop__runCallback2( + globalObject, + JSValue::encode(callback), + JSValue::encode(jsUndefined()), + JSValue::encode(jsUndefined()), + JSValue::encode(result)); + return; } extern "C" void Bun__GeneratePrimeJobCtx__deinit(GeneratePrimeJobCtx* ctx) @@ -265,6 +256,39 @@ void GeneratePrimeJob::createAndSchedule(JSGlobalObject* globalObject, int32_t s Bun__GeneratePrimeJob__createAndSchedule(globalObject, ctx, JSValue::encode(callback)); } +JSValue GeneratePrimeJob::result(JSGlobalObject* globalObject, JSC::ThrowScope& scope, const ncrypto::BignumPointer& prime, bool bigint) +{ + VM& vm = globalObject->vm(); + + if (bigint) { + ncrypto::DataPointer primeHex = prime.toHex(); + if (!primeHex) { + throwOutOfMemoryError(globalObject, scope); + return {}; + } + + JSValue result = JSBigInt::parseInt(globalObject, vm, primeHex.span(), 16, JSBigInt::ErrorParseMode::IgnoreExceptions, JSBigInt::ParseIntSign::Unsigned); + if (result.isEmpty()) { + ERR::CRYPTO_OPERATION_FAILED(scope, globalObject, "could not generate prime"_s); + return {}; + } + + return result; + } + + ArrayBufferContents contents; + + auto buf = ArrayBuffer::tryCreateUninitialized(prime.byteLength(), 1); + if (!buf) { + throwOutOfMemoryError(globalObject, scope); + return {}; + } + + BignumPointer::EncodePaddedInto(prime.get(), reinterpret_cast(buf->data()), buf->byteLength()); + + return JSArrayBuffer::create(vm, globalObject->arrayBufferStructure(), WTFMove(buf)); +} + JSC_DEFINE_HOST_FUNCTION(jsGeneratePrime, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); @@ -470,31 +494,12 @@ JSC_DEFINE_HOST_FUNCTION(jsGeneratePrimeSync, (JSC::JSGlobalObject * lexicalGlob return ERR::CRYPTO_OPERATION_FAILED(scope, lexicalGlobalObject, "could not generate prime"_s); } - auto* globalObject = defaultGlobalObject(lexicalGlobalObject); - prime.generate({ .bits = size, .safe = safe, .add = add, .rem = rem }, [](int32_t a, int32_t b) -> bool { // TODO(dylan-conway): ideally we check for !vm->isShuttingDown() here return true; }); - if (bigint) { - ncrypto::DataPointer primeHex = prime.toHex(); - if (!primeHex) { - throwOutOfMemoryError(lexicalGlobalObject, scope, "could not generate prime"_s); - return JSValue::encode({}); - } - - return JSValue::encode(JSBigInt::parseInt(lexicalGlobalObject, vm, primeHex.span(), 16, JSBigInt::ErrorParseMode::ThrowExceptions, - JSBigInt::ParseIntSign::Unsigned)); - } - - JSC::JSUint8Array* result = JSC::JSUint8Array::createUninitialized(lexicalGlobalObject, globalObject->JSBufferSubclassStructure(), prime.byteLength()); - if (!result) { - throwOutOfMemoryError(lexicalGlobalObject, scope, "could not generate prime"_s); - return JSValue::encode({}); - } - - ncrypto::BignumPointer::EncodePaddedInto(prime.get(), reinterpret_cast(result->vector()), result->byteLength()); - - return JSValue::encode(result); + return JSValue::encode(GeneratePrimeJob::result(lexicalGlobalObject, scope, prime, bigint)); } + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/CryptoPrimes.h b/src/bun.js/bindings/node/crypto/CryptoPrimes.h index 4a82d28008..1f8f36fd60 100644 --- a/src/bun.js/bindings/node/crypto/CryptoPrimes.h +++ b/src/bun.js/bindings/node/crypto/CryptoPrimes.h @@ -4,15 +4,14 @@ #include "helpers.h" #include "ncrypto.h" -using namespace JSC; -using namespace Bun; +namespace Bun { struct CheckPrimeJobCtx { CheckPrimeJobCtx(ncrypto::BignumPointer candidate, int32_t checks); ~CheckPrimeJobCtx(); - void runTask(JSGlobalObject* lexicalGlobalObject); - void runFromJS(JSGlobalObject* lexicalGlobalObject, JSValue callback); + void runTask(JSC::JSGlobalObject* lexicalGlobalObject); + void runFromJS(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue callback); void deinit(); int32_t m_checks; @@ -25,8 +24,8 @@ struct CheckPrimeJobCtx { // Opaque struct created zig land struct CheckPrimeJob { - static CheckPrimeJob* create(JSGlobalObject*, ncrypto::BignumPointer candidate, int32_t checks, JSValue callback); - static void createAndSchedule(JSGlobalObject* globalObject, ncrypto::BignumPointer candidate, int32_t checks, JSValue callback); + static CheckPrimeJob* create(JSC::JSGlobalObject*, ncrypto::BignumPointer candidate, int32_t checks, JSC::JSValue callback); + static void createAndSchedule(JSC::JSGlobalObject* globalObject, ncrypto::BignumPointer candidate, int32_t checks, JSC::JSValue callback); void schedule(); }; @@ -35,8 +34,8 @@ struct GeneratePrimeJobCtx { GeneratePrimeJobCtx(int32_t size, bool safe, ncrypto::BignumPointer prime, ncrypto::BignumPointer add, ncrypto::BignumPointer rem, bool bigint); ~GeneratePrimeJobCtx(); - void runTask(JSGlobalObject* lexicalGlobalObject); - void runFromJS(JSGlobalObject* lexicalGlobalObject, JSValue callback); + void runTask(JSC::JSGlobalObject* lexicalGlobalObject); + void runFromJS(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue callback); void deinit(); int32_t m_size; @@ -51,8 +50,10 @@ struct GeneratePrimeJobCtx { // Opaque struct created zig land struct GeneratePrimeJob { - static GeneratePrimeJob* create(JSGlobalObject*, int32_t size, bool safe, ncrypto::BignumPointer prime, ncrypto::BignumPointer add, ncrypto::BignumPointer rem, bool bigint, JSValue callback); - static void createAndSchedule(JSGlobalObject*, int32_t size, bool safe, ncrypto::BignumPointer prime, ncrypto::BignumPointer add, ncrypto::BignumPointer rem, bool bigint, JSValue callback); + static GeneratePrimeJob* create(JSC::JSGlobalObject*, int32_t size, bool safe, ncrypto::BignumPointer prime, ncrypto::BignumPointer add, ncrypto::BignumPointer rem, bool bigint, JSC::JSValue callback); + static void createAndSchedule(JSC::JSGlobalObject*, int32_t size, bool safe, ncrypto::BignumPointer prime, ncrypto::BignumPointer add, ncrypto::BignumPointer rem, bool bigint, JSC::JSValue callback); + + static JSC::JSValue result(JSC::JSGlobalObject*, JSC::ThrowScope&, const ncrypto::BignumPointer& prime, bool bigint); void schedule(); }; @@ -61,3 +62,5 @@ JSC_DECLARE_HOST_FUNCTION(jsCheckPrime); JSC_DECLARE_HOST_FUNCTION(jsCheckPrimeSync); JSC_DECLARE_HOST_FUNCTION(jsGeneratePrime); JSC_DECLARE_HOST_FUNCTION(jsGeneratePrimeSync); + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/CryptoSignJob.cpp b/src/bun.js/bindings/node/crypto/CryptoSignJob.cpp new file mode 100644 index 0000000000..336294c3c4 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoSignJob.cpp @@ -0,0 +1,387 @@ +#include "CryptoSignJob.h" +#include "NodeValidator.h" +#include "KeyObject.h" +#include "JSVerify.h" + +using namespace JSC; +using namespace ncrypto; +using namespace WebCore; + +namespace Bun { + +JSC_DEFINE_HOST_FUNCTION(jsVerifyOneShot, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSValue algorithmValue = callFrame->argument(0); + JSValue dataValue = callFrame->argument(1); + JSValue keyValue = callFrame->argument(2); + JSValue signatureValue = callFrame->argument(3); + JSValue callbackValue = callFrame->argument(4); + + auto ctx = SignJobCtx::fromJS(lexicalGlobalObject, scope, SignJobCtx::Mode::Verify, algorithmValue, dataValue, keyValue, signatureValue, callbackValue); + RETURN_IF_EXCEPTION(scope, {}); + + if (!callbackValue.isUndefined()) { + SignJob::createAndSchedule(lexicalGlobalObject, WTFMove(*ctx), callbackValue); + return JSValue::encode(jsUndefined()); + } + + ctx->runTask(lexicalGlobalObject); + + if (!ctx->m_verifyResult) { + throwCryptoError(lexicalGlobalObject, scope, ctx->m_opensslError, "verify operation failed"_s); + return JSValue::encode({}); + } + + return JSValue::encode(jsBoolean(*ctx->m_verifyResult)); +} + +JSC_DEFINE_HOST_FUNCTION(jsSignOneShot, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSValue algorithmValue = callFrame->argument(0); + JSValue dataValue = callFrame->argument(1); + JSValue keyValue = callFrame->argument(2); + JSValue callbackValue = callFrame->argument(3); + + auto ctx = SignJobCtx::fromJS(lexicalGlobalObject, scope, SignJobCtx::Mode::Sign, algorithmValue, dataValue, keyValue, jsUndefined(), callbackValue); + RETURN_IF_EXCEPTION(scope, {}); + + if (!callbackValue.isUndefined()) { + SignJob::createAndSchedule(lexicalGlobalObject, WTFMove(*ctx), callbackValue); + return JSValue::encode(jsUndefined()); + } + + ctx->runTask(lexicalGlobalObject); + + if (!ctx->m_signResult) { + throwCryptoError(lexicalGlobalObject, scope, ctx->m_opensslError, "sign operation failed"_s); + return JSValue::encode({}); + } + + auto& result = ctx->m_signResult.value(); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + auto sigBuf = ArrayBuffer::createUninitialized(result.size(), 1); + memcpy(sigBuf->data(), result.data(), result.size()); + auto* signature = JSUint8Array::create(lexicalGlobalObject, globalObject->JSBufferSubclassStructure(), WTFMove(sigBuf), 0, result.size()); + return JSValue::encode(signature); +} + +extern "C" void Bun__SignJobCtx__deinit(SignJobCtx* ctx) +{ + ctx->deinit(); +} +void SignJobCtx::deinit() +{ + delete this; +} + +extern "C" void Bun__SignJobCtx__runTask(SignJobCtx* ctx, JSGlobalObject* globalObject) +{ + ctx->runTask(globalObject); +} +void SignJobCtx::runTask(JSGlobalObject* globalObject) +{ + ClearErrorOnReturn clearError; + auto context = EVPMDCtxPointer::New(); + if (UNLIKELY(!context)) { + m_opensslError = ERR_get_error(); + return; + } + + const auto& key = m_keyData->asymmetricKey; + + std::optional ctx; + switch (m_mode) { + case Mode::Sign: + ctx = context.signInit(key, m_digest); + break; + case Mode::Verify: + ctx = context.verifyInit(key, m_digest); + break; + } + + if (!ctx) { + m_opensslError = ERR_get_error(); + return; + } + + int32_t padding = m_padding.value_or(key.getDefaultSignPadding()); + + if (key.isRsaVariant() && !EVPKeyCtxPointer::setRsaPadding(*ctx, padding, m_saltLength)) { + m_opensslError = ERR_get_error(); + return; + } + + switch (m_mode) { + case Mode::Sign: { + auto dataBuf = ncrypto::Buffer { + .data = m_data.data(), + .len = m_data.size(), + }; + + if (key.isOneShotVariant()) { + auto data = context.signOneShot(dataBuf); + if (!data) { + m_opensslError = ERR_get_error(); + return; + } + + m_signResult = ByteSource::allocated(data.release()); + } else { + auto data = context.sign(dataBuf); + if (!data) { + m_opensslError = ERR_get_error(); + return; + } + + auto bs = ByteSource::allocated(data.release()); + + if (key.isSigVariant() && m_dsaSigEnc == DSASigEnc::P1363) { + uint32_t n = key.getBytesOfRS().value_or(NoDsaSignature); + if (n == NoDsaSignature) { + m_opensslError = ERR_get_error(); + return; + } + + auto p1363Buffer = DataPointer::Alloc(n * 2); + if (!p1363Buffer) { + m_opensslError = ERR_get_error(); + return; + } + + p1363Buffer.zero(); + + ncrypto::Buffer sigBuf { + .data = reinterpret_cast(bs.data()), + .len = bs.size(), + }; + + if (!ncrypto::extractP1363(sigBuf, reinterpret_cast(p1363Buffer.get()), n)) { + m_opensslError = ERR_get_error(); + return; + } + + m_signResult = ByteSource::allocated(p1363Buffer.release()); + } else { + m_signResult = WTFMove(bs); + } + } + break; + } + case Mode::Verify: { + auto dataBuf = ncrypto::Buffer { + .data = m_data.data(), + .len = m_data.size(), + }; + auto sigBuf = ncrypto::Buffer { + .data = m_signature.data(), + .len = m_signature.size(), + }; + m_verifyResult = context.verify(dataBuf, sigBuf); + if (!m_verifyResult.has_value()) { + m_opensslError = ERR_get_error(); + } + break; + } + } +} + +extern "C" void Bun__SignJobCtx__runFromJS(SignJobCtx* ctx, JSGlobalObject* globalObject, EncodedJSValue callback) +{ + ctx->runFromJS(globalObject, JSValue::decode(callback)); +} +void SignJobCtx::runFromJS(JSGlobalObject* lexicalGlobalObject, JSValue callback) +{ + VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + switch (m_mode) { + case Mode::Sign: { + if (!m_signResult) { + JSValue err = createCryptoError(lexicalGlobalObject, scope, m_opensslError, "sign operation failed"_s); + Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(callback), JSValue::encode(jsUndefined()), JSValue::encode(err)); + return; + } + + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + auto sigBuf = ArrayBuffer::createUninitialized(m_signResult->size(), 1); + memcpy(sigBuf->data(), m_signResult->data(), m_signResult->size()); + auto* signature = JSUint8Array::create(lexicalGlobalObject, globalObject->JSBufferSubclassStructure(), WTFMove(sigBuf), 0, m_signResult->size()); + + Bun__EventLoop__runCallback2( + lexicalGlobalObject, + JSValue::encode(callback), + JSValue::encode(jsUndefined()), + JSValue::encode(jsNull()), + JSValue::encode(signature)); + + break; + } + case Mode::Verify: { + if (!m_verifyResult) { + JSValue err = createCryptoError(lexicalGlobalObject, scope, m_opensslError, "verify operation failed"_s); + Bun__EventLoop__runCallback1(lexicalGlobalObject, JSValue::encode(callback), JSValue::encode(jsUndefined()), JSValue::encode(err)); + return; + } + + Bun__EventLoop__runCallback2( + lexicalGlobalObject, + JSValue::encode(callback), + JSValue::encode(jsUndefined()), + JSValue::encode(jsNull()), + JSValue::encode(jsBoolean(*m_verifyResult))); + break; + } + } +} + +extern "C" SignJob* Bun__SignJob__create(JSGlobalObject* globalObject, SignJobCtx* ctx, EncodedJSValue callback); +SignJob* SignJob::create(JSGlobalObject* globalObject, SignJobCtx&& ctx, JSValue callback) +{ + SignJobCtx* ctxCopy = new SignJobCtx(WTFMove(ctx)); + return Bun__SignJob__create(globalObject, ctxCopy, JSValue::encode(callback)); +} + +extern "C" void Bun__SignJob__schedule(SignJob* job); +void SignJob::schedule() +{ + Bun__SignJob__schedule(this); +} + +extern "C" void Bun__SignJob__createAndSchedule(JSGlobalObject* globalObject, SignJobCtx* ctx, EncodedJSValue callback); +void SignJob::createAndSchedule(JSGlobalObject* globalObject, SignJobCtx&& ctx, JSValue callback) +{ + SignJobCtx* ctxCopy = new SignJobCtx(WTFMove(ctx)); + Bun__SignJob__createAndSchedule(globalObject, ctxCopy, JSValue::encode(callback)); +} + +std::optional getPadding(JSGlobalObject* globalObject, ThrowScope& scope, JSValue options) +{ + return getIntOption(globalObject, scope, options, "padding"_s); +} + +std::optional SignJobCtx::fromJS(JSGlobalObject* globalObject, ThrowScope& scope, Mode mode, + JSValue algorithmValue, JSValue dataValue, JSValue keyValue, JSValue signatureValue, JSValue callbackValue) +{ + if (!algorithmValue.isUndefinedOrNull()) { + V::validateString(scope, globalObject, algorithmValue, "algorithm"_s); + RETURN_IF_EXCEPTION(scope, {}); + } + + if (!callbackValue.isUndefined()) { + V::validateFunction(scope, globalObject, callbackValue, "callback"_s); + RETURN_IF_EXCEPTION(scope, {}); + } + + auto dataView = getArrayBufferOrView2(globalObject, scope, dataValue, "data"_s, jsUndefined()); + RETURN_IF_EXCEPTION(scope, {}); + + Vector data; + data.append(std::span { dataView->data(), dataView->size() }); + + if (mode == Mode::Sign) { + if (keyValue.pureToBoolean() == TriState::False) { + ERR::CRYPTO_SIGN_KEY_REQUIRED(scope, globalObject); + return std::nullopt; + } + } + + auto padding = getPadding(globalObject, scope, keyValue); + RETURN_IF_EXCEPTION(scope, {}); + auto pssSaltLength = getSaltLength(globalObject, scope, keyValue); + RETURN_IF_EXCEPTION(scope, {}); + auto dsaSigEnc = getDSASigEnc(globalObject, scope, keyValue); + RETURN_IF_EXCEPTION(scope, {}); + + GCOwnedDataScope> signatureView = { nullptr, {} }; + if (mode == Mode::Verify) { + signatureView = getArrayBufferOrView2(globalObject, scope, signatureValue, "signature"_s, jsUndefined(), true); + RETURN_IF_EXCEPTION(scope, {}); + } + + auto prepareResult = mode == Mode::Verify + ? KeyObject::preparePublicOrPrivateKey(globalObject, scope, keyValue) + : KeyObject::preparePrivateKey(globalObject, scope, keyValue); + RETURN_IF_EXCEPTION(scope, {}); + + ClearErrorOnReturn clearError; + auto keyType = mode == Mode::Verify + ? CryptoKeyType::Public + : CryptoKeyType::Private; + + KeyObject keyObject; + + if (prepareResult.keyData) { + keyObject = KeyObject::create(keyType, WTFMove(*prepareResult.keyData)); + } else { + + keyObject = KeyObject::getPublicOrPrivateKey( + globalObject, + scope, + prepareResult.keyDataView, + keyType, + prepareResult.formatType, + prepareResult.encodingType, + prepareResult.cipher, + WTFMove(prepareResult.passphrase)); + RETURN_IF_EXCEPTION(scope, {}); + } + + Digest digest = {}; + + if (!algorithmValue.isUndefinedOrNull()) { + auto* algorithmString = algorithmValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto algorithmView = algorithmString->view(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + digest = Digest::FromName(algorithmView); + if (!digest) { + ERR::CRYPTO_INVALID_DIGEST(scope, globalObject, algorithmView); + return {}; + } + } + + if (mode == Mode::Verify) { + Vector signature; + if (keyObject.asymmetricKey().isSigVariant() && dsaSigEnc == DSASigEnc::P1363) { + convertP1363ToDER( + ncrypto::Buffer { + .data = signatureView->data(), + .len = signatureView->size(), + }, + keyObject.asymmetricKey(), + signature); + } else { + signature.append(std::span { signatureView->data(), signatureView->size() }); + } + + return SignJobCtx( + mode, + keyObject.data(), + WTFMove(data), + digest, + padding, + pssSaltLength, + dsaSigEnc, + WTFMove(signature)); + } + + return SignJobCtx( + mode, + keyObject.data(), + WTFMove(data), + digest, + padding, + pssSaltLength, + dsaSigEnc); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/CryptoSignJob.h b/src/bun.js/bindings/node/crypto/CryptoSignJob.h new file mode 100644 index 0000000000..ac029b09cb --- /dev/null +++ b/src/bun.js/bindings/node/crypto/CryptoSignJob.h @@ -0,0 +1,73 @@ +#pragma once + +#include "root.h" +#include "CryptoUtil.h" +#include "KeyObject.h" + +namespace Bun { +JSC_DECLARE_HOST_FUNCTION(jsSignOneShot); +JSC_DECLARE_HOST_FUNCTION(jsVerifyOneShot); + +static const unsigned int NoDsaSignature = static_cast(-1); + +struct SignJobCtx { + WTF_MAKE_TZONE_ALLOCATED(name); + +public: + enum class Mode { + Sign, + Verify + }; + + SignJobCtx(Mode mode, RefPtr keyData, Vector&& data, ncrypto::Digest digest, std::optional padding, std::optional saltLength, DSASigEnc dsaSigEnc, Vector&& signature = {}) + : m_mode(mode) + , m_keyData(keyData) + , m_data(WTFMove(data)) + , m_signature(WTFMove(signature)) + , m_digest(digest) + , m_padding(padding) + , m_saltLength(saltLength) + , m_dsaSigEnc(dsaSigEnc) + + { + } + + SignJobCtx(SignJobCtx&& other) + : m_mode(other.m_mode) + , m_keyData(WTFMove(other.m_keyData)) + , m_data(WTFMove(other.m_data)) + , m_signature(WTFMove(other.m_signature)) + , m_digest(other.m_digest) + , m_padding(other.m_padding) + , m_saltLength(other.m_saltLength) + , m_dsaSigEnc(other.m_dsaSigEnc) + { + } + + static std::optional fromJS(JSC::JSGlobalObject*, JSC::ThrowScope&, Mode mode, + JSValue algorithmValue, JSValue dataValue, JSValue keyValue, JSValue signatureValue, JSValue callbackValue); + + void runTask(JSC::JSGlobalObject*); + void runFromJS(JSC::JSGlobalObject*, JSC::JSValue callback); + void deinit(); + + Mode m_mode; + RefPtr m_keyData; + Vector m_data; + Vector m_signature; + ncrypto::Digest m_digest; + std::optional m_padding; + std::optional m_saltLength; + DSASigEnc m_dsaSigEnc; + + std::optional m_signResult = { std::nullopt }; + std::optional m_verifyResult = { std::nullopt }; + int m_opensslError = 0; +}; + +struct SignJob { + static SignJob* create(JSC::JSGlobalObject*, SignJobCtx&&, JSC::JSValue callback); + static void createAndSchedule(JSC::JSGlobalObject*, SignJobCtx&&, JSC::JSValue callback); + void schedule(); +}; +} diff --git a/src/bun.js/bindings/node/crypto/CryptoUtil.cpp b/src/bun.js/bindings/node/crypto/CryptoUtil.cpp index 87d40e6cd2..66530b1867 100644 --- a/src/bun.js/bindings/node/crypto/CryptoUtil.cpp +++ b/src/bun.js/bindings/node/crypto/CryptoUtil.cpp @@ -9,15 +9,15 @@ #include "JSBufferEncodingType.h" #include "JSCryptoKey.h" #include "CryptoKeyRSA.h" -#include "AsymmetricKeyValue.h" -#include "KeyObject.h" #include "JSVerify.h" #include #include "CryptoKeyRaw.h" +#include "JSKeyObject.h" namespace Bun { using namespace JSC; +using namespace ncrypto; namespace ExternZigHash { struct Hasher; @@ -378,10 +378,9 @@ void throwCryptoError(JSC::JSGlobalObject* globalObject, ThrowScope& scope, uint throwException(globalObject, scope, errorObject); } -std::optional getIntOption(JSC::JSGlobalObject* globalObject, JSValue options, WTF::ASCIILiteral name) +std::optional getIntOption(JSC::JSGlobalObject* globalObject, ThrowScope& scope, JSValue options, WTF::ASCIILiteral name) { JSC::VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); JSC::JSValue value = options.get(globalObject, JSC::Identifier::fromString(vm, name)); RETURN_IF_EXCEPTION(scope, std::nullopt); @@ -397,29 +396,28 @@ std::optional getIntOption(JSC::JSGlobalObject* globalObject, JSValue o return value.asInt32(); } -int32_t getPadding(JSC::JSGlobalObject* globalObject, JSValue options, const ncrypto::EVPKeyPointer& pkey) +int32_t getPadding(JSC::JSGlobalObject* globalObject, ThrowScope& scope, JSValue options, const ncrypto::EVPKeyPointer& pkey) { - auto padding = getIntOption(globalObject, options, "padding"_s); + auto padding = getIntOption(globalObject, scope, options, "padding"_s); return padding.value_or(pkey.getDefaultSignPadding()); } -std::optional getSaltLength(JSC::JSGlobalObject* globalObject, JSValue options) +std::optional getSaltLength(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, JSValue options) { - return getIntOption(globalObject, options, "saltLength"_s); + return getIntOption(globalObject, scope, options, "saltLength"_s); } -NodeCryptoKeys::DSASigEnc getDSASigEnc(JSC::JSGlobalObject* globalObject, JSValue options) +DSASigEnc getDSASigEnc(JSC::JSGlobalObject* globalObject, ThrowScope& scope, JSValue options) { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); if (!options.isObject() || options.asCell()->type() != JSC::JSType::FinalObjectType) { - return NodeCryptoKeys::DSASigEnc::DER; + return DSASigEnc::DER; } JSValue dsaEncoding = options.get(globalObject, Identifier::fromString(globalObject->vm(), "dsaEncoding"_s)); RETURN_IF_EXCEPTION(scope, {}); if (dsaEncoding.isUndefined()) { - return NodeCryptoKeys::DSASigEnc::DER; + return DSASigEnc::DER; } if (!dsaEncoding.isString()) { @@ -427,21 +425,79 @@ NodeCryptoKeys::DSASigEnc getDSASigEnc(JSC::JSGlobalObject* globalObject, JSValu return {}; } - WTF::String dsaEncodingStr = dsaEncoding.toWTFString(globalObject); + auto* dsaEncodingStr = dsaEncoding.toString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto dsaEncodingView = dsaEncodingStr->view(globalObject); RETURN_IF_EXCEPTION(scope, {}); - if (dsaEncodingStr == "der"_s) { - return NodeCryptoKeys::DSASigEnc::DER; + if (dsaEncodingView == "der"_s) { + return DSASigEnc::DER; } - if (dsaEncodingStr == "ieee-p1363"_s) { - return NodeCryptoKeys::DSASigEnc::P1363; + if (dsaEncodingView == "ieee-p1363"_s) { + return DSASigEnc::P1363; } Bun::ERR::INVALID_ARG_VALUE(scope, globalObject, "options.dsaEncoding"_s, dsaEncoding); return {}; } +bool convertP1363ToDER(const ncrypto::Buffer& p1363Sig, + const ncrypto::EVPKeyPointer& pkey, + WTF::Vector& derBuffer) +{ + // Get the size of r and s components from the key + auto bytesOfRS = pkey.getBytesOfRS(); + if (!bytesOfRS) { + // If we can't get the bytes of RS, this is not a signature variant + // that we can convert. Return false to indicate that the original + // signature should be used. + return false; + } + + size_t bytesOfRSValue = bytesOfRS.value(); + + // Check if the signature size is valid (should be 2 * bytesOfRS) + if (p1363Sig.len != 2 * bytesOfRSValue) { + // If the signature size doesn't match what we expect, return false + // to indicate that the original signature should be used. + return false; + } + + // Create BignumPointers for r and s components + ncrypto::BignumPointer r(p1363Sig.data, bytesOfRSValue); + if (!r) { + return false; + } + + ncrypto::BignumPointer s(p1363Sig.data + bytesOfRSValue, bytesOfRSValue); + if (!s) { + return false; + } + + // Create a new ECDSA_SIG structure and set r and s components + auto asn1_sig = ncrypto::ECDSASigPointer::New(); + if (!asn1_sig) { + return false; + } + + if (!asn1_sig.setParams(WTFMove(r), WTFMove(s))) { + return false; + } + + // Encode the signature in DER format + auto buf = asn1_sig.encode(); + if (buf.len < 0) { + return false; + } + + if (!derBuffer.tryAppend(std::span { buf.data, buf.len })) { + return false; + } + + return true; +} + JSC::JSArrayBufferView* getArrayBufferOrView(JSGlobalObject* globalObject, ThrowScope& scope, JSValue value, ASCIILiteral argName, BufferEncodingType encoding) { if (value.isString()) { @@ -472,6 +528,55 @@ JSC::JSArrayBufferView* getArrayBufferOrView(JSGlobalObject* globalObject, Throw return getArrayBufferOrView(globalObject, scope, value, argName, jsUndefined()); } +// maybe replace other getArrayBufferOrView +GCOwnedDataScope> getArrayBufferOrView2(JSGlobalObject* globalObject, ThrowScope& scope, JSValue dataValue, ASCIILiteral argName, JSValue encodingValue, bool arrayBufferViewOnly) +{ + using Return = GCOwnedDataScope>; + + if (auto* view = jsDynamicCast(dataValue)) { + return { view, view->span() }; + } + + if (arrayBufferViewOnly) { + ERR::INVALID_ARG_INSTANCE(scope, globalObject, argName, "Buffer, TypedArray, or DataView"_s, dataValue); + return { nullptr, {} }; + }; + + if (auto* arrayBuffer = jsDynamicCast(dataValue)) { + return { arrayBuffer, arrayBuffer->impl()->span() }; + } + + if (dataValue.isString()) { + auto* str = dataValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, Return(nullptr, {})); + auto strView = str->view(globalObject); + RETURN_IF_EXCEPTION(scope, Return(nullptr, {})); + + BufferEncodingType encoding = BufferEncodingType::utf8; + if (encodingValue.isString()) { + auto* encodingString = encodingValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, Return(nullptr, {})); + auto encodingView = encodingString->view(globalObject); + RETURN_IF_EXCEPTION(scope, Return(nullptr, {})); + + if (encodingView != "buffer"_s) { + encoding = parseEnumerationFromView(encodingView).value_or(BufferEncodingType::utf8); + RETURN_IF_EXCEPTION(scope, Return(nullptr, {})); + } + } + + JSValue buffer = JSValue::decode(WebCore::constructFromEncoding(globalObject, strView, encoding)); + RETURN_IF_EXCEPTION(scope, Return(nullptr, {})); + + if (auto* view = jsDynamicCast(buffer)) { + return { view, view->span() }; + } + } + + ERR::INVALID_ARG_TYPE(scope, globalObject, argName, "string or an instance of ArrayBuffer, Buffer, TypedArray, or DataView"_s, dataValue); + return Return(nullptr, {}); +} + JSC::JSArrayBufferView* getArrayBufferOrView(JSGlobalObject* globalObject, ThrowScope& scope, JSValue value, ASCIILiteral argName, JSValue encodingValue, bool defaultBufferEncoding) { if (value.isString()) { @@ -533,256 +638,231 @@ JSC::JSArrayBufferView* getArrayBufferOrView(JSGlobalObject* globalObject, Throw return view; } -std::optional preparePrivateKey(JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, JSValue maybeKey, std::optional algorithmIdentifier) +std::optional> getBuffer(JSC::JSValue maybeBuffer) { - ncrypto::ClearErrorOnReturn clearError; + if (auto* view = jsDynamicCast(maybeBuffer)) { + if (view->isDetached()) { + return std::nullopt; + } - VM& vm = lexicalGlobalObject->vm(); + return view->span(); + } - if (!maybeKey.isCell()) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key"_s, "ArrayBuffer, Buffer, TypedArray, DataView, string, KeyObject, or CryptoKey"_s, maybeKey); + if (auto* arrayBuffer = jsDynamicCast(maybeBuffer)) { + auto* buffer = arrayBuffer->impl(); + if (buffer->isDetached()) { + return std::nullopt; + } + + return buffer->span(); + } + + return std::nullopt; +} + +bool isStringOrBuffer(JSValue value) +{ + if (value.isString()) { + return true; + } + + if (jsDynamicCast(value)) { + return true; + } + + if (jsDynamicCast(value)) { + return true; + } + + return false; +} + +String makeOptionString(WTF::StringView objName, const ASCIILiteral& optionName) +{ + return objName.isNull() ? makeString("options."_s, optionName) : makeString("options."_s, objName, '.', optionName); +} + +ncrypto::EVPKeyPointer::PKFormatType parseKeyFormat(JSGlobalObject* globalObject, ThrowScope& scope, JSValue formatValue, std::optional defaultFormat, WTF::String optionName) +{ + if (formatValue.isUndefined() && defaultFormat) { + return *defaultFormat; + } + + if (formatValue.isString()) { + JSString* formatString = formatValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + GCOwnedDataScope formatView = formatString->view(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + if (formatView == "pem"_s) { + return EVPKeyPointer::PKFormatType::PEM; + } else if (formatView == "der"_s) { + return EVPKeyPointer::PKFormatType::DER; + } else if (formatView == "jwk"_s) { + return EVPKeyPointer::PKFormatType::JWK; + } + } + + ERR::INVALID_ARG_VALUE(scope, globalObject, optionName, formatValue); + return {}; +} + +std::optional parseKeyType(JSGlobalObject* globalObject, ThrowScope& scope, JSValue typeValue, bool required, JSValue keyTypeValue, std::optional isPublic, WTF::String optionName) +{ + if (typeValue.isUndefined() && !required) { return std::nullopt; } - auto optionsCell = maybeKey.asCell(); - auto optionsType = optionsCell->type(); + WTF::StringView keyTypeView = WTF::nullStringView(); + if (!keyTypeValue.isUndefined()) { + keyTypeView = keyTypeValue.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + } - if (optionsCell->inherits()) { - auto* cryptoKey = jsCast(optionsCell); - - // convert it to a key object, then to EVPKeyPointer - auto& key = cryptoKey->wrapped(); - - if (algorithmIdentifier) { - switch (key.keyClass()) { - case CryptoKeyClass::RSA: { - const auto& rsa = downcast(key); - CryptoAlgorithmIdentifier restrictHash; - bool isRestricted = rsa.isRestrictedToHash(restrictHash); - if (isRestricted && algorithmIdentifier.value() != restrictHash) { - JSC::throwTypeError(lexicalGlobalObject, scope, "digest not allowed"_s); - return std::nullopt; - } - } - default: - break; - } - } - - AsymmetricKeyValue keyValue(key); - if (keyValue.key) { - EVP_PKEY_up_ref(keyValue.key); - ncrypto::EVPKeyPointer keyPtr(keyValue.key); - return keyPtr; - } - - throwCryptoOperationFailed(lexicalGlobalObject, scope); - return std::nullopt; - } else if (maybeKey.isObject()) { - JSObject* optionsObj = optionsCell->getObject(); - const auto& names = WebCore::builtinNames(vm); - - if (auto val = optionsObj->getIfPropertyExists(lexicalGlobalObject, names.bunNativePtrPrivateName())) { - if (val.isCell() && val.inherits()) { - auto* cryptoKey = jsCast(val.asCell()); - - auto& key = cryptoKey->wrapped(); - - if (algorithmIdentifier) { - switch (key.keyClass()) { - case CryptoKeyClass::RSA: { - const auto& rsa = downcast(key); - CryptoAlgorithmIdentifier restrictHash; - bool isRestricted = rsa.isRestrictedToHash(restrictHash); - if (isRestricted && algorithmIdentifier.value() != restrictHash) { - JSC::throwTypeError(lexicalGlobalObject, scope, "digest not allowed"_s); - return std::nullopt; - } - } - default: - break; - } - } - - AsymmetricKeyValue keyValue(key); - if (keyValue.key) { - EVP_PKEY_up_ref(keyValue.key); - ncrypto::EVPKeyPointer keyPtr(keyValue.key); - return keyPtr; - } - throwCryptoOperationFailed(lexicalGlobalObject, scope); - return std::nullopt; - } - } else if (optionsType >= Int8ArrayType && optionsType <= DataViewType) { - auto dataBuf = KeyObject__GetBuffer(maybeKey); - if (dataBuf.hasException()) { - return std::nullopt; - } - - ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config; - config.format = ncrypto::EVPKeyPointer::PKFormatType::PEM; - - auto buffer = dataBuf.releaseReturnValue(); - ncrypto::Buffer ncryptoBuf { - .data = buffer.data(), - .len = buffer.size(), - }; - - auto res = ncrypto::EVPKeyPointer::TryParsePrivateKey(config, ncryptoBuf); - if (res) { - ncrypto::EVPKeyPointer keyPtr(WTFMove(res.value)); - return keyPtr; - } - - if (res.error.value() == ncrypto::EVPKeyPointer::PKParseError::NEED_PASSPHRASE) { - Bun::ERR::MISSING_PASSPHRASE(scope, lexicalGlobalObject, "Passphrase required for encrypted key"_s); - return std::nullopt; - } - - throwCryptoError(lexicalGlobalObject, scope, res.openssl_error.value_or(0), "Failed to read private key"_s); - return std::nullopt; - } - - JSValue key = optionsObj->get(lexicalGlobalObject, Identifier::fromString(vm, "key"_s)); - RETURN_IF_EXCEPTION(scope, {}); - JSValue encodingValue = optionsObj->get(lexicalGlobalObject, Identifier::fromString(vm, "encoding"_s)); - RETURN_IF_EXCEPTION(scope, {}); - JSValue passphrase = optionsObj->get(lexicalGlobalObject, Identifier::fromString(vm, "passphrase"_s)); - RETURN_IF_EXCEPTION(scope, {}); - JSValue formatValue = optionsObj->get(lexicalGlobalObject, Identifier::fromString(vm, "format"_s)); - RETURN_IF_EXCEPTION(scope, {}); - WTF::StringView formatStr = WTF::nullStringView(); - if (formatValue.isString()) { - auto str = formatValue.toString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, {}); - formatStr = str->view(lexicalGlobalObject); - } - - if (!key.isCell()) { - if (formatStr == "jwk"_s) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.key"_s, "object"_s, key); - } else { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.key"_s, "ArrayBuffer, Buffer, TypedArray, DataView, string, KeyObject, or CryptoKey"_s, key); - } - return std::nullopt; - } - - String encodingString = encodingValue.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, {}); - - auto keyCell = key.asCell(); - auto keyCellType = keyCell->type(); - if (keyCell->inherits()) { - auto* cryptoKey = jsCast(keyCell); - auto& key = cryptoKey->wrapped(); - - if (algorithmIdentifier) { - switch (key.keyClass()) { - case CryptoKeyClass::RSA: { - const auto& rsa = downcast(key); - CryptoAlgorithmIdentifier restrictHash; - bool isRestricted = rsa.isRestrictedToHash(restrictHash); - if (isRestricted && algorithmIdentifier.value() != restrictHash) { - JSC::throwTypeError(lexicalGlobalObject, scope, "digest not allowed"_s); - return std::nullopt; - } - } - default: - break; - } - } - - AsymmetricKeyValue keyValue(key); - if (keyValue.key) { - EVP_PKEY_up_ref(keyValue.key); - ncrypto::EVPKeyPointer keyPtr(keyValue.key); - return keyPtr; - } - throwCryptoOperationFailed(lexicalGlobalObject, scope); - return std::nullopt; - } else if (key.isObject()) { - JSObject* keyObj = key.getObject(); - if (auto keyVal = keyObj->getIfPropertyExists(lexicalGlobalObject, names.bunNativePtrPrivateName())) { - if (keyVal.isCell() && keyVal.inherits()) { - auto* cryptoKey = jsCast(keyVal.asCell()); - - auto& key = cryptoKey->wrapped(); - AsymmetricKeyValue keyValue(key); - if (keyValue.key) { - EVP_PKEY_up_ref(keyValue.key); - ncrypto::EVPKeyPointer keyPtr(WTFMove(keyValue.key)); - return keyPtr; - } - throwCryptoOperationFailed(lexicalGlobalObject, scope); - return std::nullopt; - } - } else if (keyCellType >= Int8ArrayType && keyCellType <= DataViewType) { - auto dataBuf = KeyObject__GetBuffer(key); - if (dataBuf.hasException()) { - return std::nullopt; - } - - ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config; - config.format = parseKeyFormat(lexicalGlobalObject, formatValue, "options.format"_s, ncrypto::EVPKeyPointer::PKFormatType::PEM); - - config.passphrase = passphraseFromBufferSource(lexicalGlobalObject, scope, passphrase); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - // Get the type value from options - JSValue typeValue = optionsObj->get(lexicalGlobalObject, Identifier::fromString(vm, "type"_s)); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - // Parse key type for private key - auto keyType = parseKeyType(lexicalGlobalObject, typeValue, config.format == ncrypto::EVPKeyPointer::PKFormatType::DER, WTF::nullStringView(), false, "options.type"_s); - RETURN_IF_EXCEPTION(scope, std::nullopt); - config.type = keyType.value_or(ncrypto::EVPKeyPointer::PKEncodingType::PKCS1); - - auto buffer = dataBuf.releaseReturnValue(); - ncrypto::Buffer ncryptoBuf { - .data = buffer.data(), - .len = buffer.size(), - }; - - auto res = ncrypto::EVPKeyPointer::TryParsePrivateKey(config, ncryptoBuf); - if (!res) { - if (res.error.value() == ncrypto::EVPKeyPointer::PKParseError::NEED_PASSPHRASE) { - Bun::ERR::MISSING_PASSPHRASE(scope, lexicalGlobalObject, "Passphrase required for encrypted key"_s); - return std::nullopt; - } - - throwCryptoError(lexicalGlobalObject, scope, res.openssl_error.value_or(0), "Failed to read private key"_s); - return std::nullopt; - } - - ncrypto::EVPKeyPointer keyPtr(WTFMove(res.value)); - return keyPtr; - } else if (formatStr == "jwk"_s) { - bool isPublic = false; - return getKeyObjectHandleFromJwk(lexicalGlobalObject, scope, key, isPublic); - } - } else if (formatStr == "jwk"_s) { - bool isPublic = false; - return getKeyObjectHandleFromJwk(lexicalGlobalObject, scope, key, isPublic); - } else if (key.isString()) { - WTF::String keyStr = key.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - return keyFromString(lexicalGlobalObject, scope, keyStr, passphrase); - } - - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.key"_s, "ArrayBuffer, Buffer, TypedArray, DataView, string, KeyObject, or CryptoKey"_s, key); - return std::nullopt; - } else if (maybeKey.isString()) { - WTF::String keyStr = maybeKey.toWTFString(lexicalGlobalObject); + if (typeValue.isString()) { + JSString* typeString = typeValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, std::nullopt); + GCOwnedDataScope typeView = typeString->view(globalObject); RETURN_IF_EXCEPTION(scope, std::nullopt); - return keyFromString(lexicalGlobalObject, scope, keyStr, jsUndefined()); + if (typeView == "pkcs1"_s) { + if (!keyTypeView.isNull() && keyTypeView != "rsa"_s) { + ERR::CRYPTO_INCOMPATIBLE_KEY_OPTIONS(scope, globalObject, typeView, "can only be used for RSA keys"_s); + return std::nullopt; + } + return EVPKeyPointer::PKEncodingType::PKCS1; + } + + if (typeView == "spki"_s && (!isPublic || *isPublic != false)) { + return EVPKeyPointer::PKEncodingType::SPKI; + } + + if (typeView == "pkcs8"_s && (!isPublic || *isPublic != true)) { + return EVPKeyPointer::PKEncodingType::PKCS8; + } + + if (typeView == "sec1"_s && (!isPublic || *isPublic != true)) { + if (!keyTypeView.isNull() && keyTypeView != "ec"_s) { + ERR::CRYPTO_INCOMPATIBLE_KEY_OPTIONS(scope, globalObject, typeView, "can only be used for EC keys"_s); + return std::nullopt; + } + return EVPKeyPointer::PKEncodingType::SEC1; + } } - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key"_s, "ArrayBuffer, Buffer, TypedArray, DataView, string, KeyObject, or CryptoKey"_s, maybeKey); + ERR::INVALID_ARG_VALUE(scope, globalObject, optionName, typeValue); return std::nullopt; } +void parseKeyFormatAndType(JSGlobalObject* globalObject, ThrowScope& scope, JSObject* enc, JSValue keyTypeValue, std::optional isPublic, bool isInput, WTF::StringView objName, EVPKeyPointer::PrivateKeyEncodingConfig& config) +{ + VM& vm = globalObject->vm(); + + JSValue formatValue = enc->get(globalObject, Identifier::fromString(vm, "format"_s)); + RETURN_IF_EXCEPTION(scope, ); + JSValue typeValue = enc->get(globalObject, Identifier::fromString(vm, "type"_s)); + RETURN_IF_EXCEPTION(scope, ); + + config.format = parseKeyFormat(globalObject, scope, formatValue, isInput ? std::optional { EVPKeyPointer::PKFormatType::PEM } : std::nullopt, makeOptionString(objName, "format"_s)); + RETURN_IF_EXCEPTION(scope, ); + + bool isRequired = (!isInput || config.format == EVPKeyPointer::PKFormatType::DER) && config.format != EVPKeyPointer::PKFormatType::JWK; + std::optional maybeKeyType = parseKeyType(globalObject, scope, typeValue, isRequired, keyTypeValue, isPublic, makeOptionString(objName, "type"_s)); + RETURN_IF_EXCEPTION(scope, ); + + if (maybeKeyType) { + config.type = *maybeKeyType; + } +} + +void parseKeyEncoding(JSGlobalObject* globalObject, ThrowScope& scope, JSObject* enc, JSValue keyTypeValue, std::optional isPublic, WTF::StringView objName, EVPKeyPointer::PrivateKeyEncodingConfig& config) +{ + VM& vm = globalObject->vm(); + + bool isInput = keyTypeValue.isUndefined(); + + parseKeyFormatAndType(globalObject, scope, enc, keyTypeValue, isPublic, isInput, objName, config); + RETURN_IF_EXCEPTION(scope, ); + + JSValue encodingValue = jsUndefined(); + JSValue passphraseValue = jsUndefined(); + JSValue cipherValue = jsUndefined(); + + if (!isPublic || *isPublic != true) { + cipherValue = enc->get(globalObject, Identifier::fromString(vm, "cipher"_s)); + RETURN_IF_EXCEPTION(scope, ); + passphraseValue = enc->get(globalObject, Identifier::fromString(vm, "passphrase"_s)); + RETURN_IF_EXCEPTION(scope, ); + encodingValue = enc->get(globalObject, Identifier::fromString(vm, "encoding"_s)); + RETURN_IF_EXCEPTION(scope, ); + + if (!isInput) { + if (!cipherValue.isUndefinedOrNull()) { + if (!cipherValue.isString()) { + ERR::INVALID_ARG_VALUE(scope, globalObject, makeOptionString(objName, "cipher"_s), cipherValue); + return; + } + if (config.format == EVPKeyPointer::PKFormatType::DER && (config.type == EVPKeyPointer::PKEncodingType::PKCS1 || config.type == EVPKeyPointer::PKEncodingType::SEC1)) { + ERR::CRYPTO_INCOMPATIBLE_KEY_OPTIONS(scope, globalObject, EVPKeyPointer::EncodingName(config.type), "does not support encryption"_s); + return; + } + } else if (!passphraseValue.isUndefined()) { + ERR::INVALID_ARG_VALUE(scope, globalObject, makeOptionString(objName, "cipher"_s), cipherValue); + return; + } + } + + if ((isInput && !passphraseValue.isUndefined() && !isStringOrBuffer(passphraseValue)) || (!isInput && !cipherValue.isUndefinedOrNull() && !isStringOrBuffer(passphraseValue))) { + ERR::INVALID_ARG_VALUE(scope, globalObject, makeOptionString(objName, "passphrase"_s), passphraseValue); + return; + } + } + + if (!passphraseValue.isUndefined()) { + JSArrayBufferView* passphraseView = getArrayBufferOrView(globalObject, scope, passphraseValue, "key.passphrase"_s, encodingValue); + RETURN_IF_EXCEPTION(scope, ); + config.passphrase = DataPointer::FromSpan(passphraseView->span()); + } + + if (config.output_key_object) { + if (!isInput) { + } + } else { + if (!isInput) { + if (cipherValue.isString()) { + JSString* cipherString = cipherValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, ); + GCOwnedDataScope cipherView = cipherString->view(globalObject); + RETURN_IF_EXCEPTION(scope, ); + config.cipher = getCipherByName(cipherView); + if (config.cipher == nullptr) { + ERR::CRYPTO_UNKNOWN_CIPHER(scope, globalObject, cipherView); + return; + } + } else { + config.cipher = nullptr; + } + } + } +} + +void parsePublicKeyEncoding(JSGlobalObject* globalObject, ThrowScope& scope, JSObject* enc, JSValue keyTypeValue, WTF::StringView objName, EVPKeyPointer::PublicKeyEncodingConfig& config) +{ + EVPKeyPointer::PrivateKeyEncodingConfig dummyConfig = {}; + parseKeyEncoding(globalObject, scope, enc, keyTypeValue, keyTypeValue.pureToBoolean() != TriState::False ? std::optional(true) : std::nullopt, objName, dummyConfig); + RETURN_IF_EXCEPTION(scope, ); + + // using private config because it's a super set of public config + config.format = dummyConfig.format; + config.type = dummyConfig.type; + config.output_key_object = dummyConfig.output_key_object; +} + +void parsePrivateKeyEncoding(JSGlobalObject* globalObject, ThrowScope& scope, JSObject* enc, JSValue keyTypeValue, WTF::StringView objName, EVPKeyPointer::PrivateKeyEncodingConfig& config) +{ + parseKeyEncoding(globalObject, scope, enc, keyTypeValue, false, objName, config); +} + bool isArrayBufferOrView(JSValue value) { if (value.isCell()) { @@ -818,89 +898,6 @@ bool isKeyValidForCurve(const EC_GROUP* group, const ncrypto::BignumPointer& pri return privateKey < order; } -// takes a key value and encoding value -// - if key is string, returns the key as a vector of bytes, using encoding if !isUndefined -// - if key is isAnyArrayBuffer, return the bytes -// - if !bufferOnly: -// - if key is KeyObject with native crypto key, extract the key material -// - if key is CryptoKey, ensure `type` is secret, then extract the key material -// - none matched, throw error for INVALID_ARG_TYPE -void prepareSecretKey(JSGlobalObject* globalObject, ThrowScope& scope, Vector& out, JSValue key, JSValue encodingValue, bool bufferOnly) -{ - VM& vm = globalObject->vm(); - - // Handle KeyObject (if not bufferOnly) - if (!bufferOnly && key.isObject()) { - JSObject* obj = key.getObject(); - auto& names = WebCore::builtinNames(vm); - - // Check for BunNativePtr on the object - if (auto val = obj->getIfPropertyExists(globalObject, names.bunNativePtrPrivateName())) { - if (auto* cryptoKey = jsDynamicCast(val.asCell())) { - - JSValue typeValue = obj->get(globalObject, Identifier::fromString(vm, "type"_s)); - RETURN_IF_EXCEPTION(scope, ); - - auto wrappedKey = cryptoKey->protectedWrapped(); - - if (!typeValue.isString()) { - Bun::ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, typeValue, "secret"_s); - return; - } - - WTF::String typeString = typeValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, ); - - if (wrappedKey->type() != CryptoKeyType::Secret || typeString != "secret"_s) { - Bun::ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, typeValue, "secret"_s); - return; - } - - auto keyData = getSymmetricKey(wrappedKey); - - if (UNLIKELY(!keyData)) { - Bun::ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, typeValue, "secret"_s); - return; - } - - out.append(keyData.value()); - return; - } - } - } - - // Handle string or buffer - if (key.isString()) { - JSString* keyString = key.toString(globalObject); - RETURN_IF_EXCEPTION(scope, ); - - auto encoding = parseEnumeration(*globalObject, encodingValue).value_or(WebCore::BufferEncodingType::utf8); - RETURN_IF_EXCEPTION(scope, ); - if (encoding == WebCore::BufferEncodingType::buffer) { - encoding = WebCore::BufferEncodingType::utf8; - } - - auto keyView = keyString->view(globalObject); - RETURN_IF_EXCEPTION(scope, ); - - // TODO(dylan-conway): add a way to do this with just the Vector. no need to create a buffer - JSValue buffer = JSValue::decode(WebCore::constructFromEncoding(globalObject, keyView, encoding)); - auto* view = jsDynamicCast(buffer); - out.append(std::span { reinterpret_cast(view->vector()), view->byteLength() }); - return; - } - - // Handle ArrayBuffer types - if (auto* view = jsDynamicCast(key)) { - out.append(std::span { reinterpret_cast(view->vector()), view->byteLength() }); - return; - } - - // If we got here, the key is not a valid type - WTF::String expectedTypes = bufferOnly ? "ArrayBuffer, Buffer, TypedArray, DataView, or a string"_s : "ArrayBuffer, Buffer, TypedArray, DataView, string, CryptoKey, or KeyObject"_s; - Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "key"_s, expectedTypes, key); -} - ByteSource::ByteSource(ByteSource&& other) noexcept : data_(other.data_) , allocated_data_(other.allocated_data_) diff --git a/src/bun.js/bindings/node/crypto/CryptoUtil.h b/src/bun.js/bindings/node/crypto/CryptoUtil.h index b770e2e130..4f6331be1a 100644 --- a/src/bun.js/bindings/node/crypto/CryptoUtil.h +++ b/src/bun.js/bindings/node/crypto/CryptoUtil.h @@ -11,15 +11,12 @@ namespace Bun { using namespace JSC; -namespace NodeCryptoKeys { enum class DSASigEnc { DER, P1363, Invalid, }; -} - namespace ExternZigHash { struct Hasher; @@ -44,18 +41,25 @@ ncrypto::EVPKeyPointer::PKFormatType parseKeyFormat(JSC::JSGlobalObject* globalO std::optional parseKeyType(JSC::JSGlobalObject* globalObject, JSValue typeValue, bool required, WTF::StringView keyType, std::optional isPublic, WTF::ASCIILiteral optionName); bool isArrayBufferOrView(JSValue value); std::optional passphraseFromBufferSource(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, JSValue input); +JSValue createCryptoError(JSC::JSGlobalObject* globalObject, ThrowScope& scope, uint32_t err, const char* message); void throwCryptoError(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, uint32_t err, const char* message = nullptr); void throwCryptoOperationFailed(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope); -std::optional getIntOption(JSC::JSGlobalObject* globalObject, JSValue options, WTF::ASCIILiteral name); -int32_t getPadding(JSC::JSGlobalObject* globalObject, JSValue options, const ncrypto::EVPKeyPointer& pkey); -std::optional getSaltLength(JSC::JSGlobalObject* globalObject, JSValue options); -NodeCryptoKeys::DSASigEnc getDSASigEnc(JSC::JSGlobalObject* globalObject, JSValue options); +std::optional getIntOption(JSC::JSGlobalObject* globalObject, JSC::ThrowScope&, JSValue options, WTF::ASCIILiteral name); +int32_t getPadding(JSC::JSGlobalObject* globalObject, JSC::ThrowScope&, JSValue options, const ncrypto::EVPKeyPointer& pkey); +std::optional getSaltLength(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, JSValue options); +DSASigEnc getDSASigEnc(JSC::JSGlobalObject* globalObject, JSC::ThrowScope&, JSValue options); +bool convertP1363ToDER(const ncrypto::Buffer& p1363Sig, const ncrypto::EVPKeyPointer& pkey, WTF::Vector& derBuffer); +GCOwnedDataScope> getArrayBufferOrView2(JSGlobalObject* globalObject, ThrowScope& scope, JSValue dataValue, ASCIILiteral argName, JSValue encodingValue, bool arrayBufferViewOnly = false); JSC::JSArrayBufferView* getArrayBufferOrView(JSGlobalObject* globalObject, ThrowScope& scope, JSValue value, ASCIILiteral argName, JSValue encodingValue, bool defaultBufferEncoding = false); JSC::JSArrayBufferView* getArrayBufferOrView(JSGlobalObject* globalObject, ThrowScope& scope, JSValue value, ASCIILiteral argName, BufferEncodingType encoding); -std::optional preparePrivateKey(JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, JSValue maybeKey, std::optional algorithmIdentifier); JSValue getStringOption(JSGlobalObject* globalObject, JSValue options, const WTF::ASCIILiteral& name); -void prepareSecretKey(JSGlobalObject* globalObject, ThrowScope& scope, Vector& out, JSValue key, JSValue encoding, bool bufferOnly = false); bool isKeyValidForCurve(const EC_GROUP* group, const ncrypto::BignumPointer& privateKey); +std::optional> getBuffer(JSC::JSValue maybeBuffer); + +// For output encoding +void parsePublicKeyEncoding(JSGlobalObject*, ThrowScope&, JSObject* enc, JSValue keyTypeValue, WTF::StringView objName, ncrypto::EVPKeyPointer::PublicKeyEncodingConfig&); +void parsePrivateKeyEncoding(JSGlobalObject*, ThrowScope&, JSObject* enc, JSValue keyTypeValue, WTF::StringView objName, ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig&); +void parseKeyEncoding(JSGlobalObject* globalObject, ThrowScope& scope, JSObject* enc, JSValue keyTypeValue, std::optional isPublic, WTF::StringView objName, ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config); // Modified version of ByteSource from node // diff --git a/src/bun.js/bindings/node/crypto/JSCipher.cpp b/src/bun.js/bindings/node/crypto/JSCipher.cpp index da79142eb5..c5a9e2a6d7 100644 --- a/src/bun.js/bindings/node/crypto/JSCipher.cpp +++ b/src/bun.js/bindings/node/crypto/JSCipher.cpp @@ -9,6 +9,10 @@ #include #include #include +#include "CryptoUtil.h" +#include "openssl/rsa.h" +#include "NodeValidator.h" +#include "KeyObject.h" namespace Bun { @@ -43,4 +47,192 @@ void setupCipherClassStructure(JSC::LazyClassStructure::Initializer& init) init.setConstructor(constructor); } +enum class KeyType { + Public, + Private, +}; + +enum class CipherOperation { + encrypt, + decrypt, + sign, + recover, +}; + +JSValue rsaFunction(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, KeyType keyType, CipherOperation operation, int32_t defaultPadding) +{ + VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + JSValue optionsValue = callFrame->argument(0); + JSValue bufferValue = callFrame->argument(1); + + KeyObject keyObject; + switch (keyType) { + case KeyType::Public: { + ncrypto::MarkPopErrorOnReturn popErrorScope; + auto prepareResult = KeyObject::preparePublicOrPrivateKey(globalObject, scope, optionsValue); + RETURN_IF_EXCEPTION(scope, {}); + if (prepareResult.keyData) { + RefPtr data = *prepareResult.keyData; + keyObject = KeyObject::create(CryptoKeyType::Public, WTFMove(data)); + } else { + keyObject = KeyObject::getPublicOrPrivateKey( + globalObject, + scope, + prepareResult.keyDataView, + CryptoKeyType::Public, + prepareResult.formatType, + prepareResult.encodingType, + prepareResult.cipher, + WTFMove(prepareResult.passphrase)); + RETURN_IF_EXCEPTION(scope, {}); + } + break; + } + case KeyType::Private: { + ncrypto::MarkPopErrorOnReturn popErrorScope; + auto prepareResult = KeyObject::preparePrivateKey(globalObject, scope, optionsValue); + RETURN_IF_EXCEPTION(scope, {}); + if (prepareResult.keyData) { + RefPtr data = *prepareResult.keyData; + keyObject = KeyObject::create(CryptoKeyType::Private, WTFMove(data)); + } else { + keyObject = KeyObject::getPublicOrPrivateKey( + globalObject, + scope, + prepareResult.keyDataView, + CryptoKeyType::Private, + prepareResult.formatType, + prepareResult.encodingType, + prepareResult.cipher, + WTFMove(prepareResult.passphrase)); + RETURN_IF_EXCEPTION(scope, {}); + } + break; + } + } + + auto& pkey = keyObject.asymmetricKey(); + + ncrypto::Digest digest; + int32_t padding = defaultPadding; + GCOwnedDataScope> oaepLabel = { nullptr, {} }; + JSValue encodingValue = jsUndefined(); + if (JSObject* options = optionsValue.getObject()) { + JSValue paddingValue = options->get(lexicalGlobalObject, Identifier::fromString(vm, "padding"_s)); + RETURN_IF_EXCEPTION(scope, {}); + if (!paddingValue.isUndefined()) { + padding = paddingValue.toInt32(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + } + + JSValue oaepHashValue = options->get(lexicalGlobalObject, Identifier::fromString(vm, "oaepHash"_s)); + RETURN_IF_EXCEPTION(scope, {}); + if (!oaepHashValue.isUndefined()) { + V::validateString(scope, lexicalGlobalObject, oaepHashValue, "options.oaepHash"_s); + RETURN_IF_EXCEPTION(scope, {}); + JSString* oaepHashString = oaepHashValue.toString(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + GCOwnedDataScope oaepHashView = oaepHashString->view(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + digest = ncrypto::Digest::FromName(oaepHashView); + if (!digest) { + ERR::OSSL_EVP_INVALID_DIGEST(scope, lexicalGlobalObject); + return {}; + } + } + + encodingValue = options->get(lexicalGlobalObject, Identifier::fromString(vm, "encoding"_s)); + RETURN_IF_EXCEPTION(scope, {}); + + JSValue oaepLabelValue = options->get(lexicalGlobalObject, Identifier::fromString(vm, "oaepLabel"_s)); + RETURN_IF_EXCEPTION(scope, {}); + if (!oaepLabelValue.isUndefined()) { + oaepLabel = getArrayBufferOrView2(lexicalGlobalObject, scope, oaepLabelValue, "options.oaepLabel"_s, encodingValue); + RETURN_IF_EXCEPTION(scope, {}); + } + } + + auto buffer = getArrayBufferOrView2(lexicalGlobalObject, scope, bufferValue, "buffer"_s, encodingValue); + RETURN_IF_EXCEPTION(scope, {}); + + if (operation == CipherOperation::decrypt && keyType == KeyType::Private && padding == RSA_PKCS1_PADDING) { + ncrypto::EVPKeyCtxPointer ctx = pkey.newCtx(); + + if (!ctx.initForDecrypt()) { + throwCryptoError(lexicalGlobalObject, scope, ERR_get_error()); + return {}; + } + + throwError(lexicalGlobalObject, scope, ErrorCode::ERR_INVALID_ARG_VALUE, "RSA_PKCS1_PADDING is no longer supported for private decryption"_s); + } + + ncrypto::Buffer labelBuf = {}; + if (oaepLabel.owner) { + labelBuf = { + .data = oaepLabel->data(), + .len = oaepLabel->size(), + }; + } + + ncrypto::Cipher::CipherParams cipherParams { + .padding = padding, + .digest = digest, + .label = labelBuf, + }; + + ncrypto::Buffer bufferBuf { + .data = buffer->data(), + .len = buffer->size(), + }; + + ncrypto::DataPointer result; + switch (operation) { + case CipherOperation::encrypt: + result = ncrypto::Cipher::encrypt(pkey, cipherParams, bufferBuf); + break; + case CipherOperation::decrypt: + result = ncrypto::Cipher::decrypt(pkey, cipherParams, bufferBuf); + break; + case CipherOperation::sign: + result = ncrypto::Cipher::sign(pkey, cipherParams, bufferBuf); + break; + case CipherOperation::recover: + result = ncrypto::Cipher::recover(pkey, cipherParams, bufferBuf); + break; + } + + if (!result) { + throwCryptoError(lexicalGlobalObject, scope, ERR_get_error()); + return {}; + } + + RefPtr outBuf = JSC::ArrayBuffer::tryCreate(result.span()); + if (!outBuf) { + throwOutOfMemoryError(lexicalGlobalObject, scope); + return {}; + } + + return JSUint8Array::create(lexicalGlobalObject, globalObject->JSBufferSubclassStructure(), WTFMove(outBuf), 0, result.size()); +} + +JSC_DEFINE_HOST_FUNCTION(jsPublicEncrypt, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + return JSValue::encode(rsaFunction(globalObject, callFrame, KeyType::Public, CipherOperation::encrypt, RSA_PKCS1_OAEP_PADDING)); +} +JSC_DEFINE_HOST_FUNCTION(jsPublicDecrypt, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + return JSValue::encode(rsaFunction(globalObject, callFrame, KeyType::Public, CipherOperation::recover, RSA_PKCS1_PADDING)); +} +JSC_DEFINE_HOST_FUNCTION(jsPrivateEncrypt, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + return JSValue::encode(rsaFunction(globalObject, callFrame, KeyType::Private, CipherOperation::sign, RSA_PKCS1_PADDING)); +} +JSC_DEFINE_HOST_FUNCTION(jsPrivateDecrypt, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + return JSValue::encode(rsaFunction(globalObject, callFrame, KeyType::Private, CipherOperation::decrypt, RSA_PKCS1_OAEP_PADDING)); +} + } // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSCipher.h b/src/bun.js/bindings/node/crypto/JSCipher.h index a87f7084e5..7c0a4dd625 100644 --- a/src/bun.js/bindings/node/crypto/JSCipher.h +++ b/src/bun.js/bindings/node/crypto/JSCipher.h @@ -119,4 +119,9 @@ private: void setupCipherClassStructure(JSC::LazyClassStructure::Initializer&); +JSC_DECLARE_HOST_FUNCTION(jsPublicEncrypt); +JSC_DECLARE_HOST_FUNCTION(jsPublicDecrypt); +JSC_DECLARE_HOST_FUNCTION(jsPrivateEncrypt); +JSC_DECLARE_HOST_FUNCTION(jsPrivateDecrypt); + } // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSCipherConstructor.cpp b/src/bun.js/bindings/node/crypto/JSCipherConstructor.cpp index 28ff014fd5..ed43f741ad 100644 --- a/src/bun.js/bindings/node/crypto/JSCipherConstructor.cpp +++ b/src/bun.js/bindings/node/crypto/JSCipherConstructor.cpp @@ -10,6 +10,7 @@ #include "openssl/bn.h" #include "openssl/err.h" #include "ncrypto.h" +#include "KeyObject.h" using namespace JSC; using namespace WebCore; @@ -121,10 +122,11 @@ JSC_DEFINE_HOST_FUNCTION(constructCipher, (JSC::JSGlobalObject * globalObject, J } } - WTF::Vector keyData; - prepareSecretKey(globalObject, scope, keyData, keyValue, encodingValue); + KeyObject keyObject = KeyObject::prepareSecretKey(globalObject, scope, keyValue, encodingValue); RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + auto keyData = keyObject.symmetricKey().span(); + JSArrayBufferView* ivView = nullptr; if (!ivValue.isNull()) { ivView = getArrayBufferOrView(globalObject, scope, ivValue, "iv"_s, jsUndefined()); diff --git a/src/bun.js/bindings/node/crypto/JSDiffieHellmanGroup.h b/src/bun.js/bindings/node/crypto/JSDiffieHellmanGroup.h index ef9b9f91ad..cb3d544fd6 100644 --- a/src/bun.js/bindings/node/crypto/JSDiffieHellmanGroup.h +++ b/src/bun.js/bindings/node/crypto/JSDiffieHellmanGroup.h @@ -23,7 +23,7 @@ public: static JSDiffieHellmanGroup* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSGlobalObject* globalObject, ncrypto::DHPointer&& dh) { - JSDiffieHellmanGroup* instance = new (NotNull, JSC::allocateCell(vm)) JSDiffieHellmanGroup(vm, structure, std::move(dh)); + JSDiffieHellmanGroup* instance = new (NotNull, JSC::allocateCell(vm)) JSDiffieHellmanGroup(vm, structure, WTFMove(dh)); instance->finishCreation(vm, globalObject); return instance; } @@ -49,7 +49,7 @@ public: private: JSDiffieHellmanGroup(JSC::VM& vm, JSC::Structure* structure, ncrypto::DHPointer&& dh) : Base(vm, structure) - , m_dh(std::move(dh)) + , m_dh(WTFMove(dh)) { } diff --git a/src/bun.js/bindings/node/crypto/JSECDHConstructor.cpp b/src/bun.js/bindings/node/crypto/JSECDHConstructor.cpp index 371bb94eef..0450471b05 100644 --- a/src/bun.js/bindings/node/crypto/JSECDHConstructor.cpp +++ b/src/bun.js/bindings/node/crypto/JSECDHConstructor.cpp @@ -10,7 +10,6 @@ #include "openssl/bn.h" #include "openssl/err.h" #include "ncrypto.h" -#include "KeyObject.h" namespace Bun { diff --git a/src/bun.js/bindings/node/crypto/JSHmac.cpp b/src/bun.js/bindings/node/crypto/JSHmac.cpp index 861eacd575..a49827acc4 100644 --- a/src/bun.js/bindings/node/crypto/JSHmac.cpp +++ b/src/bun.js/bindings/node/crypto/JSHmac.cpp @@ -12,6 +12,7 @@ #include #include "NodeValidator.h" #include +#include "KeyObject.h" namespace Bun { @@ -257,7 +258,7 @@ JSC_DEFINE_HOST_FUNCTION(constructHmac, (JSC::JSGlobalObject * globalObject, JSC // Check if we have initialization arguments JSValue algorithmValue = callFrame->argument(0); - Bun::V::validateString(scope, globalObject, algorithmValue, "hmac"_s); + V::validateString(scope, globalObject, algorithmValue, "hmac"_s); RETURN_IF_EXCEPTION(scope, {}); // Get encoding next before stringifying algorithm @@ -268,7 +269,7 @@ JSC_DEFINE_HOST_FUNCTION(constructHmac, (JSC::JSGlobalObject * globalObject, JSC RETURN_IF_EXCEPTION(scope, {}); if (!encodingValue.isUndefinedOrNull()) { - Bun::V::validateString(scope, globalObject, encodingValue, "options.encoding"_s); + V::validateString(scope, globalObject, encodingValue, "options.encoding"_s); RETURN_IF_EXCEPTION(scope, {}); } } @@ -278,11 +279,10 @@ JSC_DEFINE_HOST_FUNCTION(constructHmac, (JSC::JSGlobalObject * globalObject, JSC JSValue key = callFrame->argument(1); - Vector keyData; - prepareSecretKey(globalObject, scope, keyData, key, encodingValue); + KeyObject keyObject = KeyObject::prepareSecretKey(globalObject, scope, key, encodingValue); RETURN_IF_EXCEPTION(scope, {}); - hmac->init(globalObject, scope, algorithm, keyData.span()); + hmac->init(globalObject, scope, algorithm, keyObject.symmetricKey().span()); RETURN_IF_EXCEPTION(scope, {}); return JSC::JSValue::encode(hmac); @@ -293,7 +293,7 @@ JSC_DEFINE_HOST_FUNCTION(callHmac, (JSC::JSGlobalObject * globalObject, JSC::Cal JSC::VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - throwTypeError(globalObject, scope, "Class constructor Hmac cannot be invoked without 'new'"_s); + throwConstructorCannotBeCalledAsFunctionTypeError(globalObject, scope, "Hmac"_s); return JSC::encodedJSUndefined(); } diff --git a/src/bun.js/bindings/node/crypto/JSKeyObject.cpp b/src/bun.js/bindings/node/crypto/JSKeyObject.cpp new file mode 100644 index 0000000000..d07202c08c --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSKeyObject.cpp @@ -0,0 +1,46 @@ +#include "JSKeyObject.h" +#include "JSKeyObjectPrototype.h" +#include "JSKeyObjectConstructor.h" +#include "DOMIsoSubspaces.h" +#include "ZigGlobalObject.h" +#include "ErrorCode.h" +#include +#include +#include +#include +#include + +namespace Bun { + +const JSC::ClassInfo JSKeyObject::s_info = { "KeyObject"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSKeyObject) }; + +void JSKeyObject::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); +} + +template +void JSKeyObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + JSKeyObject* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); +} + +DEFINE_VISIT_CHILDREN(JSKeyObject); + +void setupKeyObjectClassStructure(JSC::LazyClassStructure::Initializer& init) +{ + auto* prototypeStructure = JSKeyObjectPrototype::createStructure(init.vm, init.global, init.global->objectPrototype()); + auto* prototype = JSKeyObjectPrototype::create(init.vm, init.global, prototypeStructure); + + auto* constructorStructure = JSKeyObjectConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()); + auto* constructor = JSKeyObjectConstructor::create(init.vm, init.global, constructorStructure, prototype); + + auto* structure = JSKeyObject::createStructure(init.vm, init.global, prototype); + init.setPrototype(prototype); + init.setStructure(structure); + init.setConstructor(constructor); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSKeyObject.h b/src/bun.js/bindings/node/crypto/JSKeyObject.h new file mode 100644 index 0000000000..49af4225ee --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSKeyObject.h @@ -0,0 +1,67 @@ +#pragma once + +#include "root.h" +#include +#include +#include +#include +#include "ncrypto.h" +#include "BunClientData.h" +#include "openssl/ssl.h" +#include "KeyObject.h" + +namespace Bun { + +class JSKeyObject : public JSC::JSDestructibleObject { + WTF_MAKE_TZONE_ALLOCATED(JSKeyObject); + +public: + using Base = JSC::JSDestructibleObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + + static JSKeyObject* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSGlobalObject* globalObject, KeyObject&& keyObject) + { + JSKeyObject* instance = new (NotNull, JSC::allocateCell(vm)) JSKeyObject(vm, structure, WTFMove(keyObject)); + instance->finishCreation(vm, globalObject); + return instance; + } + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForJSKeyObject.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSKeyObject = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForJSKeyObject.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForJSKeyObject = std::forward(space); }); + } + + JSKeyObject(JSC::VM& vm, JSC::Structure* structure, KeyObject&& keyObject) + : Base(vm, structure) + , m_handle(WTFMove(keyObject)) + { + } + + KeyObject& handle() { return m_handle; } + const KeyObject& handle() const { return m_handle; } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); + static void destroy(JSC::JSCell* cell) { static_cast(cell)->~JSKeyObject(); } + + KeyObject m_handle; + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; +}; + +void setupKeyObjectClassStructure(JSC::LazyClassStructure::Initializer&); + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSKeyObjectConstructor.cpp b/src/bun.js/bindings/node/crypto/JSKeyObjectConstructor.cpp new file mode 100644 index 0000000000..04bfbcb1fa --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSKeyObjectConstructor.cpp @@ -0,0 +1,128 @@ +#include "JSKeyObjectConstructor.h" +#include "JSKeyObject.h" +#include "ErrorCode.h" +#include "JSBufferEncodingType.h" +#include "NodeValidator.h" +#include +#include +#include "CryptoUtil.h" +#include "openssl/dh.h" +#include "openssl/bn.h" +#include "openssl/err.h" +#include "ncrypto.h" +#include "JSCryptoKey.h" +#include "JSSecretKeyObject.h" +#include "JSPublicKeyObject.h" +#include "JSPrivateKeyObject.h" +#include "ZigGlobalObject.h" +#include "CryptoKeyAES.h" +#include "CryptoKeyHMAC.h" +#include "CryptoKeyRaw.h" +#include "CryptoKey.h" +#include "CryptoKeyType.h" +using namespace JSC; +using namespace WebCore; +using namespace ncrypto; + +namespace Bun { + +JSC_DECLARE_HOST_FUNCTION(jsKeyObjectConstructor_from); + +static const JSC::HashTableValue JSKeyObjectConstructorTableValues[] = { + { "from"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsKeyObjectConstructor_from, 1 } }, +}; + +const JSC::ClassInfo JSKeyObjectConstructor::s_info = { "KeyObject"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSKeyObjectConstructor) }; + +void JSKeyObjectConstructor::finishCreation(VM& vm, JSGlobalObject* globalObject, JSObject* prototype) +{ + Base::finishCreation(vm, 2, "KeyObject"_s); + putDirect(vm, vm.propertyNames->prototype, prototype, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly); + reifyStaticProperties(vm, JSKeyObjectConstructor::info(), JSKeyObjectConstructorTableValues, *this); +} + +JSC_DEFINE_HOST_FUNCTION(callKeyObject, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + throwTypeError(lexicalGlobalObject, scope, "Cannot call KeyObject class constructor without |new|"_s); + return JSValue::encode({}); +} + +JSC_DEFINE_HOST_FUNCTION(constructKeyObject, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSValue typeValue = callFrame->argument(0); + + if (!typeValue.isString()) { + // always INVALID_ARG_VALUE + // https://github.com/nodejs/node/blob/e1fabe4f58722af265d11081b91ce287f90738f4/lib/internal/crypto/keys.js#L108 + return ERR::INVALID_ARG_VALUE(scope, lexicalGlobalObject, "type"_s, typeValue); + } + + JSString* typeString = typeValue.toString(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + GCOwnedDataScope typeView = typeString->view(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + if (typeView != "secret"_s && typeView != "public"_s && typeView != "private"_s) { + return ERR::INVALID_ARG_VALUE(scope, lexicalGlobalObject, "type"_s, typeValue); + } + + JSValue handleValue = callFrame->argument(1); + // constructing a KeyObject is impossible + return ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "handle"_s, "object"_s, handleValue); +} + +JSC_DEFINE_HOST_FUNCTION(jsKeyObjectConstructor_from, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + // 1. Validate Input Argument + JSValue keyValue = callFrame->argument(0); + JSCryptoKey* cryptoKey = jsDynamicCast(keyValue); + + if (!cryptoKey) { + return ERR::INVALID_ARG_TYPE_INSTANCE(scope, globalObject, "key"_s, "CryptoKey"_s, keyValue); + } + + WebCore::CryptoKey& wrappedKey = cryptoKey->wrapped(); + + auto keyObjectResult = KeyObject::create(wrappedKey); + if (UNLIKELY(keyObjectResult.hasException())) { + WebCore::propagateException(*lexicalGlobalObject, scope, keyObjectResult.releaseException()); + return JSValue::encode({}); + } + + // 2. Determine Key Type and Extract Material + switch (wrappedKey.type()) { + case CryptoKeyType::Secret: { + auto* structure = globalObject->m_JSSecretKeyObjectClassStructure.get(globalObject); + JSSecretKeyObject* instance = JSSecretKeyObject::create(vm, structure, globalObject, keyObjectResult.releaseReturnValue()); + RELEASE_AND_RETURN(scope, JSValue::encode(instance)); + } + + case CryptoKeyType::Public: { + auto* structure = globalObject->m_JSPublicKeyObjectClassStructure.get(globalObject); + JSPublicKeyObject* instance = JSPublicKeyObject::create(vm, structure, globalObject, keyObjectResult.releaseReturnValue()); + RELEASE_AND_RETURN(scope, JSValue::encode(instance)); + } + + case CryptoKeyType::Private: { + auto* structure = globalObject->m_JSPrivateKeyObjectClassStructure.get(globalObject); + JSPrivateKeyObject* instance = JSPrivateKeyObject::create(vm, structure, globalObject, keyObjectResult.releaseReturnValue()); + RELEASE_AND_RETURN(scope, JSValue::encode(instance)); + } + } + + ASSERT_NOT_REACHED(); + + // Should not be reached + RELEASE_AND_RETURN(scope, JSValue::encode(jsUndefined())); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSKeyObjectConstructor.h b/src/bun.js/bindings/node/crypto/JSKeyObjectConstructor.h new file mode 100644 index 0000000000..92acba57cc --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSKeyObjectConstructor.h @@ -0,0 +1,45 @@ +#pragma once + +#include "root.h" +#include + +namespace Bun { + +JSC_DECLARE_HOST_FUNCTION(callKeyObject); +JSC_DECLARE_HOST_FUNCTION(constructKeyObject); + +class JSKeyObjectConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSKeyObjectConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSObject* prototype) + { + JSKeyObjectConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) JSKeyObjectConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; + } + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.internalFunctionSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); + } + +private: + JSKeyObjectConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, callKeyObject, constructKeyObject) + { + } + + void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSObject* prototype); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSKeyObjectPrototype.cpp b/src/bun.js/bindings/node/crypto/JSKeyObjectPrototype.cpp new file mode 100644 index 0000000000..79fa4aee24 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSKeyObjectPrototype.cpp @@ -0,0 +1,81 @@ +#include "JSKeyObjectPrototype.h" +#include "JSKeyObject.h" +#include "ErrorCode.h" +#include "CryptoUtil.h" +#include "BunProcess.h" +#include "NodeValidator.h" +#include "JSBufferEncodingType.h" +#include +#include + +using namespace Bun; +using namespace JSC; +using namespace WebCore; +using namespace ncrypto; + +JSC_DECLARE_HOST_FUNCTION(jsKeyObjectPrototype_equals); +JSC_DECLARE_CUSTOM_GETTER(jsKeyObjectPrototype_type); + +const JSC::ClassInfo JSKeyObjectPrototype::s_info = { "KeyObject"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSKeyObjectPrototype) }; + +static const JSC::HashTableValue JSKeyObjectPrototypeTableValues[] = { + { "equals"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsKeyObjectPrototype_equals, 1 } }, + { "type"_s, static_cast(PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::GetterSetterType, jsKeyObjectPrototype_type, 0 } }, +}; + +void JSKeyObjectPrototype::finishCreation(JSC::VM& vm) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSKeyObjectPrototype::info(), JSKeyObjectPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +JSC_DEFINE_HOST_FUNCTION(jsKeyObjectPrototype_equals, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSKeyObject* thisObject = jsDynamicCast(callFrame->thisValue()); + if (!thisObject) { + throwThisTypeError(*globalObject, scope, "KeyObject"_s, "equals"_s); + return JSValue::encode({}); + } + + JSValue otherKeyObjectValue = callFrame->argument(0); + JSKeyObject* otherKeyObject = jsDynamicCast(otherKeyObjectValue); + if (!otherKeyObject) { + return ERR::INVALID_ARG_INSTANCE(scope, globalObject, "otherKeyObject"_s, "KeyObject"_s, otherKeyObjectValue); + } + + KeyObject& thisHandle = thisObject->handle(); + KeyObject& otherHandle = otherKeyObject->handle(); + + std::optional result = thisHandle.equals(otherHandle); + if (!result.has_value()) { + return ERR::CRYPTO_UNSUPPORTED_OPERATION(scope, globalObject); + } + + return JSValue::encode(jsBoolean(*result)); +} + +JSC_DEFINE_CUSTOM_GETTER(jsKeyObjectPrototype_type, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName propertyName)) +{ + VM& vm = globalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSKeyObject* keyObject = jsDynamicCast(JSValue::decode(thisValue)); + if (!keyObject) { + return JSValue::encode(jsUndefined()); + } + + KeyObject& handle = keyObject->handle(); + + switch (handle.type()) { + case CryptoKeyType::Secret: + return JSValue::encode(jsNontrivialString(vm, "secret"_s)); + case CryptoKeyType::Public: + return JSValue::encode(jsNontrivialString(vm, "public"_s)); + case CryptoKeyType::Private: + return JSValue::encode(jsNontrivialString(vm, "private"_s)); + } +} diff --git a/src/bun.js/bindings/node/crypto/JSKeyObjectPrototype.h b/src/bun.js/bindings/node/crypto/JSKeyObjectPrototype.h new file mode 100644 index 0000000000..dae86a98f2 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSKeyObjectPrototype.h @@ -0,0 +1,44 @@ +#pragma once + +#include "root.h" +#include +#include + +namespace Bun { + +class JSKeyObjectPrototype : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSKeyObjectPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSKeyObjectPrototype* prototype = new (NotNull, JSC::allocateCell(vm)) JSKeyObjectPrototype(vm, structure); + prototype->finishCreation(vm); + return prototype; + } + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + + DECLARE_INFO; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + structure->setMayBePrototype(true); + return structure; + } + + JSKeyObjectPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSPrivateKeyObject.cpp b/src/bun.js/bindings/node/crypto/JSPrivateKeyObject.cpp new file mode 100644 index 0000000000..828e08ce43 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSPrivateKeyObject.cpp @@ -0,0 +1,49 @@ +#include "JSPrivateKeyObject.h" +#include "JSPrivateKeyObjectPrototype.h" +#include "JSKeyObjectConstructor.h" +#include "DOMIsoSubspaces.h" +#include "ZigGlobalObject.h" +#include "ErrorCode.h" +#include +#include +#include +#include +#include + +namespace Bun { + +const JSC::ClassInfo JSPrivateKeyObject::s_info = { "PrivateKeyObject"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSPrivateKeyObject) }; + +void JSPrivateKeyObject::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + Base::finishCreation(vm, globalObject); +} + +template +void JSPrivateKeyObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + JSPrivateKeyObject* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + visitor.append(thisObject->m_keyDetails); +} + +DEFINE_VISIT_CHILDREN(JSPrivateKeyObject); + +void setupPrivateKeyObjectClassStructure(JSC::LazyClassStructure::Initializer& init) +{ + auto* globalObject = defaultGlobalObject(init.global); + + auto* prototypeStructure = JSPrivateKeyObjectPrototype::createStructure(init.vm, init.global, globalObject->KeyObjectPrototype()); + auto* prototype = JSPrivateKeyObjectPrototype::create(init.vm, init.global, prototypeStructure); + + auto* constructorStructure = JSKeyObjectConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()); + auto* constructor = JSKeyObjectConstructor::create(init.vm, init.global, constructorStructure, prototype); + + auto* structure = JSPrivateKeyObject::createStructure(init.vm, init.global, prototype); + init.setPrototype(prototype); + init.setStructure(structure); + init.setConstructor(constructor); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSPrivateKeyObject.h b/src/bun.js/bindings/node/crypto/JSPrivateKeyObject.h new file mode 100644 index 0000000000..1b97bf3ddf --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSPrivateKeyObject.h @@ -0,0 +1,60 @@ +#pragma once + +#include "root.h" +#include +#include +#include +#include +#include "ncrypto.h" +#include "BunClientData.h" +#include "openssl/ssl.h" +#include "JSKeyObject.h" + +namespace Bun { + +class JSPrivateKeyObject final : public JSKeyObject { +public: + using Base = JSKeyObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + + static JSPrivateKeyObject* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSGlobalObject* globalObject, KeyObject&& keyObject) + { + JSPrivateKeyObject* instance = new (NotNull, JSC::allocateCell(vm)) JSPrivateKeyObject(vm, structure, WTFMove(keyObject)); + instance->finishCreation(vm, globalObject); + return instance; + } + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForJSPrivateKeyObject.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSPrivateKeyObject = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForJSPrivateKeyObject.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForJSPrivateKeyObject = std::forward(space); }); + } + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + JSC::WriteBarrier m_keyDetails; + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); + + JSPrivateKeyObject(JSC::VM& vm, JSC::Structure* structure, KeyObject&& keyObject) + : Base(vm, structure, WTFMove(keyObject)) + { + } +}; + +void setupPrivateKeyObjectClassStructure(JSC::LazyClassStructure::Initializer&); + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSPrivateKeyObjectConstructor.cpp b/src/bun.js/bindings/node/crypto/JSPrivateKeyObjectConstructor.cpp new file mode 100644 index 0000000000..820409fd8f --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSPrivateKeyObjectConstructor.cpp @@ -0,0 +1,40 @@ +#include "JSPrivateKeyObjectConstructor.h" +#include "JSPrivateKeyObject.h" +#include "ErrorCode.h" +#include "JSBufferEncodingType.h" +#include "NodeValidator.h" +#include +#include +#include "CryptoUtil.h" +#include "openssl/dh.h" +#include "openssl/bn.h" +#include "openssl/err.h" +#include "ncrypto.h" + +using namespace JSC; +using namespace WebCore; +using namespace ncrypto; + +namespace Bun { + +const JSC::ClassInfo JSPrivateKeyObjectConstructor::s_info = { "PrivateKeyObject"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSPrivateKeyObjectConstructor) }; + +JSC_DEFINE_HOST_FUNCTION(callPrivateKeyObject, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + throwConstructorCannotBeCalledAsFunctionTypeError(lexicalGlobalObject, scope, "PrivateKeyObject"_s); + return JSValue::encode({}); +} + +JSC_DEFINE_HOST_FUNCTION(constructPrivateKeyObject, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSValue handleValue = callFrame->argument(0); + // constructing a PrivateKeyObject is impossible + return ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "handle"_s, "object"_s, handleValue); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSPrivateKeyObjectConstructor.h b/src/bun.js/bindings/node/crypto/JSPrivateKeyObjectConstructor.h new file mode 100644 index 0000000000..b843e40bca --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSPrivateKeyObjectConstructor.h @@ -0,0 +1,49 @@ +#pragma once + +#include "root.h" +#include + +namespace Bun { + +JSC_DECLARE_HOST_FUNCTION(callPrivateKeyObject); +JSC_DECLARE_HOST_FUNCTION(constructPrivateKeyObject); + +class JSPrivateKeyObjectConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSPrivateKeyObjectConstructor* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSObject* prototype) + { + JSPrivateKeyObjectConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) JSPrivateKeyObjectConstructor(vm, structure); + constructor->finishCreation(vm, prototype); + return constructor; + } + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.internalFunctionSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); + } + +private: + JSPrivateKeyObjectConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, callPrivateKeyObject, constructPrivateKeyObject) + { + } + + void finishCreation(JSC::VM& vm, JSC::JSObject* prototype) + { + Base::finishCreation(vm, 2, "PrivateKeyObject"_s); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly); + } +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSPrivateKeyObjectPrototype.cpp b/src/bun.js/bindings/node/crypto/JSPrivateKeyObjectPrototype.cpp new file mode 100644 index 0000000000..412e4d7ec5 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSPrivateKeyObjectPrototype.cpp @@ -0,0 +1,108 @@ +#include "JSPrivateKeyObjectPrototype.h" +#include "JSPrivateKeyObject.h" +#include "ErrorCode.h" +#include "CryptoUtil.h" +#include "BunProcess.h" +#include "NodeValidator.h" +#include "JSBufferEncodingType.h" +#include +#include + +using namespace Bun; +using namespace JSC; +using namespace WebCore; +using namespace ncrypto; + +JSC_DECLARE_HOST_FUNCTION(jsPrivateKeyObjectPrototype_export); +JSC_DECLARE_CUSTOM_GETTER(jsPrivateKeyObjectPrototype_asymmetricKeyType); +JSC_DECLARE_CUSTOM_GETTER(jsPrivateKeyObjectPrototype_asymmetricKeyDetails); +JSC_DECLARE_HOST_FUNCTION(jsPrivateKeyObjectPrototype_toCryptoKey); + +const JSC::ClassInfo JSPrivateKeyObjectPrototype::s_info = { "PrivateKeyObject"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSPrivateKeyObjectPrototype) }; + +static const JSC::HashTableValue JSPrivateKeyObjectPrototypeTableValues[] = { + { "asymmetricKeyType"_s, static_cast(PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::GetterSetterType, jsPrivateKeyObjectPrototype_asymmetricKeyType, 0 } }, + { "asymmetricKeyDetails"_s, static_cast(PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::GetterSetterType, jsPrivateKeyObjectPrototype_asymmetricKeyDetails, 0 } }, + { "export"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsPrivateKeyObjectPrototype_export, 1 } }, + // { "toCryptoKey"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsPrivateKeyObjectPrototype_toCryptoKey, 3 } }, +}; + +void JSPrivateKeyObjectPrototype::finishCreation(JSC::VM& vm) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSPrivateKeyObjectPrototype::info(), JSPrivateKeyObjectPrototypeTableValues, *this); + + // intentionally inherit KeyObject's toStringTag + // https://github.com/nodejs/node/blob/95b0f9d448832eeb75586c89fab0777a1a4b0610/lib/internal/crypto/keys.js#L146 +} + +JSC_DEFINE_HOST_FUNCTION(jsPrivateKeyObjectPrototype_export, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSPrivateKeyObject* privateKeyObject = jsDynamicCast(callFrame->thisValue()); + if (!privateKeyObject) { + throwThisTypeError(*globalObject, scope, "PrivateKeyObject"_s, "export"_s); + return {}; + } + + KeyObject& handle = privateKeyObject->handle(); + JSValue optionsValue = callFrame->argument(0); + return JSValue::encode(handle.exportAsymmetric(globalObject, scope, optionsValue, CryptoKeyType::Private)); +} + +JSC_DEFINE_HOST_FUNCTION(jsPrivateKeyObjectPrototype_asymmetricKeyType, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName propertyName)) +{ + VM& vm = globalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSPrivateKeyObject* privateKeyObject = jsDynamicCast(JSValue::decode(thisValue)); + if (!privateKeyObject) { + return JSValue::encode(jsUndefined()); + } + + KeyObject& handle = privateKeyObject->handle(); + return JSValue::encode(handle.asymmetricKeyType(globalObject)); +} + +JSC_DEFINE_HOST_FUNCTION(jsPrivateKeyObjectPrototype_asymmetricKeyDetails, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName propertyName)) +{ + VM& vm = globalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSPrivateKeyObject* privateKeyObject = jsDynamicCast(JSValue::decode(thisValue)); + if (!privateKeyObject) { + return JSValue::encode(jsUndefined()); + } + + if (auto* keyDetails = privateKeyObject->m_keyDetails.get()) { + return JSValue::encode(keyDetails); + } + + KeyObject& handle = privateKeyObject->handle(); + JSObject* keyDetails = handle.asymmetricKeyDetails(globalObject, scope); + RETURN_IF_EXCEPTION(scope, {}); + + privateKeyObject->m_keyDetails.set(vm, privateKeyObject, keyDetails); + return JSValue::encode(keyDetails); +} + +// JSC_DEFINE_HOST_FUNCTION(jsPrivateKeyObjectPrototype_toCryptoKey, (JSGlobalObject * globalObject, CallFrame* callFrame)) +// { +// VM& vm = globalObject->vm(); +// ThrowScope scope = DECLARE_THROW_SCOPE(vm); + +// JSPrivateKeyObject* privateKeyObject = jsDynamicCast(callFrame->thisValue()); +// if (!privateKeyObject) { +// throwThisTypeError(*globalObject, scope, "PrivateKeyObject"_s, "toCryptoKey"_s); +// return JSValue::encode({}); +// } + +// KeyObject& handle = privateKeyObject->handle(); +// JSValue algorithmValue = callFrame->argument(0); +// JSValue extractableValue = callFrame->argument(1); +// JSValue keyUsagesValue = callFrame->argument(2); + +// return JSValue::encode(handle.toCryptoKey(globalObject, scope, algorithmValue, extractableValue, keyUsagesValue)); +// } diff --git a/src/bun.js/bindings/node/crypto/JSPrivateKeyObjectPrototype.h b/src/bun.js/bindings/node/crypto/JSPrivateKeyObjectPrototype.h new file mode 100644 index 0000000000..7d25af0d01 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSPrivateKeyObjectPrototype.h @@ -0,0 +1,45 @@ +#pragma once + +#include "root.h" +#include +#include + +namespace Bun { + +class JSPrivateKeyObjectPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSPrivateKeyObjectPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSPrivateKeyObjectPrototype* prototype = new (NotNull, JSC::allocateCell(vm)) JSPrivateKeyObjectPrototype(vm, structure); + prototype->finishCreation(vm); + return prototype; + } + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + + DECLARE_INFO; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + structure->setMayBePrototype(true); + return structure; + } + +private: + JSPrivateKeyObjectPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSPublicKeyObject.cpp b/src/bun.js/bindings/node/crypto/JSPublicKeyObject.cpp new file mode 100644 index 0000000000..3bf3d02a0a --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSPublicKeyObject.cpp @@ -0,0 +1,49 @@ +#include "JSPublicKeyObject.h" +#include "JSPublicKeyObjectPrototype.h" +#include "JSKeyObjectConstructor.h" +#include "DOMIsoSubspaces.h" +#include "ZigGlobalObject.h" +#include "ErrorCode.h" +#include +#include +#include +#include +#include + +namespace Bun { + +const JSC::ClassInfo JSPublicKeyObject::s_info = { "PublicKeyObject"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSPublicKeyObject) }; + +void JSPublicKeyObject::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + Base::finishCreation(vm, globalObject); +} + +template +void JSPublicKeyObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + JSPublicKeyObject* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + visitor.append(thisObject->m_keyDetails); +} + +DEFINE_VISIT_CHILDREN(JSPublicKeyObject); + +void setupPublicKeyObjectClassStructure(JSC::LazyClassStructure::Initializer& init) +{ + auto* globalObject = defaultGlobalObject(init.global); + + auto* prototypeStructure = JSPublicKeyObjectPrototype::createStructure(init.vm, init.global, globalObject->KeyObjectPrototype()); + auto* prototype = JSPublicKeyObjectPrototype::create(init.vm, init.global, prototypeStructure); + + auto* constructorStructure = JSKeyObjectConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()); + auto* constructor = JSKeyObjectConstructor::create(init.vm, init.global, constructorStructure, prototype); + + auto* structure = JSPublicKeyObject::createStructure(init.vm, init.global, prototype); + init.setPrototype(prototype); + init.setStructure(structure); + init.setConstructor(constructor); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSPublicKeyObject.h b/src/bun.js/bindings/node/crypto/JSPublicKeyObject.h new file mode 100644 index 0000000000..4354f6a57c --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSPublicKeyObject.h @@ -0,0 +1,61 @@ +#pragma once + +#include "root.h" +#include +#include +#include +#include +#include "ncrypto.h" +#include "BunClientData.h" +#include "openssl/ssl.h" +#include "JSKeyObject.h" +#include "KeyObject.h" + +namespace Bun { + +class JSPublicKeyObject final : public JSKeyObject { +public: + using Base = JSKeyObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + + static JSPublicKeyObject* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSGlobalObject* globalObject, KeyObject&& keyObject) + { + JSPublicKeyObject* instance = new (NotNull, JSC::allocateCell(vm)) JSPublicKeyObject(vm, structure, WTFMove(keyObject)); + instance->finishCreation(vm, globalObject); + return instance; + } + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForJSPublicKeyObject.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSPublicKeyObject = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForJSPublicKeyObject.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForJSPublicKeyObject = std::forward(space); }); + } + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + JSC::WriteBarrier m_keyDetails; + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); + + JSPublicKeyObject(JSC::VM& vm, JSC::Structure* structure, KeyObject&& keyObject) + : Base(vm, structure, WTFMove(keyObject)) + { + } +}; + +void setupPublicKeyObjectClassStructure(JSC::LazyClassStructure::Initializer&); + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSPublicKeyObjectConstructor.cpp b/src/bun.js/bindings/node/crypto/JSPublicKeyObjectConstructor.cpp new file mode 100644 index 0000000000..fd9f52b531 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSPublicKeyObjectConstructor.cpp @@ -0,0 +1,40 @@ +#include "JSPublicKeyObjectConstructor.h" +#include "JSPublicKeyObject.h" +#include "ErrorCode.h" +#include "JSBufferEncodingType.h" +#include "NodeValidator.h" +#include +#include +#include "CryptoUtil.h" +#include "openssl/dh.h" +#include "openssl/bn.h" +#include "openssl/err.h" +#include "ncrypto.h" + +using namespace JSC; +using namespace WebCore; +using namespace ncrypto; + +namespace Bun { + +const JSC::ClassInfo JSPublicKeyObjectConstructor::s_info = { "PublicKeyObject"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSPublicKeyObjectConstructor) }; + +JSC_DEFINE_HOST_FUNCTION(callPublicKeyObject, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + throwConstructorCannotBeCalledAsFunctionTypeError(lexicalGlobalObject, scope, "PublicKeyObject"_s); + return JSValue::encode({}); +} + +JSC_DEFINE_HOST_FUNCTION(constructPublicKeyObject, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSValue handleValue = callFrame->argument(0); + // constructing a PublicKeyObject is impossible + return ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "handle"_s, "object"_s, handleValue); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSPublicKeyObjectConstructor.h b/src/bun.js/bindings/node/crypto/JSPublicKeyObjectConstructor.h new file mode 100644 index 0000000000..27c7da9cc9 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSPublicKeyObjectConstructor.h @@ -0,0 +1,49 @@ +#pragma once + +#include "root.h" +#include + +namespace Bun { + +JSC_DECLARE_HOST_FUNCTION(callPublicKeyObject); +JSC_DECLARE_HOST_FUNCTION(constructPublicKeyObject); + +class JSPublicKeyObjectConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSPublicKeyObjectConstructor* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSObject* prototype) + { + JSPublicKeyObjectConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) JSPublicKeyObjectConstructor(vm, structure); + constructor->finishCreation(vm, prototype); + return constructor; + } + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.internalFunctionSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); + } + +private: + JSPublicKeyObjectConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, callPublicKeyObject, constructPublicKeyObject) + { + } + + void finishCreation(JSC::VM& vm, JSC::JSObject* prototype) + { + Base::finishCreation(vm, 2, "PublicKeyObject"_s); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly); + } +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSPublicKeyObjectPrototype.cpp b/src/bun.js/bindings/node/crypto/JSPublicKeyObjectPrototype.cpp new file mode 100644 index 0000000000..f21c272c7d --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSPublicKeyObjectPrototype.cpp @@ -0,0 +1,109 @@ +#include "JSPublicKeyObjectPrototype.h" +#include "JSPublicKeyObject.h" +#include "ErrorCode.h" +#include "CryptoUtil.h" +#include "BunProcess.h" +#include "NodeValidator.h" +#include "JSBufferEncodingType.h" +#include +#include + +using namespace Bun; +using namespace JSC; +using namespace WebCore; +using namespace ncrypto; + +JSC_DECLARE_HOST_FUNCTION(jsPublicKeyObjectPrototype_export); +JSC_DECLARE_CUSTOM_GETTER(jsPublicKeyObjectPrototype_asymmetricKeyType); +JSC_DECLARE_CUSTOM_GETTER(jsPublicKeyObjectPrototype_asymmetricKeyDetails); +JSC_DECLARE_HOST_FUNCTION(jsPublicKeyObjectPrototype_toCryptoKey); + +const JSC::ClassInfo JSPublicKeyObjectPrototype::s_info = { "PublicKeyObject"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSPublicKeyObjectPrototype) }; + +static const JSC::HashTableValue JSPublicKeyObjectPrototypeTableValues[] = { + { "asymmetricKeyType"_s, static_cast(PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::GetterSetterType, jsPublicKeyObjectPrototype_asymmetricKeyType, 0 } }, + { "asymmetricKeyDetails"_s, static_cast(PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::GetterSetterType, jsPublicKeyObjectPrototype_asymmetricKeyDetails, 0 } }, + { "export"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsPublicKeyObjectPrototype_export, 1 } }, + // { "toCryptoKey"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsPublicKeyObjectPrototype_toCryptoKey, 3 } }, +}; + +void JSPublicKeyObjectPrototype::finishCreation(JSC::VM& vm) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSPublicKeyObjectPrototype::info(), JSPublicKeyObjectPrototypeTableValues, *this); + + // intentionally inherit KeyObject's toStringTag + // https://github.com/nodejs/node/blob/95b0f9d448832eeb75586c89fab0777a1a4b0610/lib/internal/crypto/keys.js#L146 +} + +JSC_DEFINE_HOST_FUNCTION(jsPublicKeyObjectPrototype_export, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSPublicKeyObject* publicKeyObject = jsDynamicCast(callFrame->thisValue()); + if (!publicKeyObject) { + throwThisTypeError(*globalObject, scope, "PublicKeyObject"_s, "export"_s); + return JSValue::encode({}); + } + + KeyObject& handle = publicKeyObject->handle(); + JSValue optionsValue = callFrame->argument(0); + return JSValue::encode(handle.exportAsymmetric(globalObject, scope, optionsValue, CryptoKeyType::Public)); +} + +JSC_DEFINE_HOST_FUNCTION(jsPublicKeyObjectPrototype_asymmetricKeyType, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName propertyName)) +{ + VM& vm = globalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSPublicKeyObject* publicKeyObject = jsDynamicCast(JSValue::decode(thisValue)); + if (!publicKeyObject) { + return JSValue::encode(jsUndefined()); + } + + KeyObject& handle = publicKeyObject->handle(); + return JSValue::encode(handle.asymmetricKeyType(globalObject)); +} + +JSC_DEFINE_HOST_FUNCTION(jsPublicKeyObjectPrototype_asymmetricKeyDetails, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName propertyName)) +{ + VM& vm = globalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSPublicKeyObject* publicKeyObject = jsDynamicCast(JSValue::decode(thisValue)); + if (!publicKeyObject) { + return JSValue::encode(jsUndefined()); + } + + if (auto* keyDetails = publicKeyObject->m_keyDetails.get()) { + return JSValue::encode(keyDetails); + } + + KeyObject& handle = publicKeyObject->handle(); + JSObject* keyDetails = handle.asymmetricKeyDetails(globalObject, scope); + RETURN_IF_EXCEPTION(scope, {}); + + publicKeyObject->m_keyDetails.set(vm, publicKeyObject, keyDetails); + + return JSValue::encode(keyDetails); +} + +// JSC_DEFINE_HOST_FUNCTION(jsPublicKeyObjectPrototype_toCryptoKey, (JSGlobalObject * globalObject, CallFrame* callFrame)) +// { +// VM& vm = globalObject->vm(); +// ThrowScope scope = DECLARE_THROW_SCOPE(vm); + +// JSPublicKeyObject* publicKeyObject = jsDynamicCast(callFrame->thisValue()); +// if (!publicKeyObject) { +// throwThisTypeError(*globalObject, scope, "PublicKeyObject"_s, "toCryptoKey"_s); +// return JSValue::encode({}); +// } + +// KeyObject& handle = publicKeyObject->handle(); +// JSValue algorithmValue = callFrame->argument(0); +// JSValue extractableValue = callFrame->argument(1); +// JSValue keyUsagesValue = callFrame->argument(2); + +// return JSValue::encode(handle.toCryptoKey(globalObject, scope, algorithmValue, extractableValue, keyUsagesValue)); +// } diff --git a/src/bun.js/bindings/node/crypto/JSPublicKeyObjectPrototype.h b/src/bun.js/bindings/node/crypto/JSPublicKeyObjectPrototype.h new file mode 100644 index 0000000000..2a4d4697b1 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSPublicKeyObjectPrototype.h @@ -0,0 +1,45 @@ +#pragma once + +#include "root.h" +#include +#include + +namespace Bun { + +class JSPublicKeyObjectPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSPublicKeyObjectPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSPublicKeyObjectPrototype* prototype = new (NotNull, JSC::allocateCell(vm)) JSPublicKeyObjectPrototype(vm, structure); + prototype->finishCreation(vm); + return prototype; + } + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + + DECLARE_INFO; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + structure->setMayBePrototype(true); + return structure; + } + +private: + JSPublicKeyObjectPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSSecretKeyObject.cpp b/src/bun.js/bindings/node/crypto/JSSecretKeyObject.cpp new file mode 100644 index 0000000000..2326740ba0 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSSecretKeyObject.cpp @@ -0,0 +1,48 @@ +#include "JSSecretKeyObject.h" +#include "JSSecretKeyObjectPrototype.h" +#include "JSSecretKeyObjectConstructor.h" +#include "DOMIsoSubspaces.h" +#include "ZigGlobalObject.h" +#include "ErrorCode.h" +#include +#include +#include +#include +#include + +namespace Bun { + +const JSC::ClassInfo JSSecretKeyObject::s_info = { "SecretKeyObject"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSSecretKeyObject) }; + +void JSSecretKeyObject::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + Base::finishCreation(vm, globalObject); +} + +template +void JSSecretKeyObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + JSSecretKeyObject* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); +} + +DEFINE_VISIT_CHILDREN(JSSecretKeyObject); + +void setupSecretKeyObjectClassStructure(JSC::LazyClassStructure::Initializer& init) +{ + auto* globalObject = defaultGlobalObject(init.global); + + auto* prototypeStructure = JSSecretKeyObjectPrototype::createStructure(init.vm, init.global, globalObject->KeyObjectPrototype()); + auto* prototype = JSSecretKeyObjectPrototype::create(init.vm, init.global, prototypeStructure); + + auto* constructorStructure = JSSecretKeyObjectConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()); + auto* constructor = JSSecretKeyObjectConstructor::create(init.vm, constructorStructure, prototype); + + auto* structure = JSSecretKeyObject::createStructure(init.vm, init.global, prototype); + init.setPrototype(prototype); + init.setStructure(structure); + init.setConstructor(constructor); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSSecretKeyObject.h b/src/bun.js/bindings/node/crypto/JSSecretKeyObject.h new file mode 100644 index 0000000000..e0a2d95664 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSSecretKeyObject.h @@ -0,0 +1,58 @@ +#pragma once + +#include "root.h" +#include +#include +#include +#include +#include "ncrypto.h" +#include "BunClientData.h" +#include "openssl/ssl.h" +#include "JSKeyObject.h" + +namespace Bun { + +class JSSecretKeyObject final : public JSKeyObject { +public: + using Base = JSKeyObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + + static JSSecretKeyObject* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSGlobalObject* globalObject, KeyObject&& keyObject) + { + JSSecretKeyObject* instance = new (NotNull, JSC::allocateCell(vm)) JSSecretKeyObject(vm, structure, WTFMove(keyObject)); + instance->finishCreation(vm, globalObject); + return instance; + } + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForJSSecretKeyObject.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSSecretKeyObject = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForJSSecretKeyObject.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForJSSecretKeyObject = std::forward(space); }); + } + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); + + JSSecretKeyObject(JSC::VM& vm, JSC::Structure* structure, KeyObject&& keyObject) + : Base(vm, structure, WTFMove(keyObject)) + { + } +}; + +void setupSecretKeyObjectClassStructure(JSC::LazyClassStructure::Initializer&); + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSSecretKeyObjectConstructor.cpp b/src/bun.js/bindings/node/crypto/JSSecretKeyObjectConstructor.cpp new file mode 100644 index 0000000000..6e5e5f2e37 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSSecretKeyObjectConstructor.cpp @@ -0,0 +1,39 @@ +#include "JSSecretKeyObjectConstructor.h" +#include "JSSecretKeyObject.h" +#include "ErrorCode.h" +#include "JSBufferEncodingType.h" +#include "NodeValidator.h" +#include +#include +#include "CryptoUtil.h" +#include "openssl/dh.h" +#include "openssl/bn.h" +#include "openssl/err.h" +#include "ncrypto.h" + +using namespace JSC; +using namespace WebCore; +using namespace ncrypto; + +namespace Bun { + +const JSC::ClassInfo JSSecretKeyObjectConstructor::s_info = { "SecretKeyObject"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSSecretKeyObjectConstructor) }; + +JSC_DEFINE_HOST_FUNCTION(callSecretKeyObject, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + throwConstructorCannotBeCalledAsFunctionTypeError(lexicalGlobalObject, scope, "SecretKeyObject"_s); + return JSValue::encode({}); +} + +JSC_DEFINE_HOST_FUNCTION(constructSecretKeyObject, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = lexicalGlobalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSValue handleValue = callFrame->argument(0); + return ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "handle"_s, "object"_s, handleValue); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSSecretKeyObjectConstructor.h b/src/bun.js/bindings/node/crypto/JSSecretKeyObjectConstructor.h new file mode 100644 index 0000000000..1ec0f8b0ba --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSSecretKeyObjectConstructor.h @@ -0,0 +1,49 @@ +#pragma once + +#include "root.h" +#include + +namespace Bun { + +JSC_DECLARE_HOST_FUNCTION(callSecretKeyObject); +JSC_DECLARE_HOST_FUNCTION(constructSecretKeyObject); + +class JSSecretKeyObjectConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSSecretKeyObjectConstructor* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSObject* prototype) + { + JSSecretKeyObjectConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) JSSecretKeyObjectConstructor(vm, structure); + constructor->finishCreation(vm, prototype); + return constructor; + } + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.internalFunctionSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); + } + +private: + JSSecretKeyObjectConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, callSecretKeyObject, constructSecretKeyObject) + { + } + + void finishCreation(JSC::VM& vm, JSC::JSObject* prototype) + { + Base::finishCreation(vm, 2, "SecretKeyObject"_s); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly); + } +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSSecretKeyObjectPrototype.cpp b/src/bun.js/bindings/node/crypto/JSSecretKeyObjectPrototype.cpp new file mode 100644 index 0000000000..222871d1fd --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSSecretKeyObjectPrototype.cpp @@ -0,0 +1,82 @@ +#include "JSSecretKeyObjectPrototype.h" +#include "JSSecretKeyObject.h" +#include "ErrorCode.h" +#include "CryptoUtil.h" +#include "BunProcess.h" +#include "NodeValidator.h" +#include "JSBufferEncodingType.h" +#include +#include + +using namespace Bun; +using namespace JSC; +using namespace WebCore; +using namespace ncrypto; + +JSC_DECLARE_HOST_FUNCTION(jsSecretKeyObjectExport); +JSC_DECLARE_CUSTOM_GETTER(jsSecretKeyObjectSymmetricKeySize); +JSC_DECLARE_HOST_FUNCTION(jsSecretKeyObjectToCryptoKey); + +const JSC::ClassInfo JSSecretKeyObjectPrototype::s_info = { "SecretKeyObject"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSSecretKeyObjectPrototype) }; + +static const JSC::HashTableValue JSSecretKeyObjectPrototypeTableValues[] = { + { "export"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSecretKeyObjectExport, 1 } }, + { "symmetricKeySize"_s, static_cast(PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::GetterSetterType, jsSecretKeyObjectSymmetricKeySize, 0 } }, + // { "toCryptoKey"_s, static_cast(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSecretKeyObjectToCryptoKey, 3 } }, +}; + +void JSSecretKeyObjectPrototype::finishCreation(JSC::VM& vm) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSSecretKeyObjectPrototype::info(), JSSecretKeyObjectPrototypeTableValues, *this); + + // intentionally inherit KeyObject's toStringTag + // https://github.com/nodejs/node/blob/95b0f9d448832eeb75586c89fab0777a1a4b0610/lib/internal/crypto/keys.js#L146 +} + +JSC_DEFINE_HOST_FUNCTION(jsSecretKeyObjectExport, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + ThrowScope scope = DECLARE_THROW_SCOPE(vm); + + JSSecretKeyObject* secretKeyObject = jsDynamicCast(callFrame->thisValue()); + if (!secretKeyObject) { + throwThisTypeError(*globalObject, scope, "SecretKeyObject"_s, "export"_s); + return JSValue::encode({}); + } + + KeyObject& handle = secretKeyObject->handle(); + JSValue optionsValue = callFrame->argument(0); + + return JSValue::encode(handle.exportSecret(globalObject, scope, optionsValue)); +} + +JSC_DEFINE_CUSTOM_GETTER(jsSecretKeyObjectSymmetricKeySize, (JSGlobalObject*, JSC::EncodedJSValue thisValue, PropertyName propertyName)) +{ + JSSecretKeyObject* secretKeyObject = jsDynamicCast(JSValue::decode(thisValue)); + if (!secretKeyObject) { + return JSValue::encode(jsUndefined()); + } + + size_t symmetricKeySize = secretKeyObject->handle().symmetricKey().size(); + return JSValue::encode(jsNumber(symmetricKeySize)); +} + +// JSC_DEFINE_HOST_FUNCTION(jsSecretKeyObjectToCryptoKey, (JSGlobalObject * globalObject, CallFrame* callFrame)) +// { +// VM& vm = globalObject->vm(); +// ThrowScope scope = DECLARE_THROW_SCOPE(vm); + +// JSSecretKeyObject* secretKeyObject = jsDynamicCast(callFrame->thisValue()); +// if (!secretKeyObject) { +// throwThisTypeError(*globalObject, scope, "SecretKeyObject"_s, "toCryptoKey"_s); +// return JSValue::encode({}); +// } + +// KeyObject& handle = secretKeyObject->handle(); +// JSValue algorithmValue = callFrame->argument(0); +// JSValue extractableValue = callFrame->argument(1); +// JSValue keyUsagesValue = callFrame->argument(2); + +// return JSValue::encode(handle.toCryptoKey(globalObject, scope, algorithmValue, extractableValue, keyUsagesValue)); +// } diff --git a/src/bun.js/bindings/node/crypto/JSSecretKeyObjectPrototype.h b/src/bun.js/bindings/node/crypto/JSSecretKeyObjectPrototype.h new file mode 100644 index 0000000000..215d1a5a1e --- /dev/null +++ b/src/bun.js/bindings/node/crypto/JSSecretKeyObjectPrototype.h @@ -0,0 +1,46 @@ +#pragma once + +#include "root.h" +#include +#include +#include "JSKeyObjectPrototype.h" + +namespace Bun { + +class JSSecretKeyObjectPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSSecretKeyObjectPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSSecretKeyObjectPrototype* prototype = new (NotNull, JSC::allocateCell(vm)) JSSecretKeyObjectPrototype(vm, structure); + prototype->finishCreation(vm); + return prototype; + } + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + + DECLARE_INFO; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + structure->setMayBePrototype(true); + return structure; + } + +private: + JSSecretKeyObjectPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSSign.cpp b/src/bun.js/bindings/node/crypto/JSSign.cpp index b7de703cf3..7835db85bc 100644 --- a/src/bun.js/bindings/node/crypto/JSSign.cpp +++ b/src/bun.js/bindings/node/crypto/JSSign.cpp @@ -8,9 +8,7 @@ #include #include #include "JSBufferEncodingType.h" -#include "KeyObject.h" #include "JSCryptoKey.h" -#include "AsymmetricKeyValue.h" #include "NodeValidator.h" #include "JSBuffer.h" #include "CryptoUtil.h" @@ -18,6 +16,7 @@ #include "JSVerify.h" #include "CryptoAlgorithmRegistry.h" #include "CryptoKeyRSA.h" +#include "KeyObject.h" namespace Bun { @@ -291,7 +290,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSignProtoFuncUpdate, (JSC::JSGlobalObject * globalObj return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "data"_s, "string or an instance of Buffer, TypedArray, or DataView"_s, data); } -JSUint8Array* signWithKey(JSC::JSGlobalObject* lexicalGlobalObject, JSSign* thisObject, const ncrypto::EVPKeyPointer& pkey, NodeCryptoKeys::DSASigEnc dsa_sig_enc, int padding, std::optional salt_len) +JSUint8Array* signWithKey(JSC::JSGlobalObject* lexicalGlobalObject, JSSign* thisObject, const ncrypto::EVPKeyPointer& pkey, DSASigEnc dsa_sig_enc, int padding, std::optional salt_len) { JSC::VM& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); @@ -358,7 +357,7 @@ JSUint8Array* signWithKey(JSC::JSGlobalObject* lexicalGlobalObject, JSSign* this } // Convert to P1363 format if requested for EC keys - if (dsa_sig_enc == NodeCryptoKeys::DSASigEnc::P1363 && pkey.isSigVariant()) { + if (dsa_sig_enc == DSASigEnc::P1363 && pkey.isSigVariant()) { auto p1363Size = pkey.getBytesOfRS().value_or(0) * 2; if (p1363Size > 0) { auto p1363Buffer = JSC::ArrayBuffer::tryCreate(p1363Size, 1); @@ -420,23 +419,36 @@ JSC_DEFINE_HOST_FUNCTION(jsSignProtoFuncSign, (JSC::JSGlobalObject * lexicalGlob RETURN_IF_EXCEPTION(scope, JSValue::encode({})); // Get RSA padding mode and salt length if applicable - int32_t padding = getPadding(lexicalGlobalObject, options, {}); + int32_t padding = getPadding(lexicalGlobalObject, scope, options, {}); RETURN_IF_EXCEPTION(scope, JSValue::encode({})); - std::optional saltLen = getSaltLength(lexicalGlobalObject, options); + std::optional saltLen = getSaltLength(lexicalGlobalObject, scope, options); RETURN_IF_EXCEPTION(scope, JSValue::encode({})); // Get DSA signature encoding format - NodeCryptoKeys::DSASigEnc dsaSigEnc = getDSASigEnc(lexicalGlobalObject, options); + DSASigEnc dsaSigEnc = getDSASigEnc(lexicalGlobalObject, scope, options); RETURN_IF_EXCEPTION(scope, JSValue::encode({})); - // Get key argument - std::optional maybeKeyPtr = preparePrivateKey(lexicalGlobalObject, scope, options, std::nullopt); - ASSERT(!!scope.exception() == !maybeKeyPtr.has_value()); - if (!maybeKeyPtr) { - return {}; + auto prepareResult = KeyObject::preparePrivateKey(lexicalGlobalObject, scope, options); + RETURN_IF_EXCEPTION(scope, {}); + + KeyObject keyObject; + if (prepareResult.keyData) { + keyObject = KeyObject::create(CryptoKeyType::Private, WTFMove(*prepareResult.keyData)); + } else { + keyObject = KeyObject::getPublicOrPrivateKey( + lexicalGlobalObject, + scope, + prepareResult.keyDataView, + CryptoKeyType::Private, + prepareResult.formatType, + prepareResult.encodingType, + prepareResult.cipher, + WTFMove(prepareResult.passphrase)); + RETURN_IF_EXCEPTION(scope, {}); } - ncrypto::EVPKeyPointer keyPtr = WTFMove(maybeKeyPtr.value()); + + const ncrypto::EVPKeyPointer& keyPtr = keyObject.asymmetricKey(); // Use the signWithKey function to perform the signing operation JSUint8Array* signature = signWithKey(lexicalGlobalObject, thisObject, keyPtr, dsaSigEnc, padding, saltLen); @@ -487,182 +499,6 @@ JSC_DEFINE_HOST_FUNCTION(constructSign, (JSC::JSGlobalObject * globalObject, JSC return JSC::JSValue::encode(JSSign::create(vm, structure)); } -JSC_DEFINE_HOST_FUNCTION(jsSignOneShot, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - ncrypto::ClearErrorOnReturn clearError; - - JSC::VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - auto argCount = callFrame->argumentCount(); - - // Validate algorithm if provided - JSValue algorithmValue = callFrame->argument(0); - std::optional hash = std::nullopt; - const EVP_MD* digest = nullptr; - if (!algorithmValue.isUndefinedOrNull()) { - Bun::V::validateString(scope, globalObject, algorithmValue, "algorithm"_s); - RETURN_IF_EXCEPTION(scope, {}); - - WTF::String algorithmName = algorithmValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - - hash = WebCore::CryptoAlgorithmRegistry::singleton().identifier(algorithmName); - - digest = ncrypto::getDigestByName(algorithmName); - if (!digest) { - return Bun::ERR::CRYPTO_INVALID_DIGEST(scope, globalObject, algorithmName); - } - } - - // Get data argument - JSValue dataValue = callFrame->argument(1); - JSC::JSArrayBufferView* dataView = getArrayBufferOrView(globalObject, scope, dataValue, "data"_s, jsUndefined()); - RETURN_IF_EXCEPTION(scope, {}); - if (!dataView) { - Bun::throwError(globalObject, scope, ErrorCode::ERR_INVALID_ARG_TYPE, "data must be a Buffer, TypedArray, or DataView"_s); - return {}; - } - - // Get key argument - JSValue keyValue = callFrame->argument(2); - - std::optional saltLen = getSaltLength(globalObject, keyValue); - RETURN_IF_EXCEPTION(scope, {}); - - // Get DSA signature encoding format - NodeCryptoKeys::DSASigEnc dsaSigEnc = getDSASigEnc(globalObject, keyValue); - RETURN_IF_EXCEPTION(scope, {}); - - // Prepare the private key - std::optional maybeKeyPtr = preparePrivateKey(globalObject, scope, keyValue, hash); - ASSERT(!!scope.exception() == !maybeKeyPtr.has_value()); - if (!maybeKeyPtr) { - return {}; - } - ncrypto::EVPKeyPointer keyPtr = WTFMove(maybeKeyPtr.value()); - - // Get callback if provided - JSValue callbackValue; - bool hasCallback = false; - if (argCount > 3) { - callbackValue = callFrame->argument(3); - if (!callbackValue.isUndefined()) { - Bun::V::validateFunction(scope, globalObject, callbackValue, "callback"_s); - RETURN_IF_EXCEPTION(scope, {}); - hasCallback = true; - } - } - - // Get RSA padding mode and salt length if applicable - int32_t padding = getPadding(globalObject, keyValue, keyPtr); - RETURN_IF_EXCEPTION(scope, {}); - - // Create data buffer - ncrypto::Buffer dataBuf { - .data = reinterpret_cast(dataView->vector()), - .len = dataView->byteLength() - }; - - // Create a new EVP_MD_CTX for signing - auto mdCtx = ncrypto::EVPMDCtxPointer::New(); - if (!mdCtx) { - Bun::throwError(globalObject, scope, ErrorCode::ERR_CRYPTO_OPERATION_FAILED, "Failed to create message digest context"_s); - return {}; - } - - // Initialize the context for signing with the key and digest - auto ctx = mdCtx.signInit(keyPtr, digest); - if (!ctx.has_value()) { - Bun::throwError(globalObject, scope, ErrorCode::ERR_CRYPTO_OPERATION_FAILED, "Failed to initialize signing context"_s); - return {}; - } - - // Apply RSA options if needed - if (keyPtr.isRsaVariant()) { - if (!ncrypto::EVPKeyCtxPointer::setRsaPadding(ctx.value(), padding, saltLen)) { - Bun::throwError(globalObject, scope, ErrorCode::ERR_CRYPTO_OPERATION_FAILED, "Failed to set RSA padding"_s); - return {}; - } - } - - RefPtr sigBuffer = nullptr; - if (keyPtr.isOneShotVariant()) { - auto data = mdCtx.signOneShot(dataBuf); - if (!data) { - throwCryptoError(globalObject, scope, ERR_get_error(), "Failed to create signature"_s); - return {}; - } - - sigBuffer = JSC::ArrayBuffer::tryCreate(data.size(), 1); - if (!sigBuffer) { - Bun::throwError(globalObject, scope, ErrorCode::ERR_CRYPTO_OPERATION_FAILED, "Failed to allocate signature buffer"_s); - return {}; - } - - memcpy(sigBuffer->data(), data.get(), data.size()); - - } else { - auto signatureData = mdCtx.sign(dataBuf); - if (!signatureData) { - throwCryptoError(globalObject, scope, ERR_get_error(), "Failed to create signature"_s); - return {}; - } - - // Convert to P1363 format if requested for EC keys - if (dsaSigEnc == NodeCryptoKeys::DSASigEnc::P1363 && keyPtr.isSigVariant() && keyPtr.getBytesOfRS().value_or(0) * 2 > 0) { - auto p1363Size = keyPtr.getBytesOfRS().value_or(0) * 2; - sigBuffer = JSC::ArrayBuffer::tryCreate(p1363Size, 1); - if (!sigBuffer) { - Bun::throwError(globalObject, scope, ErrorCode::ERR_CRYPTO_OPERATION_FAILED, "Failed to allocate P1363 buffer"_s); - return {}; - } - - ncrypto::Buffer derSig { - .data = reinterpret_cast(signatureData.get()), - .len = signatureData.size() - }; - - if (!ncrypto::extractP1363(derSig, static_cast(sigBuffer->data()), p1363Size / 2)) { - Bun::throwError(globalObject, scope, ErrorCode::ERR_CRYPTO_OPERATION_FAILED, "Failed to convert signature format"_s); - return {}; - } - } else { - sigBuffer = JSC::ArrayBuffer::tryCreate(signatureData.size(), 1); - if (!sigBuffer) { - Bun::throwError(globalObject, scope, ErrorCode::ERR_CRYPTO_OPERATION_FAILED, "Failed to allocate signature buffer"_s); - return {}; - } - - memcpy(sigBuffer->data(), signatureData.get(), signatureData.size()); - } - } - ASSERT(sigBuffer); - - // Create JSUint8Array from the signature buffer - auto* globalObj = defaultGlobalObject(globalObject); - auto* signature = JSC::JSUint8Array::create(globalObject, globalObj->JSBufferSubclassStructure(), WTFMove(sigBuffer), 0, sigBuffer->byteLength()); - - // If we have a callback, call it with the signature - if (hasCallback) { - JSC::MarkedArgumentBuffer args; - args.append(jsNull()); - args.append(signature); - ASSERT(!args.hasOverflowed()); - - NakedPtr returnedException = nullptr; - JSC::profiledCall(globalObject, JSC::ProfilingReason::API, callbackValue, JSC::getCallData(callbackValue), JSC::jsUndefined(), args, returnedException); - RETURN_IF_EXCEPTION(scope, {}); - if (returnedException) { - scope.throwException(globalObject, returnedException.get()); - } - - return JSValue::encode(jsUndefined()); - } - - // Otherwise, return the signature directly - return JSValue::encode(signature); -} - void setupJSSignClassStructure(LazyClassStructure::Initializer& init) { auto* prototypeStructure = JSSignPrototype::createStructure(init.vm, init.global, init.global->objectPrototype()); diff --git a/src/bun.js/bindings/node/crypto/JSSign.h b/src/bun.js/bindings/node/crypto/JSSign.h index 1ab9e0a272..e067f3404b 100644 --- a/src/bun.js/bindings/node/crypto/JSSign.h +++ b/src/bun.js/bindings/node/crypto/JSSign.h @@ -81,8 +81,6 @@ private: void finishCreation(JSC::VM& vm, JSC::JSObject* prototype); }; -JSC_DECLARE_HOST_FUNCTION(jsSignOneShot); - void setupJSSignClassStructure(JSC::LazyClassStructure::Initializer& init); } // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSVerify.cpp b/src/bun.js/bindings/node/crypto/JSVerify.cpp index 29825a3c96..e4c310fd63 100644 --- a/src/bun.js/bindings/node/crypto/JSVerify.cpp +++ b/src/bun.js/bindings/node/crypto/JSVerify.cpp @@ -9,9 +9,7 @@ #include #include #include "JSBufferEncodingType.h" -#include "KeyObject.h" #include "JSCryptoKey.h" -#include "AsymmetricKeyValue.h" #include "NodeValidator.h" #include "JSBuffer.h" #include "CryptoUtil.h" @@ -24,6 +22,7 @@ #include "CryptoKeyEC.h" #include "CryptoKeyRSA.h" #include "wtf/text/Base64.h" +#include "KeyObject.h" // Forward declarations for functions defined in other files namespace Bun { @@ -306,11 +305,6 @@ JSC_DEFINE_HOST_FUNCTION(jsVerifyProtoFuncUpdate, (JSGlobalObject * globalObject return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "data"_s, "string or an instance of Buffer, TypedArray, or DataView"_s, data); } -std::optional preparePublicOrPrivateKey(JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, JSValue maybeKey); -bool convertP1363ToDER(const ncrypto::Buffer& p1363Sig, - const ncrypto::EVPKeyPointer& pkey, - WTF::Vector& derBuffer); - JSC_DEFINE_HOST_FUNCTION(jsVerifyProtoFuncVerify, (JSGlobalObject * globalObject, CallFrame* callFrame)) { ncrypto::ClearErrorOnReturn clearErrorOnReturn; @@ -322,13 +316,13 @@ JSC_DEFINE_HOST_FUNCTION(jsVerifyProtoFuncVerify, (JSGlobalObject * globalObject JSVerify* thisObject = jsDynamicCast(callFrame->thisValue()); if (UNLIKELY(!thisObject)) { Bun::throwThisTypeError(*globalObject, scope, "Verify"_s, "verify"_s); - return JSValue::encode(jsBoolean(false)); + return JSValue::encode({}); } // Check if the context is initialized if (!thisObject->m_mdCtx) { throwTypeError(globalObject, scope, "Verify.prototype.verify cannot be called before Verify.prototype.init"_s); - return JSValue::encode(jsBoolean(false)); + return JSValue::encode({}); } // This function receives two arguments: options and signature @@ -337,28 +331,39 @@ JSC_DEFINE_HOST_FUNCTION(jsVerifyProtoFuncVerify, (JSGlobalObject * globalObject JSValue sigEncodingValue = callFrame->argument(2); JSC::JSArrayBufferView* signatureBuffer = getArrayBufferOrView(globalObject, scope, signatureValue, "signature"_s, sigEncodingValue); - RETURN_IF_EXCEPTION(scope, JSValue::encode(jsBoolean(false))); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); - // Prepare the public or private key from options - std::optional maybeKeyPtr = preparePublicOrPrivateKey(globalObject, scope, options); - ASSERT(!!scope.exception() == !maybeKeyPtr.has_value()); - if (!maybeKeyPtr) { - return JSValue::encode({}); + auto prepareResult = KeyObject::preparePublicOrPrivateKey(globalObject, scope, options); + RETURN_IF_EXCEPTION(scope, {}); + + KeyObject keyObject; + if (prepareResult.keyData) { + keyObject = KeyObject::create(CryptoKeyType::Public, WTFMove(*prepareResult.keyData)); + } else { + keyObject = KeyObject::getPublicOrPrivateKey( + globalObject, + scope, + prepareResult.keyDataView, + CryptoKeyType::Public, + prepareResult.formatType, + prepareResult.encodingType, + prepareResult.cipher, + WTFMove(prepareResult.passphrase)); + RETURN_IF_EXCEPTION(scope, {}); } - ncrypto::EVPKeyPointer keyPtr = WTFMove(maybeKeyPtr.value()); + + const auto& keyPtr = keyObject.asymmetricKey(); // Get RSA padding mode and salt length if applicable - int32_t padding = getPadding(globalObject, options, keyPtr); - RETURN_IF_EXCEPTION(scope, JSValue::encode(jsBoolean(false))); + int32_t padding = getPadding(globalObject, scope, options, keyPtr); + RETURN_IF_EXCEPTION(scope, {}); - std::optional saltLen = getSaltLength(globalObject, options); - RETURN_IF_EXCEPTION(scope, JSValue::encode(jsBoolean(false))); + std::optional saltLen = getSaltLength(globalObject, scope, options); + RETURN_IF_EXCEPTION(scope, {}); // Get DSA signature encoding format - NodeCryptoKeys::DSASigEnc dsaSigEnc = getDSASigEnc(globalObject, options); - RETURN_IF_EXCEPTION(scope, JSValue::encode(jsBoolean(false))); - - // Get the signature buffer + DSASigEnc dsaSigEnc = getDSASigEnc(globalObject, scope, options); + RETURN_IF_EXCEPTION(scope, {}); // Move mdCtx out of JSVerify object to finalize it ncrypto::EVPMDCtxPointer mdCtx = WTFMove(thisObject->m_mdCtx); @@ -366,35 +371,35 @@ JSC_DEFINE_HOST_FUNCTION(jsVerifyProtoFuncVerify, (JSGlobalObject * globalObject // Validate DSA parameters if (!keyPtr.validateDsaParameters()) { throwTypeError(globalObject, scope, "Invalid DSA parameters"_s); - return JSValue::encode(jsBoolean(false)); + return JSValue::encode({}); } // Get the final digest auto data = mdCtx.digestFinal(mdCtx.getExpectedSize()); if (!data) { throwTypeError(globalObject, scope, "Failed to finalize digest"_s); - return JSValue::encode(jsBoolean(false)); + return JSValue::encode({}); } // Create verification context auto pkctx = keyPtr.newCtx(); if (!pkctx || pkctx.initForVerify() <= 0) { throwCryptoError(globalObject, scope, ERR_peek_error(), "Failed to initialize verification context"_s); - return JSValue::encode(jsBoolean(false)); + return JSValue::encode({}); } // Set RSA padding mode and salt length if applicable if (keyPtr.isRsaVariant()) { if (!ncrypto::EVPKeyCtxPointer::setRsaPadding(pkctx.get(), padding, saltLen)) { throwCryptoError(globalObject, scope, ERR_peek_error(), "Failed to set RSA padding"_s); - return JSValue::encode(jsBoolean(false)); + return JSValue::encode({}); } } // Set signature MD from the digest context if (!pkctx.setSignatureMd(mdCtx)) { throwCryptoError(globalObject, scope, ERR_peek_error(), "Failed to set signature message digest"_s); - return JSValue::encode(jsBoolean(false)); + return JSValue::encode({}); } // Handle P1363 format conversion for EC keys if needed @@ -403,7 +408,7 @@ JSC_DEFINE_HOST_FUNCTION(jsVerifyProtoFuncVerify, (JSGlobalObject * globalObject .len = signatureBuffer->byteLength(), }; - if (dsaSigEnc == NodeCryptoKeys::DSASigEnc::P1363 && keyPtr.isSigVariant()) { + if (dsaSigEnc == DSASigEnc::P1363 && keyPtr.isSigVariant()) { WTF::Vector derBuffer; if (convertP1363ToDER(sigBuf, keyPtr, derBuffer)) { @@ -424,172 +429,6 @@ JSC_DEFINE_HOST_FUNCTION(jsVerifyProtoFuncVerify, (JSGlobalObject * globalObject return JSValue::encode(jsBoolean(result)); } -JSC_DEFINE_HOST_FUNCTION(jsVerifyOneShot, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - ncrypto::ClearErrorOnReturn clearError; - - JSC::VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - auto argCount = callFrame->argumentCount(); - - // Validate algorithm if provided - JSValue algorithmValue = callFrame->argument(0); - const EVP_MD* digest = nullptr; - if (!algorithmValue.isUndefinedOrNull()) { - Bun::V::validateString(scope, globalObject, algorithmValue, "algorithm"_s); - RETURN_IF_EXCEPTION(scope, {}); - - WTF::String algorithmName = algorithmValue.toWTFString(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - - digest = ncrypto::getDigestByName(algorithmName); - if (!digest) { - return Bun::ERR::CRYPTO_INVALID_DIGEST(scope, globalObject, algorithmName); - } - } - - // Get data argument - JSValue dataValue = callFrame->argument(1); - JSC::JSArrayBufferView* dataView = getArrayBufferOrView(globalObject, scope, dataValue, "data"_s, jsUndefined()); - RETURN_IF_EXCEPTION(scope, {}); - if (!dataView) { - return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "data"_s, "Buffer, TypedArray, or DataView"_s, dataValue); - } - - // Get signature argument. Can only be a buffer type, not a string - JSValue signatureValue = callFrame->argument(3); - std::span signature; - if (JSArrayBufferView* view = jsDynamicCast(signatureValue)) { - if (UNLIKELY(view->isDetached())) { - throwVMTypeError(globalObject, scope, "Buffer is detached"_s); - return {}; - } - signature = view->span(); - } else if (JSArrayBuffer* buf = jsDynamicCast(signatureValue)) { - ArrayBuffer* bufImpl = buf->impl(); - if (UNLIKELY(bufImpl->isDetached())) { - throwVMTypeError(globalObject, scope, "Buffer is detached"_s); - return {}; - } - - signature = bufImpl->span(); - } else { - ERR::INVALID_ARG_INSTANCE(scope, globalObject, "signature"_s, "Buffer, TypedArray, or DataView"_s, signatureValue); - } - - // Get key argument - JSValue keyValue = callFrame->argument(2); - - // Prepare the public or private key - std::optional maybeKeyPtr = preparePublicOrPrivateKey(globalObject, scope, keyValue); - ASSERT(!!scope.exception() == !maybeKeyPtr.has_value()); - if (!maybeKeyPtr) { - return {}; - } - ncrypto::EVPKeyPointer keyPtr = WTFMove(maybeKeyPtr.value()); - - // Get callback if provided - JSValue callbackValue; - bool hasCallback = false; - if (argCount > 4) { - callbackValue = callFrame->argument(4); - if (!callbackValue.isUndefined()) { - Bun::V::validateFunction(scope, globalObject, callbackValue, "callback"_s); - RETURN_IF_EXCEPTION(scope, {}); - hasCallback = true; - } - } - - // Get RSA padding mode and salt length if applicable - int32_t padding = getPadding(globalObject, keyValue, keyPtr); - RETURN_IF_EXCEPTION(scope, {}); - - std::optional saltLen = getSaltLength(globalObject, keyValue); - RETURN_IF_EXCEPTION(scope, {}); - - // Get DSA signature encoding format - NodeCryptoKeys::DSASigEnc dsaSigEnc = getDSASigEnc(globalObject, keyValue); - RETURN_IF_EXCEPTION(scope, {}); - - // Create data buffer - ncrypto::Buffer dataBuf { - .data = static_cast(dataView->vector()), - .len = dataView->byteLength() - }; - - // Create signature buffer - ncrypto::Buffer sigBuf { - .data = signature.data(), - .len = signature.size(), - }; - - // Create a new EVP_MD_CTX for verification - auto mdCtx = ncrypto::EVPMDCtxPointer::New(); - if (!mdCtx) { - Bun::throwError(globalObject, scope, ErrorCode::ERR_CRYPTO_OPERATION_FAILED, "Failed to create message digest context"_s); - return {}; - } - - // Initialize the context for verification with the key and digest - auto ctx = mdCtx.verifyInit(keyPtr, digest); - if (!ctx.has_value()) { - Bun::throwError(globalObject, scope, ErrorCode::ERR_CRYPTO_OPERATION_FAILED, "Failed to initialize verification context"_s); - return {}; - } - - // Apply RSA options if needed - if (keyPtr.isRsaVariant()) { - if (!ncrypto::EVPKeyCtxPointer::setRsaPadding(ctx.value(), padding, saltLen)) { - Bun::throwError(globalObject, scope, ErrorCode::ERR_CRYPTO_OPERATION_FAILED, "Failed to set RSA padding"_s); - return {}; - } - } - - // Handle P1363 format conversion if needed - bool result = false; - if (dsaSigEnc == NodeCryptoKeys::DSASigEnc::P1363 && keyPtr.isSigVariant()) { - WTF::Vector derBuffer; - - if (convertP1363ToDER(sigBuf, keyPtr, derBuffer)) { - // Conversion succeeded, create a new buffer with the converted signature - ncrypto::Buffer derSigBuf { - .data = derBuffer.data(), - .len = derBuffer.size(), - }; - - // Perform verification with the converted signature - result = mdCtx.verify(dataBuf, derSigBuf); - } else { - // If conversion failed, try with the original signature - result = mdCtx.verify(dataBuf, sigBuf); - } - } else { - // Perform verification with the original signature - result = mdCtx.verify(dataBuf, sigBuf); - } - - // If we have a callback, call it with the result - if (hasCallback) { - JSC::MarkedArgumentBuffer args; - args.append(jsNull()); - args.append(jsBoolean(result)); - ASSERT(!args.hasOverflowed()); - - NakedPtr returnedException = nullptr; - JSC::CallData callData = JSC::getCallData(callbackValue); - JSC::profiledCall(globalObject, JSC::ProfilingReason::API, callbackValue, callData, JSC::jsUndefined(), args, returnedException); - RETURN_IF_EXCEPTION(scope, {}); - if (returnedException) { - scope.throwException(globalObject, returnedException.get()); - } - - return JSValue::encode(jsUndefined()); - } - - // Otherwise, return the result directly - return JSValue::encode(jsBoolean(result)); -} - JSC_DEFINE_HOST_FUNCTION(callVerify, (JSGlobalObject * globalObject, CallFrame* callFrame)) { VM& vm = globalObject->vm(); @@ -654,773 +493,4 @@ std::optional keyFromPublicString(JSGlobalObject* lexica return std::nullopt; } -std::optional preparePublicOrPrivateKey(JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, JSValue maybeKey) -{ - VM& vm = lexicalGlobalObject->vm(); - - bool optionsBool = maybeKey.toBoolean(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - // Check if the key is provided - if (!optionsBool) { - Bun::ERR::CRYPTO_SIGN_KEY_REQUIRED(scope, lexicalGlobalObject); - return std::nullopt; - } - - if (!maybeKey.isCell()) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key"_s, "ArrayBuffer, Buffer, TypedArray, DataView, string, KeyObject, or CryptoKey"_s, maybeKey); - return std::nullopt; - } - - auto optionsCell = maybeKey.asCell(); - auto optionsType = optionsCell->type(); - - // Handle CryptoKey directly - if (optionsCell->inherits()) { - auto* cryptoKey = jsCast(optionsCell); - - // Convert it to a key object, then to EVPKeyPointer - auto& key = cryptoKey->wrapped(); - AsymmetricKeyValue keyValue(key); - if (keyValue.key) { - EVP_PKEY_up_ref(keyValue.key); - ncrypto::EVPKeyPointer keyPtr(keyValue.key); - return keyPtr; - } - - throwCryptoOperationFailed(lexicalGlobalObject, scope); - return std::nullopt; - } else if (maybeKey.isObject()) { - JSObject* optionsObj = optionsCell->getObject(); - const auto& names = WebCore::builtinNames(vm); - - // Check for native pointer (CryptoKey) - if (auto val = optionsObj->getIfPropertyExists(lexicalGlobalObject, names.bunNativePtrPrivateName())) { - if (val.isCell() && val.inherits()) { - auto* cryptoKey = jsCast(val.asCell()); - - auto& key = cryptoKey->wrapped(); - AsymmetricKeyValue keyValue(key); - if (keyValue.key) { - EVP_PKEY_up_ref(keyValue.key); - ncrypto::EVPKeyPointer keyPtr(keyValue.key); - return keyPtr; - } - throwCryptoOperationFailed(lexicalGlobalObject, scope); - return std::nullopt; - } - } else if (optionsType >= Int8ArrayType && optionsType <= DataViewType) { - // Handle buffer input directly - auto dataBuf = KeyObject__GetBuffer(maybeKey); - if (dataBuf.hasException()) { - return std::nullopt; - } - - auto buffer = dataBuf.releaseReturnValue(); - ncrypto::Buffer ncryptoBuf { - .data = buffer.data(), - .len = buffer.size(), - }; - - // Try as public key first with default PEM format - ncrypto::EVPKeyPointer::PublicKeyEncodingConfig pubConfig; - pubConfig.format = ncrypto::EVPKeyPointer::PKFormatType::PEM; - - auto pubRes = ncrypto::EVPKeyPointer::TryParsePublicKey(pubConfig, ncryptoBuf); - if (pubRes) { - ncrypto::EVPKeyPointer keyPtr(WTFMove(pubRes.value)); - return keyPtr; - } - - // If public key parsing fails, try as a private key - ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig privConfig; - privConfig.format = ncrypto::EVPKeyPointer::PKFormatType::PEM; - - auto privRes = ncrypto::EVPKeyPointer::TryParsePrivateKey(privConfig, ncryptoBuf); - if (privRes) { - ncrypto::EVPKeyPointer keyPtr(WTFMove(privRes.value)); - return keyPtr; - } - - if (privRes.error.value() == ncrypto::EVPKeyPointer::PKParseError::NEED_PASSPHRASE) { - Bun::ERR::MISSING_PASSPHRASE(scope, lexicalGlobalObject, "Passphrase required for encrypted key"_s); - return std::nullopt; - } - - throwCryptoError(lexicalGlobalObject, scope, privRes.openssl_error.value_or(0), "Failed to read key"_s); - return std::nullopt; - } - - // Handle options object with key property - JSValue key = optionsObj->get(lexicalGlobalObject, Identifier::fromString(vm, "key"_s)); - RETURN_IF_EXCEPTION(scope, {}); - JSValue formatValue = optionsObj->get(lexicalGlobalObject, Identifier::fromString(vm, "format"_s)); - RETURN_IF_EXCEPTION(scope, {}); - JSValue typeValue = optionsObj->get(lexicalGlobalObject, Identifier::fromString(vm, "type"_s)); - RETURN_IF_EXCEPTION(scope, {}); - JSValue passphrase = optionsObj->get(lexicalGlobalObject, Identifier::fromString(vm, "passphrase"_s)); - RETURN_IF_EXCEPTION(scope, {}); - - WTF::StringView formatStr = WTF::nullStringView(); - if (formatValue.isString()) { - auto str = formatValue.toString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, {}); - formatStr = str->view(lexicalGlobalObject); - } - - if (!key.isCell()) { - if (formatStr == "jwk"_s) { - // Use our implementation of JWK key handling - bool isPublic = true; - return getKeyObjectHandleFromJwk(lexicalGlobalObject, scope, key, isPublic); - } else { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.key"_s, "ArrayBuffer, Buffer, TypedArray, DataView, string, KeyObject, or CryptoKey"_s, key); - } - return std::nullopt; - } - - auto keyCell = key.asCell(); - auto keyCellType = keyCell->type(); - - // Handle CryptoKey in key property - if (keyCell->inherits()) { - auto* cryptoKey = jsCast(keyCell); - auto& key = cryptoKey->wrapped(); - AsymmetricKeyValue keyValue(key); - if (keyValue.key) { - EVP_PKEY_up_ref(keyValue.key); - ncrypto::EVPKeyPointer keyPtr(keyValue.key); - return keyPtr; - } - throwCryptoOperationFailed(lexicalGlobalObject, scope); - return std::nullopt; - } else if (key.isObject()) { - JSObject* keyObj = key.getObject(); - if (auto keyVal = keyObj->getIfPropertyExists(lexicalGlobalObject, names.bunNativePtrPrivateName())) { - if (keyVal.isCell() && keyVal.inherits()) { - auto* cryptoKey = jsCast(keyVal.asCell()); - - auto& key = cryptoKey->wrapped(); - AsymmetricKeyValue keyValue(key); - if (keyValue.key) { - EVP_PKEY_up_ref(keyValue.key); - ncrypto::EVPKeyPointer keyPtr(WTFMove(keyValue.key)); - return keyPtr; - } - throwCryptoOperationFailed(lexicalGlobalObject, scope); - return std::nullopt; - } - } else if (keyCellType >= Int8ArrayType && keyCellType <= DataViewType) { - // Handle buffer in key property - auto dataBuf = KeyObject__GetBuffer(key); - if (dataBuf.hasException()) { - return std::nullopt; - } - - auto buffer = dataBuf.releaseReturnValue(); - ncrypto::Buffer ncryptoBuf { - .data = buffer.data(), - .len = buffer.size(), - }; - - // Parse format and type from options - auto format = parseKeyFormat(lexicalGlobalObject, formatValue, "options.format"_s, ncrypto::EVPKeyPointer::PKFormatType::PEM); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - // If format is JWK, use our JWK implementation - if (format == ncrypto::EVPKeyPointer::PKFormatType::JWK) { - bool isPublic = true; - return getKeyObjectHandleFromJwk(lexicalGlobalObject, scope, key, isPublic); - } - - // Try as public key first - ncrypto::EVPKeyPointer::PublicKeyEncodingConfig pubConfig; - pubConfig.format = format; - - // Parse type for public key - auto pubType = parseKeyType(lexicalGlobalObject, typeValue, format == ncrypto::EVPKeyPointer::PKFormatType::DER, WTF::nullStringView(), std::nullopt, "options.type"_s); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (pubType.has_value()) { - pubConfig.type = pubType.value(); - } - - auto pubRes = ncrypto::EVPKeyPointer::TryParsePublicKey(pubConfig, ncryptoBuf); - if (pubRes) { - ncrypto::EVPKeyPointer keyPtr(WTFMove(pubRes.value)); - return keyPtr; - } - - // If public key parsing fails, try as a private key - ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig privConfig; - privConfig.format = format; - - // Parse type for private key - auto privType = parseKeyType(lexicalGlobalObject, typeValue, format == ncrypto::EVPKeyPointer::PKFormatType::DER, WTF::nullStringView(), false, "options.type"_s); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (privType.has_value()) { - privConfig.type = privType.value(); - } - - privConfig.passphrase = passphraseFromBufferSource(lexicalGlobalObject, scope, passphrase); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - auto privRes = ncrypto::EVPKeyPointer::TryParsePrivateKey(privConfig, ncryptoBuf); - if (privRes) { - ncrypto::EVPKeyPointer keyPtr(WTFMove(privRes.value)); - return keyPtr; - } - - if (privRes.error.value() == ncrypto::EVPKeyPointer::PKParseError::NEED_PASSPHRASE) { - Bun::ERR::MISSING_PASSPHRASE(scope, lexicalGlobalObject, "Passphrase required for encrypted key"_s); - return std::nullopt; - } - - throwCryptoError(lexicalGlobalObject, scope, privRes.openssl_error.value_or(0), "Failed to read key"_s); - return std::nullopt; - } else if (formatStr == "jwk"_s) { - // Use our implementation of JWK key handling - bool isPublic = true; - return getKeyObjectHandleFromJwk(lexicalGlobalObject, scope, key, isPublic); - } - } else if (key.isString()) { - // Handle string key - WTF::String keyStr = key.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - // Parse format and type from options - auto format = parseKeyFormat(lexicalGlobalObject, formatValue, "options.format"_s, ncrypto::EVPKeyPointer::PKFormatType::PEM); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - // If format is JWK, use our JWK implementation - if (format == ncrypto::EVPKeyPointer::PKFormatType::JWK) { - bool isPublic = true; - return getKeyObjectHandleFromJwk(lexicalGlobalObject, scope, key, isPublic); - } - - // Try as public key first with specified format and type - UTF8View keyUtf8(keyStr); - auto keySpan = keyUtf8.span(); - - ncrypto::Buffer ncryptoBuf { - .data = reinterpret_cast(keySpan.data()), - .len = keySpan.size(), - }; - - // Try as public key first - ncrypto::EVPKeyPointer::PublicKeyEncodingConfig pubConfig; - pubConfig.format = format; - - // Parse type for public key - auto pubType = parseKeyType(lexicalGlobalObject, typeValue, format == ncrypto::EVPKeyPointer::PKFormatType::DER, WTF::nullStringView(), std::nullopt, "options.type"_s); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (pubType.has_value()) { - pubConfig.type = pubType.value(); - } - - auto pubRes = ncrypto::EVPKeyPointer::TryParsePublicKey(pubConfig, ncryptoBuf); - if (pubRes) { - ncrypto::EVPKeyPointer keyPtr(WTFMove(pubRes.value)); - return keyPtr; - } - - // If public key parsing fails, try as a private key - ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig privConfig; - privConfig.format = format; - - // Parse type for private key - auto privType = parseKeyType(lexicalGlobalObject, typeValue, format == ncrypto::EVPKeyPointer::PKFormatType::DER, WTF::nullStringView(), false, "options.type"_s); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (privType.has_value()) { - privConfig.type = privType.value(); - } - - privConfig.passphrase = passphraseFromBufferSource(lexicalGlobalObject, scope, passphrase); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - auto privRes = ncrypto::EVPKeyPointer::TryParsePrivateKey(privConfig, ncryptoBuf); - if (privRes) { - ncrypto::EVPKeyPointer keyPtr(WTFMove(privRes.value)); - return keyPtr; - } - - if (privRes.error.value() == ncrypto::EVPKeyPointer::PKParseError::NEED_PASSPHRASE) { - Bun::ERR::MISSING_PASSPHRASE(scope, lexicalGlobalObject, "Passphrase required for encrypted key"_s); - return std::nullopt; - } - - throwCryptoError(lexicalGlobalObject, scope, privRes.openssl_error.value_or(0), "Failed to read key"_s); - return std::nullopt; - } - - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.key"_s, "ArrayBuffer, Buffer, TypedArray, DataView, string, KeyObject, or CryptoKey"_s, key); - return std::nullopt; - } else if (maybeKey.isString()) { - // Handle string key directly - WTF::String keyStr = maybeKey.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - // Try as public key first with default PEM format - UTF8View keyUtf8(keyStr); - auto keySpan = keyUtf8.span(); - - ncrypto::Buffer ncryptoBuf { - .data = reinterpret_cast(keySpan.data()), - .len = keySpan.size(), - }; - - // Try as public key first - ncrypto::EVPKeyPointer::PublicKeyEncodingConfig pubConfig; - pubConfig.format = ncrypto::EVPKeyPointer::PKFormatType::PEM; - - auto pubRes = ncrypto::EVPKeyPointer::TryParsePublicKey(pubConfig, ncryptoBuf); - if (pubRes) { - ncrypto::EVPKeyPointer keyPtr(WTFMove(pubRes.value)); - return keyPtr; - } - - // If public key parsing fails, try as a private key - ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig privConfig; - privConfig.format = ncrypto::EVPKeyPointer::PKFormatType::PEM; - - auto privRes = ncrypto::EVPKeyPointer::TryParsePrivateKey(privConfig, ncryptoBuf); - if (privRes) { - ncrypto::EVPKeyPointer keyPtr(WTFMove(privRes.value)); - return keyPtr; - } - - if (privRes.error.value() == ncrypto::EVPKeyPointer::PKParseError::NEED_PASSPHRASE) { - Bun::ERR::MISSING_PASSPHRASE(scope, lexicalGlobalObject, "Passphrase required for encrypted key"_s); - return std::nullopt; - } - - throwCryptoError(lexicalGlobalObject, scope, privRes.openssl_error.value_or(0), "Failed to read key"_s); - return std::nullopt; - } - - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key"_s, "ArrayBuffer, Buffer, TypedArray, DataView, string, KeyObject, or CryptoKey"_s, maybeKey); - return std::nullopt; -} - -// Implements the getKeyObjectHandleFromJwk function similar to Node.js implementation -std::optional getKeyObjectHandleFromJwk(JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, JSValue key, bool isPublic) -{ - auto& vm = lexicalGlobalObject->vm(); - - // Validate that key is an object - Bun::V::validateObject(scope, lexicalGlobalObject, key, "key.key"_s); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - JSObject* keyObj = key.getObject(); - - // Get and validate key.kty - JSValue ktyValue = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "kty"_s)); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (!ktyValue.isString()) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.kty"_s, "string"_s, ktyValue); - return std::nullopt; - } - - WTF::String kty = ktyValue.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - // Validate kty is one of the supported types - const WTF::Vector validKeyTypes = { "RSA"_s, "EC"_s, "OKP"_s }; - bool isValidType = false; - for (const auto& validType : validKeyTypes) { - if (kty == validType) { - isValidType = true; - break; - } - } - - if (!isValidType) { - Bun::ERR::INVALID_ARG_VALUE(scope, lexicalGlobalObject, "key.kty"_s, "must be one of: "_s, ktyValue, validKeyTypes); - return std::nullopt; - } - - // Handle OKP keys - if (kty == "OKP"_s) { - // Get and validate key.crv - JSValue crvValue = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "crv"_s)); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (!crvValue.isString()) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.crv"_s, "string"_s, crvValue); - return std::nullopt; - } - - WTF::String crv = crvValue.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - // Validate crv is one of the supported curves - const WTF::Vector validCurves = { "Ed25519"_s, "Ed448"_s, "X25519"_s, "X448"_s }; - bool validCurve = false; - for (const auto& validCurveType : validCurves) { - if (crv == validCurveType) { - validCurve = true; - break; - } - } - - if (!validCurve) { - Bun::ERR::INVALID_ARG_VALUE(scope, lexicalGlobalObject, "key.crv"_s, "must be one of: "_s, crvValue, validCurves); - return std::nullopt; - } - - // Get and validate key.x - JSValue xValue = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "x"_s)); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (!xValue.isString()) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.x"_s, "string"_s, xValue); - return std::nullopt; - } - - WTF::String xStr = xValue.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - // For private keys, validate key.d - WTF::String dStr; - if (!isPublic) { - JSValue dValue = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "d"_s)); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (!dValue.isString()) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.d"_s, "string"_s, dValue); - return std::nullopt; - } - - dStr = dValue.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - } - - // Convert base64 strings to binary data - Vector keyData; - if (isPublic) { - auto xData = WTF::base64Decode(xStr); - // auto xData = WTF::base64Decode(xStr); - if (!xData) { - Bun::ERR::CRYPTO_INVALID_JWK(scope, lexicalGlobalObject); - return std::nullopt; - } - keyData = WTFMove(*xData); - } else { - auto dData = WTF::base64Decode(dStr); - if (!dData) { - Bun::ERR::CRYPTO_INVALID_JWK(scope, lexicalGlobalObject); - return std::nullopt; - } - keyData = WTFMove(*dData); - } - - // Validate key length based on curve - if ((crv == "Ed25519"_s || crv == "X25519"_s) && keyData.size() != 32) { - Bun::ERR::CRYPTO_INVALID_JWK(scope, lexicalGlobalObject); - return std::nullopt; - } else if (crv == "Ed448"_s && keyData.size() != 57) { - Bun::ERR::CRYPTO_INVALID_JWK(scope, lexicalGlobalObject); - return std::nullopt; - } else if (crv == "X448"_s && keyData.size() != 56) { - Bun::ERR::CRYPTO_INVALID_JWK(scope, lexicalGlobalObject); - return std::nullopt; - } - - // Create the key - int nid = 0; - if (crv == "Ed25519"_s) { - nid = EVP_PKEY_ED25519; - } else if (crv == "Ed448"_s) { - nid = EVP_PKEY_ED448; - } else if (crv == "X25519"_s) { - nid = EVP_PKEY_X25519; - } else if (crv == "X448"_s) { - nid = EVP_PKEY_X448; - } - - ncrypto::Buffer buffer { - .data = keyData.data(), - .len = keyData.size(), - }; - - if (isPublic) { - return ncrypto::EVPKeyPointer::NewRawPublic(nid, buffer); - } else { - return ncrypto::EVPKeyPointer::NewRawPrivate(nid, buffer); - } - } - // Handle EC keys - else if (kty == "EC"_s) { - // Get and validate key.crv - JSValue crvValue = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "crv"_s)); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (!crvValue.isString()) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.crv"_s, "string"_s, crvValue); - return std::nullopt; - } - - WTF::String crv = crvValue.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - // Validate crv is one of the supported curves - const WTF::Vector validCurves = { "P-256"_s, "secp256k1"_s, "P-384"_s, "P-521"_s }; - bool validCurve = false; - for (const auto& validCurveType : validCurves) { - if (crv == validCurveType) { - validCurve = true; - break; - } - } - - if (!validCurve) { - Bun::ERR::INVALID_ARG_VALUE(scope, lexicalGlobalObject, "key.crv"_s, "must be one of:"_s, crvValue, validCurves); - return std::nullopt; - } - - // Get and validate key.x and key.y - JSValue xValue = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "x"_s)); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (!xValue.isString()) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.x"_s, "string"_s, xValue); - return std::nullopt; - } - - JSValue yValue = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "y"_s)); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (!yValue.isString()) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.y"_s, "string"_s, yValue); - return std::nullopt; - } - - // For private keys, validate key.d - if (!isPublic) { - JSValue dValue = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "d"_s)); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (!dValue.isString()) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.d"_s, "string"_s, dValue); - return std::nullopt; - } - } - - // Convert to WebCrypto JsonWebKey and use existing implementation - auto jwk = WebCore::JsonWebKey(); - jwk.kty = kty; - jwk.crv = crv; - jwk.x = xValue.toWTFString(lexicalGlobalObject); - jwk.y = yValue.toWTFString(lexicalGlobalObject); - - if (!isPublic) { - jwk.d = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "d"_s)).toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - } - - // Use the WebCrypto implementation to import the key - RefPtr result; - if (isPublic) { - result = WebCore::CryptoKeyEC::importJwk(WebCore::CryptoAlgorithmIdentifier::ECDSA, crv, WTFMove(jwk), true, WebCore::CryptoKeyUsageVerify); - } else { - result = WebCore::CryptoKeyEC::importJwk(WebCore::CryptoAlgorithmIdentifier::ECDSA, crv, WTFMove(jwk), true, WebCore::CryptoKeyUsageSign); - } - - if (!result) { - Bun::ERR::CRYPTO_INVALID_JWK(scope, lexicalGlobalObject); - return std::nullopt; - } - - // Convert CryptoKeyEC to EVPKeyPointer - AsymmetricKeyValue keyValue(*result); - if (!keyValue.key) { - Bun::ERR::CRYPTO_INVALID_JWK(scope, lexicalGlobalObject); - return std::nullopt; - } - - EVP_PKEY_up_ref(keyValue.key); - return ncrypto::EVPKeyPointer(keyValue.key); - } - // Handle RSA keys - else if (kty == "RSA"_s) { - // Get and validate key.n and key.e - JSValue nValue = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "n"_s)); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (!nValue.isString()) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.n"_s, "string"_s, nValue); - return std::nullopt; - } - - JSValue eValue = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "e"_s)); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (!eValue.isString()) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.e"_s, "string"_s, eValue); - return std::nullopt; - } - - // For private keys, validate additional parameters - if (!isPublic) { - JSValue dValue = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "d"_s)); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (!dValue.isString()) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.d"_s, "string"_s, dValue); - return std::nullopt; - } - - JSValue pValue = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "p"_s)); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (!pValue.isString()) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.p"_s, "string"_s, pValue); - return std::nullopt; - } - - JSValue qValue = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "q"_s)); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (!qValue.isString()) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.q"_s, "string"_s, qValue); - return std::nullopt; - } - - JSValue dpValue = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "dp"_s)); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (!dpValue.isString()) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.dp"_s, "string"_s, dpValue); - return std::nullopt; - } - - JSValue dqValue = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "dq"_s)); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (!dqValue.isString()) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.dq"_s, "string"_s, dqValue); - return std::nullopt; - } - - JSValue qiValue = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "qi"_s)); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (!qiValue.isString()) { - Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "key.qi"_s, "string"_s, qiValue); - return std::nullopt; - } - } - - // Convert to WebCrypto JsonWebKey and use existing implementation - auto jwk = WebCore::JsonWebKey(); - jwk.kty = kty; - jwk.n = nValue.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - jwk.e = eValue.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - - if (!isPublic) { - jwk.d = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "d"_s)).toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - jwk.p = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "p"_s)).toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - jwk.q = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "q"_s)).toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - jwk.dp = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "dp"_s)).toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - jwk.dq = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "dq"_s)).toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - jwk.qi = keyObj->get(lexicalGlobalObject, Identifier::fromString(vm, "qi"_s)).toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, std::nullopt); - } - - // Use the WebCrypto implementation to import the key - RefPtr result; - if (isPublic) { - result = WebCore::CryptoKeyRSA::importJwk(WebCore::CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5, std::nullopt, WTFMove(jwk), true, WebCore::CryptoKeyUsageVerify); - } else { - result = WebCore::CryptoKeyRSA::importJwk(WebCore::CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5, std::nullopt, WTFMove(jwk), true, WebCore::CryptoKeyUsageSign); - } - - if (!result) { - Bun::ERR::CRYPTO_INVALID_JWK(scope, lexicalGlobalObject); - return std::nullopt; - } - - // Convert CryptoKeyRSA to EVPKeyPointer - AsymmetricKeyValue keyValue(*result); - if (!keyValue.key) { - Bun::ERR::CRYPTO_INVALID_JWK(scope, lexicalGlobalObject); - return std::nullopt; - } - - EVP_PKEY_up_ref(keyValue.key); - return ncrypto::EVPKeyPointer(keyValue.key); - } - - // Should never reach here due to earlier validation - Bun::ERR::CRYPTO_INVALID_JWK(scope, lexicalGlobalObject); - return std::nullopt; -} - -bool convertP1363ToDER(const ncrypto::Buffer& p1363Sig, - const ncrypto::EVPKeyPointer& pkey, - WTF::Vector& derBuffer) -{ - // Get the size of r and s components from the key - auto bytesOfRS = pkey.getBytesOfRS(); - if (!bytesOfRS) { - // If we can't get the bytes of RS, this is not a signature variant - // that we can convert. Return false to indicate that the original - // signature should be used. - return false; - } - - size_t bytesOfRSValue = bytesOfRS.value(); - - // Check if the signature size is valid (should be 2 * bytesOfRS) - if (p1363Sig.len != 2 * bytesOfRSValue) { - // If the signature size doesn't match what we expect, return false - // to indicate that the original signature should be used. - return false; - } - - // Create BignumPointers for r and s components - ncrypto::BignumPointer r(p1363Sig.data, bytesOfRSValue); - if (!r) { - return false; - } - - ncrypto::BignumPointer s(p1363Sig.data + bytesOfRSValue, bytesOfRSValue); - if (!s) { - return false; - } - - // Create a new ECDSA_SIG structure and set r and s components - auto asn1_sig = ncrypto::ECDSASigPointer::New(); - if (!asn1_sig) { - return false; - } - - if (!asn1_sig.setParams(std::move(r), std::move(s))) { - return false; - } - - // Encode the signature in DER format - auto buf = asn1_sig.encode(); - if (buf.len < 0) { - return false; - } - - if (!derBuffer.tryAppend(std::span { buf.data, buf.len })) { - return false; - } - - return true; -} - } // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/JSVerify.h b/src/bun.js/bindings/node/crypto/JSVerify.h index 925f67e1ad..a7b85267cd 100644 --- a/src/bun.js/bindings/node/crypto/JSVerify.h +++ b/src/bun.js/bindings/node/crypto/JSVerify.h @@ -80,8 +80,6 @@ private: void finishCreation(JSC::VM& vm, JSC::JSObject* prototype); }; -JSC_DECLARE_HOST_FUNCTION(jsVerifyOneShot); - void setupJSVerifyClassStructure(JSC::LazyClassStructure::Initializer& init); } // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/KeyObject.cpp b/src/bun.js/bindings/node/crypto/KeyObject.cpp new file mode 100644 index 0000000000..a8e31fa349 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/KeyObject.cpp @@ -0,0 +1,1488 @@ +#include "KeyObject.h" +#include "JSPublicKeyObject.h" +#include "JSPrivateKeyObject.h" +#include "helpers.h" +#include "ZigGlobalObject.h" +#include "CryptoUtil.h" +#include "ErrorCode.h" +#include "NodeValidator.h" +#include "AsymmetricKeyValue.h" +#include "CryptoKeyAES.h" +#include "CryptoKeyHMAC.h" +#include "CryptoKeyRaw.h" +#include "CryptoKey.h" +#include "CryptoKeyType.h" +#include "JSCryptoKey.h" +#include "CryptoGenKeyPair.h" +#include "JSBuffer.h" +#include "BunString.h" + +namespace Bun { + +using namespace Bun; +using namespace JSC; +using namespace ncrypto; +using namespace WebCore; + +JSValue encodeBignum(JSGlobalObject* globalObject, ThrowScope& scope, const BIGNUM* bn, int size) +{ + auto buf = ncrypto::BignumPointer::EncodePadded(bn, size); + + JSValue encoded = JSValue::decode(StringBytes::encode(globalObject, scope, buf.span(), BufferEncodingType::base64url)); + RETURN_IF_EXCEPTION(scope, {}); + + return encoded; +} + +void setEncodedValue(JSGlobalObject* globalObject, ThrowScope& scope, JSObject* obj, JSString* name, const BIGNUM* bn, int size = 0) +{ + if (size == 0) { + size = ncrypto::BignumPointer::GetByteCount(bn); + } + + VM& vm = globalObject->vm(); + JSValue encodedBn = encodeBignum(globalObject, scope, bn, size); + RETURN_IF_EXCEPTION(scope, ); + + obj->putDirect(vm, Identifier::fromString(vm, name->value(globalObject)), encodedBn); +} + +JSC::JSValue KeyObject::exportJwkEdKey(JSC::JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, CryptoKeyType exportType) +{ + VM& vm = lexicalGlobalObject->vm(); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + auto& commonStrings = globalObject->commonStrings(); + + const auto& pkey = m_data->asymmetricKey; + + JSObject* jwk = JSC::constructEmptyObject(lexicalGlobalObject); + + ASCIILiteral curve = ([&] { + switch (pkey.id()) { + case EVP_PKEY_ED25519: + return "Ed25519"_s; + case EVP_PKEY_ED448: + return "Ed448"_s; + case EVP_PKEY_X25519: + return "X25519"_s; + case EVP_PKEY_X448: + return "X448"_s; + default: + UNREACHABLE(); + } + })(); + + jwk->putDirect( + vm, + Identifier::fromString(vm, commonStrings.jwkCrvString(lexicalGlobalObject)->value(lexicalGlobalObject)), + jsString(vm, makeString(curve))); + + if (exportType == CryptoKeyType::Private) { + ncrypto::DataPointer privateData = pkey.rawPrivateKey(); + + JSValue encoded = JSValue::decode(StringBytes::encode(lexicalGlobalObject, scope, privateData.span(), BufferEncodingType::base64url)); + RETURN_IF_EXCEPTION(scope, {}); + jwk->putDirect( + vm, + Identifier::fromString(vm, commonStrings.jwkDString(lexicalGlobalObject)->value(lexicalGlobalObject)), + encoded); + } + + ncrypto::DataPointer publicData = pkey.rawPublicKey(); + JSValue encoded = JSValue::decode(StringBytes::encode(lexicalGlobalObject, scope, publicData.span(), BufferEncodingType::base64url)); + RETURN_IF_EXCEPTION(scope, {}); + jwk->putDirect( + vm, + Identifier::fromString(vm, commonStrings.jwkXString(lexicalGlobalObject)->value(lexicalGlobalObject)), + encoded); + + jwk->putDirect( + vm, + Identifier::fromString(vm, commonStrings.jwkKtyString(lexicalGlobalObject)->value(lexicalGlobalObject)), + commonStrings.jwkOkpString(lexicalGlobalObject)); + + return jwk; +} + +JSC::JSValue KeyObject::exportJwkEcKey(JSC::JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, CryptoKeyType exportType) +{ + VM& vm = lexicalGlobalObject->vm(); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + auto& commonStrings = globalObject->commonStrings(); + + const auto& pkey = m_data->asymmetricKey; + ASSERT(pkey.id() == EVP_PKEY_EC); + + const EC_KEY* ec = pkey; + ASSERT(ec); + + const auto pub = ncrypto::ECKeyPointer::GetPublicKey(ec); + const auto group = ncrypto::ECKeyPointer::GetGroup(ec); + + int degree_bits = EC_GROUP_get_degree(group); + int degree_bytes = (degree_bits / CHAR_BIT) + (7 + (degree_bits % CHAR_BIT)) / 8; + + auto x = ncrypto::BignumPointer::New(); + auto y = ncrypto::BignumPointer::New(); + + if (!EC_POINT_get_affine_coordinates(group, pub, x.get(), y.get(), nullptr)) { + throwCryptoError(lexicalGlobalObject, scope, ERR_get_error(), + "Failed to get elliptic-curve point coordinates"); + return {}; + } + + JSObject* jwk = JSC::constructEmptyObject(lexicalGlobalObject); + + jwk->putDirect( + vm, + Identifier::fromString(vm, commonStrings.jwkKtyString(lexicalGlobalObject)->value(lexicalGlobalObject)), + commonStrings.jwkEcString(lexicalGlobalObject)); + + setEncodedValue(lexicalGlobalObject, scope, jwk, commonStrings.jwkXString(lexicalGlobalObject), x.get(), degree_bytes); + RETURN_IF_EXCEPTION(scope, {}); + setEncodedValue(lexicalGlobalObject, scope, jwk, commonStrings.jwkYString(lexicalGlobalObject), y.get(), degree_bytes); + RETURN_IF_EXCEPTION(scope, {}); + + WTF::ASCIILiteral crvName; + const int nid = EC_GROUP_get_curve_name(group); + switch (nid) { + case NID_X9_62_prime256v1: + crvName = "P-256"_s; + break; + case NID_secp256k1: + crvName = "secp256k1"_s; + break; + case NID_secp384r1: + crvName = "P-384"_s; + break; + case NID_secp521r1: + crvName = "P-521"_s; + break; + default: { + ERR::CRYPTO_JWK_UNSUPPORTED_CURVE(scope, lexicalGlobalObject, "Unsupported JWK EC curve: ", OBJ_nid2sn(nid)); + return {}; + } + } + + jwk->putDirect( + vm, + Identifier::fromString(vm, commonStrings.jwkCrvString(lexicalGlobalObject)->value(lexicalGlobalObject)), + jsString(vm, makeString(crvName))); + + if (exportType == CryptoKeyType::Private) { + auto pvt = ncrypto::ECKeyPointer::GetPrivateKey(ec); + setEncodedValue(lexicalGlobalObject, scope, jwk, commonStrings.jwkDString(lexicalGlobalObject), pvt, degree_bytes); + RETURN_IF_EXCEPTION(scope, {}); + } + + return jwk; +} + +JSC::JSValue KeyObject::exportJwkRsaKey(JSC::JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, CryptoKeyType exportType) +{ + VM& vm = lexicalGlobalObject->vm(); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + auto& commonStrings = globalObject->commonStrings(); + + JSObject* jwk = JSC::constructEmptyObject(lexicalGlobalObject); + + const auto& pkey = m_data->asymmetricKey; + const ncrypto::Rsa rsa = pkey; + + auto publicKey = rsa.getPublicKey(); + + jwk->putDirect(vm, + Identifier::fromString(vm, commonStrings.jwkKtyString(lexicalGlobalObject)->value(lexicalGlobalObject)), + commonStrings.jwkRsaString(lexicalGlobalObject)); + + setEncodedValue(lexicalGlobalObject, scope, jwk, commonStrings.jwkNString(lexicalGlobalObject), publicKey.n); + RETURN_IF_EXCEPTION(scope, {}); + setEncodedValue(lexicalGlobalObject, scope, jwk, commonStrings.jwkEString(lexicalGlobalObject), publicKey.e); + RETURN_IF_EXCEPTION(scope, {}); + + if (exportType == CryptoKeyType::Private) { + auto privateKey = rsa.getPrivateKey(); + setEncodedValue(lexicalGlobalObject, scope, jwk, commonStrings.jwkDString(lexicalGlobalObject), publicKey.d); + RETURN_IF_EXCEPTION(scope, {}); + setEncodedValue(lexicalGlobalObject, scope, jwk, commonStrings.jwkPString(lexicalGlobalObject), privateKey.p); + RETURN_IF_EXCEPTION(scope, {}); + setEncodedValue(lexicalGlobalObject, scope, jwk, commonStrings.jwkQString(lexicalGlobalObject), privateKey.q); + RETURN_IF_EXCEPTION(scope, {}); + setEncodedValue(lexicalGlobalObject, scope, jwk, commonStrings.jwkDpString(lexicalGlobalObject), privateKey.dp); + RETURN_IF_EXCEPTION(scope, {}); + setEncodedValue(lexicalGlobalObject, scope, jwk, commonStrings.jwkDqString(lexicalGlobalObject), privateKey.dq); + RETURN_IF_EXCEPTION(scope, {}); + setEncodedValue(lexicalGlobalObject, scope, jwk, commonStrings.jwkQiString(lexicalGlobalObject), privateKey.qi); + } + + return jwk; +} + +JSC::JSValue KeyObject::exportJwkSecretKey(JSC::JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope) +{ + + VM& vm = lexicalGlobalObject->vm(); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + auto& commonStrings = globalObject->commonStrings(); + + JSObject* jwk = JSC::constructEmptyObject(lexicalGlobalObject); + + JSValue encoded = JSValue::decode(StringBytes::encode(lexicalGlobalObject, scope, m_data->symmetricKey, BufferEncodingType::base64url)); + RETURN_IF_EXCEPTION(scope, {}); + + jwk->putDirect(vm, + Identifier::fromString(vm, commonStrings.jwkKtyString(lexicalGlobalObject)->value(lexicalGlobalObject)), + commonStrings.jwkOctString(lexicalGlobalObject)); + + jwk->putDirect(vm, + Identifier::fromString(vm, commonStrings.jwkKString(lexicalGlobalObject)->value(lexicalGlobalObject)), + encoded); + + return jwk; +} + +JSC::JSValue KeyObject::exportJwkAsymmetricKey(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, CryptoKeyType exportType, bool handleRsaPss) +{ + switch (m_data->asymmetricKey.id()) { + case EVP_PKEY_RSA_PSS: { + if (handleRsaPss) { + return exportJwkRsaKey(globalObject, scope, exportType); + } + break; + } + + case EVP_PKEY_RSA: + return exportJwkRsaKey(globalObject, scope, exportType); + + case EVP_PKEY_EC: + return exportJwkEcKey(globalObject, scope, exportType); + + case EVP_PKEY_ED25519: + case EVP_PKEY_ED448: + case EVP_PKEY_X25519: + case EVP_PKEY_X448: + return exportJwkEdKey(globalObject, scope, exportType); + } + + ERR::CRYPTO_JWK_UNSUPPORTED_KEY_TYPE(scope, globalObject); + return {}; +} + +JSC::JSValue KeyObject::exportJwk(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, CryptoKeyType type, bool handleRsaPss) +{ + if (type == CryptoKeyType::Secret) { + return exportJwkSecretKey(globalObject, scope); + } + + return exportJwkAsymmetricKey(globalObject, scope, type, handleRsaPss); +} + +JSValue toJS(JSGlobalObject* lexicalGlobalObject, ThrowScope& scope, const ncrypto::BIOPointer& bio, const ncrypto::EVPKeyPointer::AsymmetricKeyEncodingConfig& encodingConfig) +{ + VM& vm = lexicalGlobalObject->vm(); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + BUF_MEM* bptr = bio; + + if (encodingConfig.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) { + WTF::String pem = String::fromUTF8({ bptr->data, bptr->length }); + return jsString(vm, pem); + } + + ASSERT(encodingConfig.format == ncrypto::EVPKeyPointer::PKFormatType::DER); + + RefPtr buf = JSC::ArrayBuffer::tryCreateUninitialized(bptr->length, 1); + if (!buf) { + throwOutOfMemoryError(lexicalGlobalObject, scope); + return {}; + } + memcpy(buf->data(), bptr->data, bptr->length); + + return JSUint8Array::create(lexicalGlobalObject, globalObject->JSBufferSubclassStructure(), WTFMove(buf), 0, bptr->length); +} + +JSC::JSValue KeyObject::exportPublic(JSC::JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig& config) +{ + VM& vm = lexicalGlobalObject->vm(); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + ASSERT(type() != CryptoKeyType::Secret); + + if (config.output_key_object) { + KeyObject keyObject = *this; + keyObject.type() = CryptoKeyType::Public; + Structure* structure = globalObject->m_JSPublicKeyObjectClassStructure.get(lexicalGlobalObject); + JSPublicKeyObject* publicKey = JSPublicKeyObject::create(vm, structure, lexicalGlobalObject, WTFMove(keyObject)); + return publicKey; + } + + if (config.format == ncrypto::EVPKeyPointer::PKFormatType::JWK) { + return exportJwk(lexicalGlobalObject, scope, CryptoKeyType::Public, false); + } + + const ncrypto::EVPKeyPointer& pkey = m_data->asymmetricKey; + auto res = pkey.writePublicKey(config); + if (!res) { + throwCryptoError(lexicalGlobalObject, scope, res.openssl_error.value_or(0), "Failed to encode public key"); + return {}; + } + + return toJS(lexicalGlobalObject, scope, res.value, config); +} + +JSValue KeyObject::exportPrivate(JSGlobalObject* lexicalGlobalObject, ThrowScope& scope, const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig& config) +{ + VM& vm = lexicalGlobalObject->vm(); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + ASSERT(type() != CryptoKeyType::Secret); + + if (config.output_key_object) { + KeyObject keyObject = *this; + Structure* structure = globalObject->m_JSPrivateKeyObjectClassStructure.get(lexicalGlobalObject); + JSPrivateKeyObject* privateKey = JSPrivateKeyObject::create(vm, structure, lexicalGlobalObject, WTFMove(keyObject)); + return privateKey; + } + + if (config.format == ncrypto::EVPKeyPointer::PKFormatType::JWK) { + return exportJwk(lexicalGlobalObject, scope, CryptoKeyType::Private, false); + } + + const ncrypto::EVPKeyPointer& pkey = m_data->asymmetricKey; + auto res = pkey.writePrivateKey(config); + if (!res) { + throwCryptoError(lexicalGlobalObject, scope, res.openssl_error.value_or(0), "Failed to encode private key"); + return {}; + } + + return toJS(lexicalGlobalObject, scope, res.value, config); +} + +JSValue KeyObject::exportAsymmetric(JSGlobalObject* globalObject, ThrowScope& scope, JSValue optionsValue, CryptoKeyType exportType) +{ + VM& vm = globalObject->vm(); + + ASSERT(type() != CryptoKeyType::Secret); + + if (JSObject* options = jsDynamicCast(optionsValue)) { + JSValue formatValue = options->get(globalObject, Identifier::fromString(vm, "format"_s)); + RETURN_IF_EXCEPTION(scope, {}); + + if (formatValue.isString()) { + auto* formatString = formatValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto formatView = formatString->view(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + if (formatView == "jwk"_s) { + if (exportType == CryptoKeyType::Private) { + JSValue passphraseValue = options->get(globalObject, Identifier::fromString(vm, "passphrase"_s)); + RETURN_IF_EXCEPTION(scope, {}); + if (!passphraseValue.isUndefined()) { + ERR::CRYPTO_INCOMPATIBLE_KEY_OPTIONS(scope, globalObject, "jwk"_s, "does not support encryption"_s); + return {}; + } + } + + return exportJwk(globalObject, scope, exportType, false); + } + } + + JSValue keyType = asymmetricKeyType(globalObject); + if (exportType == CryptoKeyType::Public) { + ncrypto::EVPKeyPointer::PublicKeyEncodingConfig config; + parsePublicKeyEncoding(globalObject, scope, options, keyType, WTF::nullStringView(), config); + RETURN_IF_EXCEPTION(scope, {}); + return exportPublic(globalObject, scope, config); + } + + ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig config; + parsePrivateKeyEncoding(globalObject, scope, options, keyType, WTF::nullStringView(), config); + RETURN_IF_EXCEPTION(scope, {}); + return exportPrivate(globalObject, scope, config); + } + + // This would hit validateObject in `parseKeyEncoding` + ERR::INVALID_ARG_TYPE(scope, globalObject, "options"_s, "object"_s, optionsValue); + return {}; +} + +JSValue KeyObject::exportSecret(JSGlobalObject* lexicalGlobalObject, ThrowScope& scope, JSValue optionsValue) +{ + VM& vm = lexicalGlobalObject->vm(); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + auto exportBuffer = [this, lexicalGlobalObject, globalObject, &scope]() -> JSValue { + auto key = symmetricKey(); + auto buf = ArrayBuffer::tryCreateUninitialized(key.size(), 1); + if (!buf) { + throwOutOfMemoryError(lexicalGlobalObject, scope); + return {}; + } + memcpy(buf->data(), key.data(), key.size()); + return JSUint8Array::create(lexicalGlobalObject, globalObject->JSBufferSubclassStructure(), WTFMove(buf), 0, key.size()); + }; + + if (!optionsValue.isUndefined()) { + V::validateObject(scope, lexicalGlobalObject, optionsValue, "options"_s); + RETURN_IF_EXCEPTION(scope, {}); + JSObject* options = jsDynamicCast(optionsValue); + + JSValue formatValue = options->get(lexicalGlobalObject, Identifier::fromString(vm, "format"_s)); + RETURN_IF_EXCEPTION(scope, {}); + if (!formatValue.isUndefined()) { + if (formatValue.isString()) { + auto* formatString = formatValue.toString(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto formatView = formatString->view(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + + if (formatView == "jwk"_s) { + return exportJwk(lexicalGlobalObject, scope, CryptoKeyType::Secret, false); + } + + if (formatView == "buffer"_s) { + return exportBuffer(); + } + } + + ERR::INVALID_ARG_VALUE(scope, lexicalGlobalObject, "options.format"_s, formatValue, "must be one of: undefined, 'buffer', 'jwk'"_s); + return {}; + } + } + + return exportBuffer(); +} + +JSValue KeyObject::asymmetricKeyType(JSGlobalObject* globalObject) +{ + VM& vm = globalObject->vm(); + + if (type() == CryptoKeyType::Secret) { + return jsUndefined(); + } + + switch (m_data->asymmetricKey.id()) { + case EVP_PKEY_RSA: + return jsNontrivialString(vm, "rsa"_s); + case EVP_PKEY_RSA_PSS: + return jsNontrivialString(vm, "rsa-pss"_s); + case EVP_PKEY_DSA: + return jsNontrivialString(vm, "dsa"_s); + case EVP_PKEY_DH: + return jsNontrivialString(vm, "dh"_s); + case EVP_PKEY_EC: + return jsNontrivialString(vm, "ec"_s); + case EVP_PKEY_ED25519: + return jsNontrivialString(vm, "ed25519"_s); + case EVP_PKEY_ED448: + return jsNontrivialString(vm, "ed448"_s); + case EVP_PKEY_X25519: + return jsNontrivialString(vm, "x25519"_s); + case EVP_PKEY_X448: + return jsNontrivialString(vm, "x448"_s); + default: + return jsUndefined(); + } +} + +void KeyObject::getRsaKeyDetails(JSGlobalObject* globalObject, ThrowScope& scope, JSObject* result) +{ + VM& vm = globalObject->vm(); + + const auto& pkey = m_data->asymmetricKey; + const ncrypto::Rsa rsa = pkey; + if (!rsa) { + return; + } + + auto pubKey = rsa.getPublicKey(); + + result->putDirect(vm, Identifier::fromString(vm, "modulusLength"_s), jsNumber(ncrypto::BignumPointer::GetBitCount(pubKey.n))); + + auto publicExponentHex = BignumPointer::toHex(pubKey.e); + if (!publicExponentHex) { + ERR::CRYPTO_OPERATION_FAILED(scope, globalObject, "Failed to create publicExponent"_s); + return; + } + + JSValue publicExponent = JSBigInt::parseInt(globalObject, vm, publicExponentHex.span(), 16, JSBigInt::ErrorParseMode::IgnoreExceptions, JSBigInt::ParseIntSign::Unsigned); + if (!publicExponent) { + ERR::CRYPTO_OPERATION_FAILED(scope, globalObject, "Failed to create public exponent"_s); + return; + } + + result->putDirect(vm, Identifier::fromString(vm, "publicExponent"_s), publicExponent); + + if (pkey.id() == EVP_PKEY_RSA_PSS) { + auto maybeParams = rsa.getPssParams(); + if (maybeParams.has_value()) { + auto& params = maybeParams.value(); + result->putDirect(vm, Identifier::fromString(vm, "hashAlgorithm"_s), jsString(vm, params.digest)); + + if (params.mgf1_digest.has_value()) { + auto digest = params.mgf1_digest.value(); + result->putDirect(vm, Identifier::fromString(vm, "mgf1HashAlgorithm"_s), jsString(vm, digest)); + } + + result->putDirect(vm, Identifier::fromString(vm, "saltLength"_s), jsNumber(params.salt_length)); + } + } +} + +void KeyObject::getDsaKeyDetails(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, JSC::JSObject* result) +{ + VM& vm = globalObject->vm(); + + const ncrypto::Dsa dsa = m_data->asymmetricKey; + if (!dsa) { + return; + } + + size_t modulusLength = dsa.getModulusLength(); + size_t divisorLength = dsa.getDivisorLength(); + + result->putDirect(vm, Identifier::fromString(vm, "modulusLength"_s), jsNumber(modulusLength)); + result->putDirect(vm, Identifier::fromString(vm, "divisorLength"_s), jsNumber(divisorLength)); +} + +void KeyObject::getEcKeyDetails(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, JSC::JSObject* result) +{ + VM& vm = globalObject->vm(); + + const auto& pkey = m_data->asymmetricKey; + ASSERT(pkey.id() == EVP_PKEY_EC); + const EC_KEY* ec = pkey; + + const auto group = ncrypto::ECKeyPointer::GetGroup(ec); + int nid = EC_GROUP_get_curve_name(group); + + String namedCurve = String::fromUTF8(OBJ_nid2sn(nid)); + + result->putDirect(vm, Identifier::fromString(vm, "namedCurve"_s), jsString(vm, namedCurve)); +} + +JSObject* KeyObject::asymmetricKeyDetails(JSGlobalObject* globalObject, ThrowScope& scope) +{ + JSObject* result = JSC::constructEmptyObject(globalObject); + + if (type() == CryptoKeyType::Secret) { + return result; + } + + switch (m_data->asymmetricKey.id()) { + case EVP_PKEY_RSA: + case EVP_PKEY_RSA_PSS: + getRsaKeyDetails(globalObject, scope, result); + RETURN_IF_EXCEPTION(scope, {}); + break; + case EVP_PKEY_DSA: + getDsaKeyDetails(globalObject, scope, result); + RETURN_IF_EXCEPTION(scope, {}); + break; + case EVP_PKEY_EC: { + getEcKeyDetails(globalObject, scope, result); + RETURN_IF_EXCEPTION(scope, {}); + break; + } + default: + } + + return result; +} + +// returns std::nullopt for "unsupported crypto operation" +std::optional KeyObject::equals(const KeyObject& other) const +{ + auto thisType = type(); + auto otherType = other.type(); + if (thisType != otherType) { + return false; + } + + switch (thisType) { + case CryptoKeyType::Secret: { + auto thisKey = symmetricKey().span(); + auto otherKey = other.symmetricKey().span(); + + if (thisKey.size() != otherKey.size()) { + return false; + } + + return CRYPTO_memcmp(thisKey.data(), otherKey.data(), thisKey.size()) == 0; + } + case CryptoKeyType::Public: + case CryptoKeyType::Private: { + EVP_PKEY* thisKey = m_data->asymmetricKey.get(); + EVP_PKEY* otherKey = other.m_data->asymmetricKey.get(); + + int ok = EVP_PKEY_cmp(thisKey, otherKey); + if (ok == -2) { + return std::nullopt; + } + + return ok == 1; + } + } +} + +JSValue KeyObject::toCryptoKey(JSGlobalObject* globalObject, ThrowScope& scope, JSValue algorithmValue, JSValue extractableValue, JSValue keyUsagesValue) +{ + return jsUndefined(); +} + +static std::optional*> getSymmetricKey(const WebCore::CryptoKey& key) +{ + switch (key.keyClass()) { + case WebCore::CryptoKeyClass::AES: + return &downcast(key).key(); + case WebCore::CryptoKeyClass::HMAC: + return &downcast(key).key(); + case WebCore::CryptoKeyClass::Raw: + return &downcast(key).key(); + default: { + return std::nullopt; + } + } +} + +KeyObject KeyObject::create(CryptoKeyType type, RefPtr&& data) +{ + return KeyObject(type, WTFMove(data)); +} + +WebCore::ExceptionOr KeyObject::create(WebCore::CryptoKey& key) +{ + // Determine KeyCryptoKeyType and Extract Material + switch (key.type()) { + case WebCore::CryptoKeyType::Secret: { + // Extract symmetric key data + std::optional*> keyData = getSymmetricKey(key); + if (!keyData) { + return WebCore::Exception { WebCore::ExceptionCode::CryptoOperationFailedError, "Failed to extract secret key material"_s }; + } + + WTF::Vector copy; + copy.appendVector(*keyData.value()); + return create(WTFMove(copy)); + } + + case WebCore::CryptoKeyType::Public: { + // Extract asymmetric public key data + AsymmetricKeyValue keyValue(key); + if (!keyValue.key) { + return WebCore::Exception { WebCore::ExceptionCode::CryptoOperationFailedError, "Failed to extract public key material"_s }; + } + + // Increment ref count because KeyObject will own a reference + EVP_PKEY_up_ref(keyValue.key); + ncrypto::EVPKeyPointer keyPtr(keyValue.key); + + return create(CryptoKeyType::Public, WTFMove(keyPtr)); + } + + case WebCore::CryptoKeyType::Private: { + // Extract asymmetric private key data + AsymmetricKeyValue keyValue(key); + if (!keyValue.key) { + return WebCore::Exception { WebCore::ExceptionCode::CryptoOperationFailedError, "Failed to extract private key material"_s }; + } + + // Increment ref count because KeyObject will own a reference + EVP_PKEY_up_ref(keyValue.key); + ncrypto::EVPKeyPointer keyPtr(keyValue.key); + + return create(CryptoKeyType::Private, WTFMove(keyPtr)); + } + } + + return WebCore::Exception { WebCore::ExceptionCode::CryptoOperationFailedError, "Unknown key type"_s }; +} + +KeyObject KeyObject::create(WTF::Vector&& symmetricKey) +{ + RefPtr data = KeyObjectData::create(WTFMove(symmetricKey)); + return KeyObject(CryptoKeyType::Secret, WTFMove(data)); +} + +KeyObject KeyObject::create(CryptoKeyType type, ncrypto::EVPKeyPointer&& asymmetricKey) +{ + RefPtr data = KeyObjectData::create(WTFMove(asymmetricKey)); + return KeyObject(type, WTFMove(data)); +} + +void KeyObject::getKeyObjectFromHandle(JSGlobalObject* globalObject, ThrowScope& scope, JSValue keyValue, const KeyObject& handle, PrepareAsymmetricKeyMode mode) +{ + if (mode == PrepareAsymmetricKeyMode::CreatePrivate) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "key"_s, "string, ArrayBuffer, Buffer, TypedArray, or DataView"_s, keyValue); + return; + } + + if (handle.type() != CryptoKeyType::Private) { + if (mode == PrepareAsymmetricKeyMode::ConsumePrivate || mode == PrepareAsymmetricKeyMode::CreatePublic) { + ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, handle.type(), "private"_s); + return; + } + if (handle.type() != CryptoKeyType::Public) { + ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, handle.type(), "private or public"_s); + return; + } + } +} + +JSArrayBufferView* decodeJwkString(JSGlobalObject* globalObject, ThrowScope& scope, GCOwnedDataScope strView, ASCIILiteral keyName) +{ + JSValue decoded = JSValue::decode(constructFromEncoding(globalObject, strView, BufferEncodingType::base64)); + RETURN_IF_EXCEPTION(scope, {}); + auto* decodedBuf = jsDynamicCast(decoded); + if (!decodedBuf) { + ERR::INVALID_ARG_TYPE(scope, globalObject, keyName, "string"_s, decoded); + return {}; + } + return decodedBuf; +} + +JSValue getJwkString(JSGlobalObject* globalObject, ThrowScope& scope, JSObject* jwk, ASCIILiteral propName, ASCIILiteral keyName) +{ + JSValue value = jwk->get(globalObject, Identifier::fromString(globalObject->vm(), propName)); + RETURN_IF_EXCEPTION(scope, {}); + V::validateString(scope, globalObject, value, keyName); + RETURN_IF_EXCEPTION(scope, {}); + return value; +} + +GCOwnedDataScope getJwkStringView(JSGlobalObject* globalObject, ThrowScope& scope, JSObject* jwk, ASCIILiteral propName, ASCIILiteral keyName) +{ + JSValue value = getJwkString(globalObject, scope, jwk, propName, keyName); + RETURN_IF_EXCEPTION(scope, GCOwnedDataScope(nullptr, WTF::nullStringView())); + auto* str = value.toString(globalObject); + RETURN_IF_EXCEPTION(scope, GCOwnedDataScope(nullptr, WTF::nullStringView())); + auto strView = str->view(globalObject); + RETURN_IF_EXCEPTION(scope, GCOwnedDataScope(nullptr, WTF::nullStringView())); + return strView; +} + +JSArrayBufferView* getDecodedJwkStringBuf(JSGlobalObject* globalObject, ThrowScope& scope, JSObject* jwk, ASCIILiteral propName, ASCIILiteral keyName) +{ + auto strView = getJwkStringView(globalObject, scope, jwk, propName, keyName); + RETURN_IF_EXCEPTION(scope, {}); + + auto* dataBuf = decodeJwkString(globalObject, scope, strView, keyName); + RETURN_IF_EXCEPTION(scope, {}); + + return dataBuf; +} + +inline BignumPointer jwkBufToBn(JSArrayBufferView* buf) +{ + return BignumPointer(reinterpret_cast(buf->vector()), buf->byteLength()); +} + +KeyObject KeyObject::getKeyObjectHandleFromJwk(JSGlobalObject* globalObject, ThrowScope& scope, JSObject* jwk, PrepareAsymmetricKeyMode mode) +{ + auto ktyView = getJwkStringView(globalObject, scope, jwk, "kty"_s, "key.kty"_s); + RETURN_IF_EXCEPTION(scope, {}); + + enum class Kty { + Rsa, + Ec, + Okp, + }; + + Kty kty; + if (ktyView == "RSA"_s) { + kty = Kty::Rsa; + } else if (ktyView == "EC"_s) { + kty = Kty::Ec; + } else if (ktyView == "OKP"_s) { + kty = Kty::Okp; + } else { + // validateOneOf + ERR::INVALID_ARG_VALUE(scope, globalObject, "key.kty"_s, ktyView.owner, "must be one of: 'RSA', 'EC', 'OKP'"_s); + return {}; + } + + CryptoKeyType keyType = mode == PrepareAsymmetricKeyMode::ConsumePublic || mode == PrepareAsymmetricKeyMode::CreatePublic + ? CryptoKeyType::Public + : CryptoKeyType::Private; + + switch (kty) { + case Kty::Okp: { + auto crvView = getJwkStringView(globalObject, scope, jwk, "crv"_s, "key.crv"_s); + RETURN_IF_EXCEPTION(scope, {}); + + int nid; + if (crvView == "Ed25519"_s) { + nid = EVP_PKEY_ED25519; + } else if (crvView == "Ed448"_s) { + nid = EVP_PKEY_ED448; + } else if (crvView == "X25519"_s) { + nid = EVP_PKEY_X25519; + } else if (crvView == "X448"_s) { + nid = EVP_PKEY_X448; + } else { + // validateOneOf + ERR::INVALID_ARG_VALUE(scope, globalObject, "key.crv"_s, crvView.owner, "must be one of: 'Ed25519', 'Ed448', 'X25519', 'X448'"_s); + return {}; + } + + auto xView = getJwkStringView(globalObject, scope, jwk, "x"_s, "key.x"_s); + RETURN_IF_EXCEPTION(scope, {}); + + GCOwnedDataScope dView = GCOwnedDataScope(nullptr, WTF::nullStringView()); + + if (keyType != CryptoKeyType::Public) { + dView = getJwkStringView(globalObject, scope, jwk, "d"_s, "key.d"_s); + RETURN_IF_EXCEPTION(scope, {}); + } + + auto dataView = keyType == CryptoKeyType::Public ? xView : dView; + + auto* dataBuf = decodeJwkString(globalObject, scope, dataView, "key.x"_s); + RETURN_IF_EXCEPTION(scope, {}); + auto bufSpan = dataBuf->span(); + + switch (nid) { + case EVP_PKEY_ED25519: + case EVP_PKEY_X25519: + if (bufSpan.size() != 32) { + ERR::CRYPTO_INVALID_JWK(scope, globalObject); + return {}; + } + break; + case EVP_PKEY_ED448: + if (bufSpan.size() != 57) { + ERR::CRYPTO_INVALID_JWK(scope, globalObject); + return {}; + } + break; + case EVP_PKEY_X448: + if (bufSpan.size() != 56) { + ERR::CRYPTO_INVALID_JWK(scope, globalObject); + return {}; + } + break; + } + + MarkPopErrorOnReturn markPopError; + + auto buf = ncrypto::Buffer { + .data = bufSpan.data(), + .len = bufSpan.size(), + }; + + auto key = keyType == CryptoKeyType::Public + ? EVPKeyPointer::NewRawPublic(nid, buf) + : EVPKeyPointer::NewRawPrivate(nid, buf); + + if (!key) { + ERR::CRYPTO_INVALID_JWK(scope, globalObject); + return {}; + } + + return create(keyType, WTFMove(key)); + } + case Kty::Ec: { + auto crvView = getJwkStringView(globalObject, scope, jwk, "crv"_s, "key.crv"_s); + RETURN_IF_EXCEPTION(scope, {}); + + if (crvView != "P-256"_s && crvView != "secp256k1"_s && crvView != "P-384"_s && crvView != "P-521"_s) { + // validateOneOf + ERR::INVALID_ARG_VALUE(scope, globalObject, "key.crv"_s, crvView.owner, "must be one of: 'P-256', 'secp256k1', 'P-384', 'P-521'"_s); + return {}; + } + + auto xView = getJwkStringView(globalObject, scope, jwk, "x"_s, "key.x"_s); + RETURN_IF_EXCEPTION(scope, {}); + auto yView = getJwkStringView(globalObject, scope, jwk, "y"_s, "key.y"_s); + RETURN_IF_EXCEPTION(scope, {}); + + GCOwnedDataScope dView = GCOwnedDataScope(nullptr, WTF::nullStringView()); + + if (keyType != CryptoKeyType::Public) { + dView = getJwkStringView(globalObject, scope, jwk, "d"_s, "key.d"_s); + RETURN_IF_EXCEPTION(scope, {}); + } + + MarkPopErrorOnReturn markPopError; + + auto crvUtf8 = crvView->utf8(); + int nid = Ec::GetCurveIdFromName(crvUtf8.data()); + if (nid == NID_undef) { + ERR::CRYPTO_INVALID_CURVE(scope, globalObject); + return {}; + } + + auto ec = ECKeyPointer::NewByCurveName(nid); + if (!ec) { + ERR::CRYPTO_INVALID_JWK(scope, globalObject); + return {}; + } + + auto* xBuf = decodeJwkString(globalObject, scope, xView, "key.x"_s); + RETURN_IF_EXCEPTION(scope, {}); + auto* yBuf = decodeJwkString(globalObject, scope, yView, "key.y"_s); + RETURN_IF_EXCEPTION(scope, {}); + + if (!ec.setPublicKeyRaw(jwkBufToBn(xBuf), jwkBufToBn(yBuf))) { + ERR::CRYPTO_INVALID_JWK(scope, globalObject, "Invalid JWK EC key"_s); + return {}; + } + + if (keyType != CryptoKeyType::Public) { + auto* dBuf = decodeJwkString(globalObject, scope, dView, "key.d"_s); + auto dBufSpan = dBuf->span(); + BignumPointer dBn = BignumPointer(dBufSpan.data(), dBufSpan.size()); + if (!ec.setPrivateKey(dBn)) { + ERR::CRYPTO_INVALID_JWK(scope, globalObject, "Invalid JWK EC key"_s); + return {}; + } + } + + auto key = EVPKeyPointer::New(); + key.set(ec); + + return create(keyType, WTFMove(key)); + } + case Kty::Rsa: { + auto nView = getJwkStringView(globalObject, scope, jwk, "n"_s, "key.n"_s); + RETURN_IF_EXCEPTION(scope, {}); + auto eView = getJwkStringView(globalObject, scope, jwk, "e"_s, "key.e"_s); + RETURN_IF_EXCEPTION(scope, {}); + + auto* nBuf = decodeJwkString(globalObject, scope, nView, "key.n"_s); + RETURN_IF_EXCEPTION(scope, {}); + auto* eBuf = decodeJwkString(globalObject, scope, eView, "key.e"_s); + RETURN_IF_EXCEPTION(scope, {}); + + RSAPointer rsa(RSA_new()); + Rsa rsaView(rsa.get()); + + if (!rsaView.setPublicKey(jwkBufToBn(nBuf), jwkBufToBn(eBuf))) { + ERR::CRYPTO_INVALID_JWK(scope, globalObject, "Invalid JWK RSA key"_s); + return {}; + } + + if (keyType == CryptoKeyType::Private) { + auto* dBuf = getDecodedJwkStringBuf(globalObject, scope, jwk, "d"_s, "key.d"_s); + RETURN_IF_EXCEPTION(scope, {}); + auto* pBuf = getDecodedJwkStringBuf(globalObject, scope, jwk, "p"_s, "key.p"_s); + RETURN_IF_EXCEPTION(scope, {}); + auto* qBuf = getDecodedJwkStringBuf(globalObject, scope, jwk, "q"_s, "key.q"_s); + RETURN_IF_EXCEPTION(scope, {}); + auto* dpBuf = getDecodedJwkStringBuf(globalObject, scope, jwk, "dp"_s, "key.dp"_s); + RETURN_IF_EXCEPTION(scope, {}); + auto* dqBuf = getDecodedJwkStringBuf(globalObject, scope, jwk, "dq"_s, "key.dq"_s); + RETURN_IF_EXCEPTION(scope, {}); + auto* qiBuf = getDecodedJwkStringBuf(globalObject, scope, jwk, "qi"_s, "key.qi"_s); + RETURN_IF_EXCEPTION(scope, {}); + + if (!rsaView.setPrivateKey( + jwkBufToBn(dBuf), + jwkBufToBn(qBuf), + jwkBufToBn(pBuf), + jwkBufToBn(dpBuf), + jwkBufToBn(dqBuf), + jwkBufToBn(qiBuf))) { + ERR::CRYPTO_INVALID_JWK(scope, globalObject, "Invalid JWK RSA key"_s); + return {}; + } + } + + auto key = EVPKeyPointer::NewRSA(WTFMove(rsa)); + return create(keyType, WTFMove(key)); + } + } + + UNREACHABLE(); +} + +void KeyObject::getKeyFormatAndType( + EVPKeyPointer::PKFormatType formatType, + std::optional encodingType, + KeyEncodingContext ctx, + EVPKeyPointer::AsymmetricKeyEncodingConfig& config) +{ + // if (!formatType) { + // ASSERT(ctx == KeyEncodingContext::Generate); + // config.output_key_object = true; + // } else { + config.output_key_object = false; + + config.format = formatType; + + if (encodingType) { + config.type = *encodingType; + } else { + ASSERT((ctx == KeyEncodingContext::Input && config.format == EVPKeyPointer::PKFormatType::PEM) + || (ctx == KeyEncodingContext::Generate && config.format == EVPKeyPointer::PKFormatType::JWK)); + config.type = EVPKeyPointer::PKEncodingType::PKCS1; + } + // } +} + +EVPKeyPointer::PrivateKeyEncodingConfig KeyObject::getPrivateKeyEncoding( + JSGlobalObject* globalObject, + ThrowScope& scope, + EVPKeyPointer::PKFormatType formatType, + std::optional encodingType, + const EVP_CIPHER* cipher, + std::optional passphrase, + KeyEncodingContext ctx) +{ + EVPKeyPointer::PrivateKeyEncodingConfig config; + getKeyFormatAndType(formatType, encodingType, ctx, config); + + if (config.output_key_object) { + // TODO: make sure this case for key generation is handled + } else { + if (ctx != KeyEncodingContext::Input) { + config.cipher = cipher; + } + + if (passphrase) { + config.passphrase = WTFMove(*passphrase); + } + } + + return config; +} + +// KeyObjectHandle::init for public and private keys +KeyObject KeyObject::getPublicOrPrivateKey( + JSGlobalObject* globalObject, + ThrowScope& scope, + std::span keyData, + CryptoKeyType keyType, + EVPKeyPointer::PKFormatType formatType, + std::optional encodingType, + const EVP_CIPHER* cipher, + std::optional passphrase) +{ + auto buf = ncrypto::Buffer { + .data = reinterpret_cast(keyData.data()), + .len = keyData.size(), + }; + + if (keyType == CryptoKeyType::Private) { + auto config = getPrivateKeyEncoding( + globalObject, + scope, + formatType, + encodingType, + cipher, + WTFMove(passphrase), + KeyEncodingContext::Input); + RETURN_IF_EXCEPTION(scope, {}); + + auto res = EVPKeyPointer::TryParsePrivateKey(config, buf); + if (res) { + return create(CryptoKeyType::Private, WTFMove(res.value)); + } + + if (res.error.value() == EVPKeyPointer::PKParseError::NEED_PASSPHRASE) { + ERR::MISSING_PASSPHRASE(scope, globalObject, "Passphrase required for encrypted key"_s); + } else { + throwCryptoError(globalObject, scope, res.openssl_error.value_or(0), "Failed to read private key"_s); + } + return {}; + } + + if (buf.len > INT_MAX) { + ERR::OUT_OF_RANGE(scope, globalObject, "keyData is too big"_s); + return {}; + } + + auto config = getPrivateKeyEncoding( + globalObject, + scope, + formatType, + encodingType, + cipher, + WTFMove(passphrase), + KeyEncodingContext::Input); + RETURN_IF_EXCEPTION(scope, {}); + + if (config.format == EVPKeyPointer::PKFormatType::PEM) { + auto publicRes = EVPKeyPointer::TryParsePublicKeyPEM(buf); + if (publicRes) { + return create(CryptoKeyType::Public, WTFMove(publicRes.value)); + } + + if (publicRes.error.value() == EVPKeyPointer::PKParseError::NOT_RECOGNIZED) { + auto privateRes = EVPKeyPointer::TryParsePrivateKey(config, buf); + if (privateRes) { + return create(CryptoKeyType::Public, WTFMove(privateRes.value)); + } + + if (privateRes.error.value() == EVPKeyPointer::PKParseError::NEED_PASSPHRASE) { + ERR::MISSING_PASSPHRASE(scope, globalObject, "Passphrase required for encrypted key"_s); + } else { + throwCryptoError(globalObject, scope, privateRes.openssl_error.value_or(0), "Failed to read private key"_s); + } + return {}; + } + + throwCryptoError(globalObject, scope, publicRes.openssl_error.value_or(0), "Failed to read asymmetric key"_s); + return {}; + } + + static const auto isPublic = [](const auto& config, const auto& buffer) -> bool { + switch (config.type) { + case EVPKeyPointer::PKEncodingType::PKCS1: + return !EVPKeyPointer::IsRSAPrivateKey(buffer); + case EVPKeyPointer::PKEncodingType::SPKI: + return true; + case EVPKeyPointer::PKEncodingType::PKCS8: + return false; + case EVPKeyPointer::PKEncodingType::SEC1: + return false; + default: + return false; + } + }; + + if (isPublic(config, buf)) { + auto res = EVPKeyPointer::TryParsePublicKey(config, buf); + if (res) { + return create(CryptoKeyType::Public, WTFMove(res.value)); + } + + throwCryptoError(globalObject, scope, res.openssl_error.value_or(0), "Failed to read asymmetric key"_s); + return {}; + } + + auto res = EVPKeyPointer::TryParsePrivateKey(config, buf); + if (res) { + return create(CryptoKeyType::Private, WTFMove(res.value)); + } + + if (res.error.value() == EVPKeyPointer::PKParseError::NEED_PASSPHRASE) { + ERR::MISSING_PASSPHRASE(scope, globalObject, "Passphrase required for encrypted key"_s); + } else { + throwCryptoError(globalObject, scope, res.openssl_error.value_or(0), "Failed to read asymmetric key"_s); + } + return {}; +} + +KeyObject::PrepareAsymmetricKeyResult KeyObject::prepareAsymmetricKey(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, JSC::JSValue keyValue, PrepareAsymmetricKeyMode mode) +{ + VM& vm = globalObject->vm(); + + auto checkKeyObject = [globalObject, &scope, mode](const KeyObject& keyObject, JSValue keyValue) -> void { + if (mode == PrepareAsymmetricKeyMode::CreatePrivate) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "key"_s, "string, ArrayBuffer, Buffer, TypedArray, or DataView"_s, keyValue); + return; + } + + if (keyObject.type() != CryptoKeyType::Private) { + if (mode == PrepareAsymmetricKeyMode::ConsumePrivate || mode == PrepareAsymmetricKeyMode::CreatePublic) { + ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, keyObject.type(), "private"_s); + return; + } + if (keyObject.type() != CryptoKeyType::Public) { + ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, keyObject.type(), "private or public"_s); + return; + } + } + }; + + auto checkCryptoKey = [globalObject, &scope, mode](const CryptoKey& cryptoKey, JSValue keyValue) -> void { + if (mode == PrepareAsymmetricKeyMode::CreatePrivate) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "key"_s, "string, ArrayBuffer, Buffer, TypedArray, or DataView"_s, keyValue); + return; + } + + if (cryptoKey.type() != CryptoKeyType::Private) { + if (mode == PrepareAsymmetricKeyMode::ConsumePrivate || mode == PrepareAsymmetricKeyMode::CreatePublic) { + ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, cryptoKey.type(), "private"_s); + return; + } + if (cryptoKey.type() != CryptoKeyType::Public) { + ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, cryptoKey.type(), "private or public"_s); + return; + } + } + }; + + if (JSKeyObject* keyObject = jsDynamicCast(keyValue)) { + auto& handle = keyObject->handle(); + checkKeyObject(handle, keyValue); + RETURN_IF_EXCEPTION(scope, {}); + return { .keyData = handle.data() }; + } + + if (JSCryptoKey* cryptoKey = jsDynamicCast(keyValue)) { + auto& key = cryptoKey->wrapped(); + checkCryptoKey(key, keyValue); + RETURN_IF_EXCEPTION(scope, {}); + + auto keyObject = create(key); + if (UNLIKELY(keyObject.hasException())) { + WebCore::propagateException(*globalObject, scope, keyObject.releaseException()); + return {}; + } + KeyObject handle = keyObject.releaseReturnValue(); + RETURN_IF_EXCEPTION(scope, {}); + return { .keyData = handle.data() }; + } + + { // pem format + if (keyValue.isString()) { + auto* keyString = keyValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto keyView = keyString->view(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + JSValue decoded = JSValue::decode(constructFromEncoding(globalObject, keyView, BufferEncodingType::utf8)); + RETURN_IF_EXCEPTION(scope, {}); + + auto* decodedBuf = jsDynamicCast(decoded); + if (!decodedBuf) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "key"_s, "string"_s, decoded); + return {}; + } + + return { + .keyDataView = { decodedBuf, decodedBuf->span() }, + .formatType = EVPKeyPointer::PKFormatType::PEM, + }; + } + + if (auto* view = jsDynamicCast(keyValue)) { + return { + .keyDataView = { view, view->span() }, + .formatType = EVPKeyPointer::PKFormatType::PEM, + }; + } + + if (auto* arrayBuffer = jsDynamicCast(keyValue)) { + auto* buffer = arrayBuffer->impl(); + return { + .keyDataView = { arrayBuffer, buffer->span() }, + .formatType = EVPKeyPointer::PKFormatType::PEM, + }; + } + } + + if (JSObject* keyObj = jsDynamicCast(keyValue)) { + JSValue dataValue = keyObj->get(globalObject, Identifier::fromString(vm, "key"_s)); + RETURN_IF_EXCEPTION(scope, {}); + JSValue encodingValue = keyObj->get(globalObject, Identifier::fromString(vm, "encoding"_s)); + RETURN_IF_EXCEPTION(scope, {}); + JSValue formatValue = keyObj->get(globalObject, Identifier::fromString(vm, "format"_s)); + RETURN_IF_EXCEPTION(scope, {}); + + if (JSKeyObject* keyObject = jsDynamicCast(dataValue)) { + auto& handle = keyObject->handle(); + checkKeyObject(handle, dataValue); + RETURN_IF_EXCEPTION(scope, {}); + return { .keyData = handle.data() }; + } + + if (JSCryptoKey* cryptoKey = jsDynamicCast(dataValue)) { + auto& key = cryptoKey->wrapped(); + checkCryptoKey(key, dataValue); + RETURN_IF_EXCEPTION(scope, {}); + + auto keyObject = create(key); + if (UNLIKELY(keyObject.hasException())) { + WebCore::propagateException(*globalObject, scope, keyObject.releaseException()); + } + KeyObject handle = keyObject.releaseReturnValue(); + return { .keyData = handle.data() }; + } + + auto* formatString = formatValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto formatView = formatString->view(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + if (formatView == "jwk"_s) { + V::validateObject(scope, globalObject, dataValue, "key.key"_s); + RETURN_IF_EXCEPTION(scope, {}); + JSObject* jwk = dataValue.getObject(); + KeyObject handle = getKeyObjectHandleFromJwk(globalObject, scope, jwk, mode); + RETURN_IF_EXCEPTION(scope, {}); + return { .keyData = handle.data() }; + } + + std::optional isPublic = mode == PrepareAsymmetricKeyMode::ConsumePrivate || mode == PrepareAsymmetricKeyMode::CreatePrivate + ? std::optional(false) + : std::nullopt; + + if (dataValue.isString()) { + auto* dataString = dataValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto dataView = dataString->view(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + BufferEncodingType encoding = BufferEncodingType::utf8; + if (encodingValue.isString()) { + auto* encodingString = encodingValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto encodingView = encodingString->view(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + if (encodingView != "buffer"_s) { + encoding = parseEnumerationFromView(encodingView).value_or(BufferEncodingType::utf8); + RETURN_IF_EXCEPTION(scope, {}); + } + } + + JSValue decoded = JSValue::decode(constructFromEncoding(globalObject, dataView, encoding)); + RETURN_IF_EXCEPTION(scope, {}); + if (auto* decodedView = jsDynamicCast(decoded)) { + EVPKeyPointer::PrivateKeyEncodingConfig config; + parseKeyEncoding(globalObject, scope, keyObj, jsUndefined(), isPublic, WTF::nullStringView(), config); + RETURN_IF_EXCEPTION(scope, {}); + + return { + .keyDataView = { decodedView, decodedView->span() }, + .formatType = config.format, + .encodingType = config.type, + .cipher = config.cipher, + .passphrase = WTFMove(config.passphrase), + }; + } + } + + if (auto* view = jsDynamicCast(dataValue)) { + auto buffer = view->span(); + + EVPKeyPointer::PrivateKeyEncodingConfig config; + parseKeyEncoding(globalObject, scope, keyObj, jsUndefined(), isPublic, WTF::nullStringView(), config); + RETURN_IF_EXCEPTION(scope, {}); + + return { + .keyDataView = { view, buffer }, + .formatType = config.format, + .encodingType = config.type, + .cipher = config.cipher, + .passphrase = WTFMove(config.passphrase), + }; + } + + if (auto* arrayBuffer = jsDynamicCast(dataValue)) { + auto* buffer = arrayBuffer->impl(); + auto data = buffer->span(); + + EVPKeyPointer::PrivateKeyEncodingConfig config; + parseKeyEncoding(globalObject, scope, keyObj, jsUndefined(), isPublic, WTF::nullStringView(), config); + RETURN_IF_EXCEPTION(scope, {}); + + return { + .keyDataView = { arrayBuffer, data }, + .formatType = config.format, + .encodingType = config.type, + .cipher = config.cipher, + .passphrase = WTFMove(config.passphrase), + }; + } + + if (mode != PrepareAsymmetricKeyMode::CreatePrivate) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "key.key"_s, "string or an instance of ArrayBuffer, Buffer, TypedArray, DataView, KeyObject, or CryptoKey"_s, dataValue); + } else { + ERR::INVALID_ARG_TYPE(scope, globalObject, "key.key"_s, "string or an instance of ArrayBuffer, Buffer, TypedArray, or DataView"_s, dataValue); + } + return {}; + } + + if (mode != PrepareAsymmetricKeyMode::CreatePrivate) { + ERR::INVALID_ARG_TYPE(scope, globalObject, "key"_s, "string or an instance of ArrayBuffer, Buffer, TypedArray, DataView, KeyObject, or CryptoKey"_s, keyValue); + } else { + ERR::INVALID_ARG_TYPE(scope, globalObject, "key"_s, "string or an instance of ArrayBuffer, Buffer, TypedArray, or DataView"_s, keyValue); + } + + return {}; +} + +KeyObject::PrepareAsymmetricKeyResult KeyObject::preparePrivateKey(JSGlobalObject* globalObject, ThrowScope& scope, JSValue keyValue) +{ + return prepareAsymmetricKey(globalObject, scope, keyValue, PrepareAsymmetricKeyMode::ConsumePrivate); +} + +KeyObject::PrepareAsymmetricKeyResult KeyObject::preparePublicOrPrivateKey(JSGlobalObject* globalObject, ThrowScope& scope, JSValue keyValue) +{ + return prepareAsymmetricKey(globalObject, scope, keyValue, PrepareAsymmetricKeyMode::ConsumePublic); +} + +KeyObject KeyObject::prepareSecretKey(JSGlobalObject* globalObject, ThrowScope& scope, JSValue keyValue, JSValue encodingValue, bool bufferOnly) +{ + if (!bufferOnly) { + if (JSKeyObject* keyObject = jsDynamicCast(keyValue)) { + auto& handle = keyObject->handle(); + if (handle.type() != CryptoKeyType::Secret) { + ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, handle.type(), "secret"_s); + return {}; + } + return handle; + } else if (JSCryptoKey* cryptoKey = jsDynamicCast(keyValue)) { + auto& key = cryptoKey->wrapped(); + if (key.type() != CryptoKeyType::Secret) { + ERR::CRYPTO_INVALID_KEY_OBJECT_TYPE(scope, globalObject, key.type(), "secret"_s); + return {}; + } + auto keyObject = create(key); + if (UNLIKELY(keyObject.hasException())) { + WebCore::propagateException(globalObject, scope, keyObject.releaseException()); + return {}; + } + return keyObject.releaseReturnValue(); + } + } + + if (keyValue.isString()) { + auto* keyString = keyValue.toString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + auto keyView = keyString->view(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + BufferEncodingType encoding = parseEnumerationAllowBuffer(*globalObject, encodingValue).value_or(BufferEncodingType::utf8); + RETURN_IF_EXCEPTION(scope, {}); + + JSValue buffer = JSValue::decode(constructFromEncoding(globalObject, keyView, encoding)); + RETURN_IF_EXCEPTION(scope, {}); + + if (buffer.isEmpty()) { + // Both this exception and the one below should be unreachable, but constructFromEncoding doesn't + // guarentee that it will always return a valid buffer. + ERR::INVALID_ARG_VALUE(scope, globalObject, "encoding"_s, keyValue, "must be a valid encoding"_s); + return {}; + } + + auto* view = jsDynamicCast(buffer); + if (!view) { + ERR::INVALID_ARG_VALUE(scope, globalObject, "encoding"_s, keyValue, "must be a valid encoding"_s); + return {}; + } + + Vector copy; + copy.append(view->span()); + return create(WTFMove(copy)); + } + + // TODO(dylan-conway): avoid copying by keeping the buffer alive + if (auto* view = jsDynamicCast(keyValue)) { + Vector copy; + copy.append(view->span()); + return create(WTFMove(copy)); + } + + // TODO(dylan-conway): avoid copying by keeping the buffer alive + if (auto* arrayBuffer = jsDynamicCast(keyValue)) { + auto* impl = arrayBuffer->impl(); + Vector copy; + copy.append(impl->span()); + return create(WTFMove(copy)); + } + + if (bufferOnly) { + ERR::INVALID_ARG_INSTANCE(scope, globalObject, "key"_s, "ArrayBuffer, Buffer, TypedArray, or DataView"_s, keyValue); + } else { + ERR::INVALID_ARG_TYPE(scope, globalObject, "key"_s, "string or an instance of ArrayBuffer, Buffer, TypedArray, DataView, KeyObject, or CryptoKey"_s, keyValue); + } + + return {}; +} +} diff --git a/src/bun.js/bindings/node/crypto/KeyObject.h b/src/bun.js/bindings/node/crypto/KeyObject.h new file mode 100644 index 0000000000..f33fb40382 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/KeyObject.h @@ -0,0 +1,126 @@ +#pragma once + +#include "root.h" +#include "ncrypto.h" +#include "ExceptionOr.h" +#include "CryptoKeyType.h" +#include "KeyObjectData.h" + +namespace WebCore { +class CryptoKey; +} + +namespace Bun { + +class KeyObject { + WTF_MAKE_TZONE_ALLOCATED(KeyObject); + + KeyObject(WebCore::CryptoKeyType type, RefPtr&& data) + : m_data(WTFMove(data)) + , m_type(type) + { + } + +public: + KeyObject() = default; + ~KeyObject() = default; + + static WebCore::ExceptionOr create(WebCore::CryptoKey&); + static KeyObject create(WTF::Vector&& symmetricKey); + static KeyObject create(WebCore::CryptoKeyType type, ncrypto::EVPKeyPointer&& asymmetricKey); + static KeyObject create(WebCore::CryptoKeyType type, RefPtr&& data); + // static KeyObject createJwk(JSC::JSGlobalObject*, JSC::ThrowScope&, JSC::JSValue keyValue, WebCore::CryptoKeyType type); + + enum class KeyEncodingContext { + Input, + Export, + Generate, + }; + + enum class PrepareAsymmetricKeyMode { + ConsumePublic, + ConsumePrivate, + CreatePublic, + CreatePrivate, + }; + +private: + // Helpers for `prepareAsymmetricKey` + static KeyObject getKeyObjectHandleFromJwk(JSC::JSGlobalObject*, JSC::ThrowScope&, JSC::JSObject* jwk, PrepareAsymmetricKeyMode mode); + static void getKeyObjectFromHandle(JSC::JSGlobalObject*, JSC::ThrowScope&, JSC::JSValue keyValue, const KeyObject& handle, PrepareAsymmetricKeyMode mode); + +public: + static ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig getPrivateKeyEncoding( + JSC::JSGlobalObject*, + JSC::ThrowScope&, + ncrypto::EVPKeyPointer::PKFormatType formatType, + std::optional encodingType, + const EVP_CIPHER* cipher, + std::optional passphrase, + KeyEncodingContext ctx); + + static void getKeyFormatAndType( + ncrypto::EVPKeyPointer::PKFormatType formatType, + std::optional encodingType, + KeyEncodingContext ctx, + ncrypto::EVPKeyPointer::AsymmetricKeyEncodingConfig& config); + + static KeyObject getPublicOrPrivateKey( + JSC::JSGlobalObject* globalObject, + JSC::ThrowScope& scope, + std::span keyData, + WebCore::CryptoKeyType keyType, + ncrypto::EVPKeyPointer::PKFormatType formatType, + std::optional encodingType, + const EVP_CIPHER* cipher, + std::optional passphrase); + + struct PrepareAsymmetricKeyResult { + std::optional> keyData { std::nullopt }; + JSC::GCOwnedDataScope> keyDataView { nullptr, {} }; + ncrypto::EVPKeyPointer::PKFormatType formatType; + std::optional encodingType { std::nullopt }; + const EVP_CIPHER* cipher { nullptr }; + std::optional passphrase = { std::nullopt }; + }; + + static PrepareAsymmetricKeyResult prepareAsymmetricKey(JSC::JSGlobalObject*, JSC::ThrowScope&, JSC::JSValue keyValue, PrepareAsymmetricKeyMode mode); + static PrepareAsymmetricKeyResult preparePrivateKey(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, JSC::JSValue keyValue); + static PrepareAsymmetricKeyResult preparePublicOrPrivateKey(JSC::JSGlobalObject* globalObject, JSC::ThrowScope& scope, JSC::JSValue keyValue); + static KeyObject prepareSecretKey(JSC::JSGlobalObject*, JSC::ThrowScope&, JSC::JSValue keyValue, JSC::JSValue encodingValue, bool bufferOnly = false); + + JSC::JSValue exportJwkEdKey(JSC::JSGlobalObject*, JSC::ThrowScope&, WebCore::CryptoKeyType exportType); + JSC::JSValue exportJwkEcKey(JSC::JSGlobalObject*, JSC::ThrowScope&, WebCore::CryptoKeyType exportType); + JSC::JSValue exportJwkRsaKey(JSC::JSGlobalObject*, JSC::ThrowScope&, WebCore::CryptoKeyType exportType); + JSC::JSValue exportJwkSecretKey(JSC::JSGlobalObject*, JSC::ThrowScope&); + JSC::JSValue exportJwkAsymmetricKey(JSC::JSGlobalObject*, JSC::ThrowScope&, WebCore::CryptoKeyType exportType, bool handleRsaPss); + JSC::JSValue exportJwk(JSC::JSGlobalObject*, JSC::ThrowScope&, WebCore::CryptoKeyType type, bool handleRsaPss); + JSC::JSValue exportPublic(JSC::JSGlobalObject*, JSC::ThrowScope&, const ncrypto::EVPKeyPointer::PublicKeyEncodingConfig&); + JSC::JSValue exportPrivate(JSC::JSGlobalObject*, JSC::ThrowScope&, const ncrypto::EVPKeyPointer::PrivateKeyEncodingConfig&); + JSC::JSValue exportAsymmetric(JSC::JSGlobalObject*, JSC::ThrowScope&, JSC::JSValue optionsValue, WebCore::CryptoKeyType exportType); + JSC::JSValue exportSecret(JSC::JSGlobalObject*, JSC::ThrowScope&, JSC::JSValue optionsValue); + + void getRsaKeyDetails(JSC::JSGlobalObject*, JSC::ThrowScope&, JSC::JSObject* result); + void getDsaKeyDetails(JSC::JSGlobalObject*, JSC::ThrowScope&, JSC::JSObject* result); + void getEcKeyDetails(JSC::JSGlobalObject*, JSC::ThrowScope&, JSC::JSObject* result); + // void getDhKeyDetails(JSC::JSGlobalObject* , JSC::ThrowScope& , JSC::JSObject* result); + + JSC::JSValue asymmetricKeyType(JSC::JSGlobalObject*); + JSC::JSObject* asymmetricKeyDetails(JSC::JSGlobalObject*, JSC::ThrowScope&); + + std::optional equals(const KeyObject& other) const; + JSC::JSValue toCryptoKey(JSC::JSGlobalObject*, JSC::ThrowScope&, + JSC::JSValue algorithmValue, JSC::JSValue extractableValue, JSC::JSValue keyUsagesValue); + + inline WebCore::CryptoKeyType type() const { return m_type; } + inline WebCore::CryptoKeyType& type() { return m_type; } + const WTF::Vector& symmetricKey() const { return m_data->symmetricKey; } + const ncrypto::EVPKeyPointer& asymmetricKey() const { return m_data->asymmetricKey; } + RefPtr data() const { return m_data; } + +private: + RefPtr m_data = nullptr; + WebCore::CryptoKeyType m_type = WebCore::CryptoKeyType::Secret; +}; + +} diff --git a/src/bun.js/bindings/node/crypto/KeyObjectData.h b/src/bun.js/bindings/node/crypto/KeyObjectData.h new file mode 100644 index 0000000000..46ccfda7e2 --- /dev/null +++ b/src/bun.js/bindings/node/crypto/KeyObjectData.h @@ -0,0 +1,35 @@ +#pragma once + +#include "root.h" +#include "ncrypto.h" +#include "CryptoKeyType.h" + +struct KeyObjectData : ThreadSafeRefCounted { + WTF_MAKE_TZONE_ALLOCATED(KeyObjectData); + + KeyObjectData(WTF::Vector&& symmetricKey) + : symmetricKey(WTFMove(symmetricKey)) + { + } + + KeyObjectData(ncrypto::EVPKeyPointer&& asymmetricKey) + : asymmetricKey(WTFMove(asymmetricKey)) + { + } + +public: + ~KeyObjectData() = default; + + static RefPtr create(WTF::Vector&& symmetricKey) + { + return adoptRef(*new KeyObjectData(WTFMove(symmetricKey))); + } + + static RefPtr create(ncrypto::EVPKeyPointer&& asymmetricKey) + { + return adoptRef(*new KeyObjectData(WTFMove(asymmetricKey))); + } + + WTF::Vector symmetricKey; + const ncrypto::EVPKeyPointer asymmetricKey; +}; diff --git a/src/bun.js/bindings/node/crypto/node_crypto_binding.cpp b/src/bun.js/bindings/node/crypto/node_crypto_binding.cpp index d933d43696..095a316ca0 100644 --- a/src/bun.js/bindings/node/crypto/node_crypto_binding.cpp +++ b/src/bun.js/bindings/node/crypto/node_crypto_binding.cpp @@ -1,5 +1,4 @@ #include "node_crypto_binding.h" -#include "KeyObject.h" #include "ErrorCode.h" #include "JavaScriptCore/JSArrayBufferView.h" #include "JavaScriptCore/JSCJSValue.h" @@ -47,66 +46,20 @@ #include "CryptoPrimes.h" #include "JSCipher.h" #include "CryptoHkdf.h" +#include "JSKeyObject.h" +#include "JSSecretKeyObject.h" +#include "JSPublicKeyObject.h" +#include "JSPrivateKeyObject.h" +#include "CryptoUtil.h" +#include "CryptoKeygen.h" +#include "CryptoGenKeyPair.h" +#include "CryptoKeys.h" +#include "CryptoDhJob.h" +#include "CryptoSignJob.h" using namespace JSC; -using namespace Bun; -namespace WebCore { - -JSC_DEFINE_HOST_FUNCTION(jsStatelessDH, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - auto& vm = JSC::getVM(lexicalGlobalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - - if (callFrame->argumentCount() < 2) { - return Bun::ERR::INVALID_ARG_VALUE(scope, lexicalGlobalObject, "diffieHellman"_s, jsUndefined(), "requires 2 arguments"_s); - } - - auto* privateKeyObj = JSC::jsDynamicCast(callFrame->argument(0)); - auto* publicKeyObj = JSC::jsDynamicCast(callFrame->argument(1)); - - if (!privateKeyObj || !publicKeyObj) { - return Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "diffieHellman"_s, "CryptoKey"_s, !privateKeyObj ? callFrame->argument(0) : callFrame->argument(1)); - } - - auto& privateKey = privateKeyObj->wrapped(); - auto& publicKey = publicKeyObj->wrapped(); - - // Create AsymmetricKeyValue objects to access the EVP_PKEY pointers - WebCore::AsymmetricKeyValue ourKeyValue(privateKey); - WebCore::AsymmetricKeyValue theirKeyValue(publicKey); - - // Get the EVP_PKEY from both keys - EVP_PKEY* ourKey = ourKeyValue.key; - EVP_PKEY* theirKey = theirKeyValue.key; - - if (!ourKey || !theirKey) { - return Bun::ERR::INVALID_ARG_VALUE(scope, lexicalGlobalObject, "key"_s, jsUndefined(), "is invalid"_s); - } - - // Create EVPKeyPointers to wrap the keys - ncrypto::EVPKeyPointer ourKeyPtr(ourKey); - ncrypto::EVPKeyPointer theirKeyPtr(theirKey); - - // Use DHPointer::stateless to compute the shared secret - auto secret = ncrypto::DHPointer::stateless(ourKeyPtr, theirKeyPtr).release(); - - // These are owned by AsymmetricKeyValue, not by EVPKeyPointer. - ourKeyPtr.release(); - theirKeyPtr.release(); - - auto buffer = ArrayBuffer::createFromBytes({ reinterpret_cast(secret.data), secret.len }, createSharedTask([](void* p) { - OPENSSL_free(p); - })); - Zig::GlobalObject* globalObject = reinterpret_cast(lexicalGlobalObject); - auto* result = JSC::JSUint8Array::create(lexicalGlobalObject, globalObject->JSBufferSubclassStructure(), WTFMove(buffer), 0, secret.len); - RETURN_IF_EXCEPTION(scope, {}); - if (!result) { - return Bun::ERR::INVALID_ARG_VALUE(scope, lexicalGlobalObject, "diffieHellman"_s, jsUndefined(), "failed to allocate result buffer"_s); - } - - return JSC::JSValue::encode(result); -} +namespace Bun { JSC_DEFINE_HOST_FUNCTION(jsGetCurves, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { @@ -180,12 +133,12 @@ JSC_DEFINE_HOST_FUNCTION(jsCertVerifySpkac, (JSC::JSGlobalObject * lexicalGlobal auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - auto input = KeyObject__GetBuffer(callFrame->argument(0)); - if (input.hasException()) { + auto input = getBuffer(callFrame->argument(0)); + if (!input) { return JSValue::encode(jsUndefined()); } - auto buffer = input.releaseReturnValue(); + auto buffer = input.value(); if (buffer.size() > std::numeric_limits().max()) { return Bun::ERR::OUT_OF_RANGE(scope, lexicalGlobalObject, "spkac"_s, 0, std::numeric_limits().max(), jsNumber(buffer.size())); } @@ -199,12 +152,12 @@ JSC_DEFINE_HOST_FUNCTION(jsCertExportPublicKey, (JSC::JSGlobalObject * lexicalGl auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - auto input = KeyObject__GetBuffer(callFrame->argument(0)); - if (input.hasException()) { + auto input = getBuffer(callFrame->argument(0)); + if (!input) { return JSValue::encode(jsEmptyString(vm)); } - auto buffer = input.releaseReturnValue(); + auto buffer = input.value(); if (buffer.size() > std::numeric_limits().max()) { return Bun::ERR::OUT_OF_RANGE(scope, lexicalGlobalObject, "spkac"_s, 0, std::numeric_limits().max(), jsNumber(buffer.size())); } @@ -228,12 +181,12 @@ JSC_DEFINE_HOST_FUNCTION(jsCertExportChallenge, (JSC::JSGlobalObject * lexicalGl auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - auto input = KeyObject__GetBuffer(callFrame->argument(0)); - if (input.hasException()) { + auto input = getBuffer(callFrame->argument(0)); + if (!input) { return JSValue::encode(jsEmptyString(vm)); } - auto buffer = input.releaseReturnValue(); + auto buffer = input.value(); if (buffer.size() > std::numeric_limits().max()) { return Bun::ERR::OUT_OF_RANGE(scope, lexicalGlobalObject, "spkac"_s, 0, std::numeric_limits().max(), jsNumber(buffer.size())); } @@ -377,9 +330,6 @@ JSValue createNodeCryptoBinding(Zig::GlobalObject* globalObject) VM& vm = globalObject->vm(); JSObject* obj = constructEmptyObject(globalObject); - obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "statelessDH"_s)), - JSFunction::create(vm, globalObject, 2, "statelessDH"_s, jsStatelessDH, ImplementationVisibility::Public, NoIntrinsic), 0); - obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "certVerifySpkac"_s)), JSFunction::create(vm, globalObject, 1, "verifySpkac"_s, jsCertVerifySpkac, ImplementationVisibility::Public, NoIntrinsic), 0); obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "certExportPublicKey"_s)), @@ -416,6 +366,8 @@ JSValue createNodeCryptoBinding(Zig::GlobalObject* globalObject) globalObject->m_JSDiffieHellmanClassStructure.constructor(globalObject)); obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "DiffieHellmanGroup"_s)), globalObject->m_JSDiffieHellmanGroupClassStructure.constructor(globalObject)); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "diffieHellman"_s)), + JSFunction::create(vm, globalObject, 2, "diffieHellman"_s, jsDiffieHellman, ImplementationVisibility::Public, NoIntrinsic), 0); obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "generatePrime"_s)), JSFunction::create(vm, globalObject, 3, "generatePrime"_s, jsGeneratePrime, ImplementationVisibility::Public, NoIntrinsic), 0); @@ -434,7 +386,45 @@ JSValue createNodeCryptoBinding(Zig::GlobalObject* globalObject) obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "hkdfSync"_s)), JSFunction::create(vm, globalObject, 5, "hkdfSync"_s, jsHkdfSync, ImplementationVisibility::Public, NoIntrinsic), 0); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "KeyObject"_s)), + globalObject->m_JSKeyObjectClassStructure.constructor(globalObject)); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "SecretKeyObject"_s)), + globalObject->m_JSSecretKeyObjectClassStructure.constructor(globalObject)); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "PublicKeyObject"_s)), + globalObject->m_JSPublicKeyObjectClassStructure.constructor(globalObject)); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "PrivateKeyObject"_s)), + globalObject->m_JSPrivateKeyObjectClassStructure.constructor(globalObject)); + + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "publicEncrypt"_s)), + JSFunction::create(vm, globalObject, 2, "publicEncrypt"_s, jsPublicEncrypt, ImplementationVisibility::Public, NoIntrinsic), 0); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "publicDecrypt"_s)), + JSFunction::create(vm, globalObject, 2, "publicDecrypt"_s, jsPublicDecrypt, ImplementationVisibility::Public, NoIntrinsic), 0); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "privateEncrypt"_s)), + JSFunction::create(vm, globalObject, 2, "privateEncrypt"_s, jsPrivateEncrypt, ImplementationVisibility::Public, NoIntrinsic), 0); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "privateDecrypt"_s)), + JSFunction::create(vm, globalObject, 2, "privateDecrypt"_s, jsPrivateDecrypt, ImplementationVisibility::Public, NoIntrinsic), 0); + + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "createSecretKey"_s)), + JSFunction::create(vm, globalObject, 2, "createSecretKey"_s, jsCreateSecretKey, ImplementationVisibility::Public, NoIntrinsic), 0); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "createPublicKey"_s)), + JSFunction::create(vm, globalObject, 1, "createPublicKey"_s, jsCreatePublicKey, ImplementationVisibility::Public, NoIntrinsic), 0); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "createPrivateKey"_s)), + JSFunction::create(vm, globalObject, 1, "createPrivateKey"_s, jsCreatePrivateKey, ImplementationVisibility::Public, NoIntrinsic), 0); + + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "generateKey"_s)), + JSFunction::create(vm, globalObject, 3, "generateKey"_s, jsGenerateKey, ImplementationVisibility::Public, NoIntrinsic), 0); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "generateKeySync"_s)), + JSFunction::create(vm, globalObject, 2, "generateKeySync"_s, jsGenerateKeySync, ImplementationVisibility::Public, NoIntrinsic), 0); + + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "generateKeyPair"_s)), + JSFunction::create(vm, globalObject, 3, "generateKeyPair"_s, jsGenerateKeyPair, ImplementationVisibility::Public, NoIntrinsic), 0); + obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "generateKeyPairSync"_s)), + JSFunction::create(vm, globalObject, 2, "generateKeyPairSync"_s, jsGenerateKeyPairSync, ImplementationVisibility::Public, NoIntrinsic), 0); + + obj->putDirect(vm, Identifier::fromString(vm, "X509Certificate"_s), + globalObject->m_JSX509CertificateClassStructure.constructor(globalObject)); + return obj; } -} // namespace WebCore +} // namespace Bun diff --git a/src/bun.js/bindings/node/crypto/node_crypto_binding.h b/src/bun.js/bindings/node/crypto/node_crypto_binding.h index 6aac413975..252784808f 100644 --- a/src/bun.js/bindings/node/crypto/node_crypto_binding.h +++ b/src/bun.js/bindings/node/crypto/node_crypto_binding.h @@ -5,11 +5,8 @@ #include "helpers.h" #include "ncrypto.h" -using namespace Bun; -using namespace JSC; - -namespace WebCore { +namespace Bun { JSC::JSValue createNodeCryptoBinding(Zig::GlobalObject* globalObject); -} // namespace WebCore +} // namespace Bun diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h index 159d2417cd..e41c3cae0b 100644 --- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h @@ -75,7 +75,7 @@ public: std::unique_ptr m_clientSubspaceForDOMURL; std::unique_ptr m_clientSubspaceForURLSearchParams; std::unique_ptr m_clientSubspaceForURLSearchParamsIterator; - + std::unique_ptr m_clientSubspaceForCookie; std::unique_ptr m_clientSubspaceForCookieMap; std::unique_ptr m_clientSubspaceForCookieMapIterator; @@ -933,6 +933,10 @@ public: std::unique_ptr m_clientSubspaceForJSHmac; std::unique_ptr m_clientSubspaceForJSHash; std::unique_ptr m_clientSubspaceForJSCipher; + std::unique_ptr m_clientSubspaceForJSKeyObject; + std::unique_ptr m_clientSubspaceForJSSecretKeyObject; + std::unique_ptr m_clientSubspaceForJSPublicKeyObject; + std::unique_ptr m_clientSubspaceForJSPrivateKeyObject; std::unique_ptr m_clientSubspaceForServerRouteList; std::unique_ptr m_clientSubspaceForBunRequest; }; diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h index 6b64f5d0f3..269fd15a21 100644 --- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h @@ -920,7 +920,7 @@ public: std::unique_ptr m_subspaceForExposedToWorkerAndWindow; std::unique_ptr m_subspaceForURLSearchParams; std::unique_ptr m_subspaceForURLSearchParamsIterator; - + std::unique_ptr m_subspaceForCookie; std::unique_ptr m_subspaceForCookieMap; std::unique_ptr m_subspaceForCookieMapIterator; @@ -939,6 +939,10 @@ public: std::unique_ptr m_subspaceForJSDiffieHellmanGroup; std::unique_ptr m_subspaceForJSECDH; std::unique_ptr m_subspaceForJSCipher; + std::unique_ptr m_subspaceForJSKeyObject; + std::unique_ptr m_subspaceForJSSecretKeyObject; + std::unique_ptr m_subspaceForJSPublicKeyObject; + std::unique_ptr m_subspaceForJSPrivateKeyObject; }; } // namespace WebCore diff --git a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp index 152b97ac59..3d62140d0c 100644 --- a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp +++ b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp @@ -108,6 +108,11 @@ #include "ZigGeneratedClasses.h" #include "JSX509Certificate.h" #include "ncrypto.h" +#include "JSKeyObject.h" +#include "JSSecretKeyObject.h" +#include "JSPublicKeyObject.h" +#include "JSPrivateKeyObject.h" +#include "CryptoKeyType.h" #if USE(CG) #include @@ -131,6 +136,7 @@ namespace WebCore { using namespace JSC; +using namespace Bun; DEFINE_ALLOCATOR_WITH_HEAP_IDENTIFIER(SerializedScriptValue); @@ -236,6 +242,7 @@ enum SerializationTag { Bun__BlobTag = 254, // bun types start at 254 and decrease with each addition Bun__X509CertificateTag = 253, + Bun__KeyObjectTag = 252, ErrorTag = 255 }; @@ -1825,9 +1832,18 @@ private: serializedKey, SerializationContext::Default, dummySharedBuffers, m_forStorage); rawKeySerializer.write(key); Vector wrappedKey; - if (!wrapCryptoKey(m_lexicalGlobalObject, serializedKey, wrappedKey)) - return false; - write(wrappedKey); + + // Wrapping isn't required + // https://github.com/WebKit/WebKit/blob/c0902fc4dd3abf5d2d5e008eb0b008aeae837953/Source/WebCore/crypto/SerializedCryptoKeyWrap.h#L35-L40 + // + // and doesn't do anything currently, so we skip it. + // https://github.com/WebKit/WebKit/blob/c0902fc4dd3abf5d2d5e008eb0b008aeae837953/Source/WebCore/crypto/gcrypt/SerializedCryptoKeyWrapGCrypt.cpp#L49 + // https://github.com/WebKit/WebKit/blob/c0902fc4dd3abf5d2d5e008eb0b008aeae837953/Source/WebCore/crypto/openssl/SerializedCryptoKeyWrapOpenSSL.cpp#L51 + // + // if (!wrapCryptoKey(m_lexicalGlobalObject, serializedKey, wrappedKey)) + // return false; + + write(serializedKey); return true; } #endif @@ -1959,6 +1975,57 @@ private: return true; } + if (auto* keyObject = jsDynamicCast(obj)) { + write(Bun__KeyObjectTag); + + auto& handle = keyObject->handle(); + + write(static_cast(handle.type())); + + switch (handle.type()) { + case CryptoKeyType::Secret: { + write(handle.symmetricKey()); + return true; + } + case CryptoKeyType::Public: { + auto scope = DECLARE_THROW_SCOPE(vm); + auto* pkey = handle.asymmetricKey().get(); + + BIO* bio = BIO_new(BIO_s_mem()); + + PEM_write_bio_PUBKEY(bio, pkey); + + const uint8_t* pemData = nullptr; + uint64_t pemSize = 0; + BIO_mem_contents(bio, &pemData, reinterpret_cast(&pemSize)); + + write(pemSize); + write(pemData, pemSize); + BIO_free(bio); + + return true; + } + case CryptoKeyType::Private: { + auto scope = DECLARE_THROW_SCOPE(vm); + auto* pkey = handle.asymmetricKey().get(); + + BIO* bio = BIO_new(BIO_s_mem()); + + PEM_write_bio_PrivateKey(bio, pkey, nullptr, nullptr, 0, nullptr, nullptr); + + const uint8_t* pemData = nullptr; + uint64_t pemSize = 0; + BIO_mem_contents(bio, &pemData, reinterpret_cast(&pemSize)); + + write(pemSize); + write(pemData, pemSize); + BIO_free(bio); + + return true; + } + } + } + return false; } // Any other types are expected to serialize as null. @@ -3820,6 +3887,29 @@ private: return true; } + bool read(CryptoKeyType& result) + { + uint8_t type; + if (!read(type)) + return false; + if (type > cryptoKeyTypeMaximumValue) { + return false; + } + result = static_cast(type); + return true; + } + + bool read(BIO** bio, uint64_t length) + { + if (m_ptr + length > m_end) + return false; + *bio = BIO_new_mem_buf(m_ptr, length); + if (!*bio) + return false; + m_ptr += length; + return true; + } + bool readHMACKey(bool extractable, CryptoKeyUsageBitmap usages, RefPtr& result) { Vector keyData; @@ -4434,6 +4524,66 @@ private: return cert_obj; } + JSValue readKeyObject() + { + VM& vm = m_globalObject->vm(); + auto* globalObject = defaultGlobalObject(m_globalObject); + + CryptoKeyType keyType; + if (!read(keyType)) { + fail(); + return JSValue(); + } + + switch (keyType) { + case CryptoKeyType::Secret: { + Vector keyData; + if (!read(keyData)) { + fail(); + return JSValue(); + } + + KeyObject keyObject = KeyObject::create(WTFMove(keyData)); + Structure* structure = globalObject->m_JSSecretKeyObjectClassStructure.get(m_globalObject); + return JSSecretKeyObject::create(vm, structure, m_globalObject, WTFMove(keyObject)); + } + case CryptoKeyType::Public: + case CryptoKeyType::Private: { + uint64_t pemSize = 0; + if (!read(pemSize)) { + fail(); + return JSValue(); + } + + BIO* bio = nullptr; + if (!read(&bio, pemSize)) { + fail(); + return JSValue(); + } + + if (keyType == CryptoKeyType::Public) { + EVP_PKEY* pkey = PEM_read_bio_PUBKEY(bio, nullptr, nullptr, nullptr); + if (!pkey) { + fail(); + return JSValue(); + } + auto keyObject = KeyObject::create(CryptoKeyType::Public, ncrypto::EVPKeyPointer(pkey)); + Structure* structure = globalObject->m_JSPublicKeyObjectClassStructure.get(m_globalObject); + return JSPublicKeyObject::create(vm, structure, m_globalObject, WTFMove(keyObject)); + } + + EVP_PKEY* pkey = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr); + if (!pkey) { + fail(); + return JSValue(); + } + auto keyObject = KeyObject::create(CryptoKeyType::Private, ncrypto::EVPKeyPointer(pkey)); + Structure* structure = globalObject->m_JSPrivateKeyObjectClassStructure.get(m_globalObject); + return JSPrivateKeyObject::create(vm, structure, m_globalObject, WTFMove(keyObject)); + } + } + } + JSValue readDOMException() { CachedStringRef message; @@ -4927,16 +5077,19 @@ private: } #if ENABLE(WEB_CRYPTO) case CryptoKeyTag: { - Vector wrappedKey; - if (!read(wrappedKey)) { - fail(); - return JSValue(); - } Vector serializedKey; - if (!unwrapCryptoKey(m_lexicalGlobalObject, wrappedKey, serializedKey)) { + if (!read(serializedKey)) { fail(); return JSValue(); } + + // See CryptoKey serialization for why we don't wrap + // + // Vector serializedKey; + // if (!unwrapCryptoKey(m_lexicalGlobalObject, wrappedKey, serializedKey)) { + // fail(); + // return JSValue(); + // } JSValue cryptoKey; // Vector> dummyMessagePorts; // CloneDeserializer rawKeyDeserializer(m_lexicalGlobalObject, m_globalObject, dummyMessagePorts, nullptr, {}, serializedKey); @@ -4992,6 +5145,9 @@ private: case Bun__X509CertificateTag: return readX509Certificate(); + case Bun__KeyObjectTag: + return readKeyObject(); + default: m_ptr--; // Push the tag back return JSValue(); @@ -5021,8 +5177,8 @@ private: Vector m_blobURLs; Vector m_blobFilePaths; ArrayBufferContentsArray* m_sharedBuffers; - // Vector> m_backingStores; - // Vector> m_imageBitmaps; +// Vector> m_backingStores; +// Vector> m_imageBitmaps; #if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS) Vector> m_detachedOffscreenCanvases; Vector> m_offscreenCanvases; diff --git a/src/bun.js/bindings/webcrypto/CryptoKeyType.h b/src/bun.js/bindings/webcrypto/CryptoKeyType.h index f090806dbb..36974fc8d4 100644 --- a/src/bun.js/bindings/webcrypto/CryptoKeyType.h +++ b/src/bun.js/bindings/webcrypto/CryptoKeyType.h @@ -29,12 +29,14 @@ namespace WebCore { -enum class CryptoKeyType { - Public, +enum class CryptoKeyType : uint8_t { + Public = 0, Private, Secret }; +const constexpr uint8_t cryptoKeyTypeMaximumValue = 2; + } // namespace WebCore #endif // ENABLE(WEB_CRYPTO) diff --git a/src/bun.js/bindings/webcrypto/JSCryptoKey.cpp b/src/bun.js/bindings/webcrypto/JSCryptoKey.cpp index fdc7b5225c..f0859052b0 100644 --- a/src/bun.js/bindings/webcrypto/JSCryptoKey.cpp +++ b/src/bun.js/bindings/webcrypto/JSCryptoKey.cpp @@ -174,28 +174,6 @@ static const HashTableValue JSCryptoKeyPrototypeTableValues[] = { const ClassInfo JSCryptoKeyPrototype::s_info = { "CryptoKey"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCryptoKeyPrototype) }; -JSCryptoKey* JSCryptoKey::fromJS(JSGlobalObject* globalObject, JSValue value) -{ - if (value.inherits()) { - return jsCast(value); - } - - JSObject* object = value.getObject(); - if (!object) { - return nullptr; - } - - auto& vm = JSC::getVM(globalObject); - - auto& names = WebCore::builtinNames(vm); - - if (auto nativeValue = object->getIfPropertyExists(globalObject, names.bunNativePtrPrivateName())) { - return jsDynamicCast(nativeValue); - } - - return nullptr; -} - void JSCryptoKeyPrototype::finishCreation(VM& vm) { Base::finishCreation(vm); diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig index ad202f6e2e..44c57d992b 100644 --- a/src/bun.js/event_loop.zig +++ b/src/bun.js/event_loop.zig @@ -966,9 +966,16 @@ pub const EventLoop = struct { loop.runCallback(callback, global, thisValue, &.{ arg0, arg1 }); } + fn externRunCallback3(global: *JSC.JSGlobalObject, callback: JSC.JSValue, thisValue: JSC.JSValue, arg0: JSC.JSValue, arg1: JSC.JSValue, arg2: JSC.JSValue) callconv(.c) void { + const vm = global.bunVM(); + var loop = vm.eventLoop(); + loop.runCallback(callback, global, thisValue, &.{ arg0, arg1, arg2 }); + } + comptime { @export(&externRunCallback1, .{ .name = "Bun__EventLoop__runCallback1" }); @export(&externRunCallback2, .{ .name = "Bun__EventLoop__runCallback2" }); + @export(&externRunCallback3, .{ .name = "Bun__EventLoop__runCallback3" }); } pub fn runCallbackWithResult(this: *EventLoop, callback: JSC.JSValue, globalObject: *JSC.JSGlobalObject, thisValue: JSC.JSValue, arguments: []const JSC.JSValue) JSC.JSValue { diff --git a/src/bun.js/modules/NodeUtilTypesModule.cpp b/src/bun.js/modules/NodeUtilTypesModule.cpp index 69c8e40acc..2e5574ac60 100644 --- a/src/bun.js/modules/NodeUtilTypesModule.cpp +++ b/src/bun.js/modules/NodeUtilTypesModule.cpp @@ -16,6 +16,7 @@ #include #include #include "ZigGeneratedClasses.h" +#include "JSKeyObject.h" #include "NodeUtilTypesModule.h" @@ -445,29 +446,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionIsKeyObject, JSC::CallFrame* callframe)) { GET_FIRST_CELL - - if (!cell->isObject()) { - return JSValue::encode(jsBoolean(false)); - } - - auto* object = cell->getObject(); - - auto& vm = JSC::getVM(globalObject); - const auto& names = WebCore::builtinNames(vm); - - auto scope = DECLARE_CATCH_SCOPE(vm); - - if (auto val = object->getIfPropertyExists(globalObject, - names.bunNativePtrPrivateName())) { - if (val.isCell() && val.inherits()) - return JSValue::encode(jsBoolean(true)); - } - - if (scope.exception()) { - scope.clearException(); - } - - return JSValue::encode(jsBoolean(false)); + return JSValue::encode(jsBoolean(cell->inherits())); } JSC_DEFINE_HOST_FUNCTION(jsFunctionIsCryptoKey, (JSC::JSGlobalObject * globalObject, diff --git a/src/bun.js/node/node_crypto_binding.zig b/src/bun.js/node/node_crypto_binding.zig index c8dff5011f..3f03ad4ae5 100644 --- a/src/bun.js/node/node_crypto_binding.zig +++ b/src/bun.js/node/node_crypto_binding.zig @@ -104,11 +104,27 @@ fn ExternCryptoJob(comptime name: []const u8) type { pub const CheckPrimeJob = ExternCryptoJob("CheckPrimeJob"); pub const GeneratePrimeJob = ExternCryptoJob("GeneratePrimeJob"); pub const HkdfJob = ExternCryptoJob("HkdfJob"); +pub const SecretKeyJob = ExternCryptoJob("SecretKeyJob"); +pub const RsaKeyPairJob = ExternCryptoJob("RsaKeyPairJob"); +pub const DsaKeyPairJob = ExternCryptoJob("DsaKeyPairJob"); +pub const EcKeyPairJob = ExternCryptoJob("EcKeyPairJob"); +pub const NidKeyPairJob = ExternCryptoJob("NidKeyPairJob"); +pub const DhKeyPairJob = ExternCryptoJob("DhKeyPairJob"); +pub const DhJob = ExternCryptoJob("DhJob"); +pub const SignJob = ExternCryptoJob("SignJob"); comptime { _ = CheckPrimeJob; _ = GeneratePrimeJob; _ = HkdfJob; + _ = SecretKeyJob; + _ = RsaKeyPairJob; + _ = DsaKeyPairJob; + _ = EcKeyPairJob; + _ = NidKeyPairJob; + _ = DhKeyPairJob; + _ = DhJob; + _ = SignJob; } fn CryptoJob(comptime Ctx: type) type { diff --git a/src/crash_handler.zig b/src/crash_handler.zig index e2c1bcabea..bc66e14147 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -166,7 +166,7 @@ pub const Action = union(enum) { res.kind.label(), }); }, - .dlopen => |path| try writer.print("while loading native module: {s}", .{path}), + .dlopen => |path| try writer.print("loading native module: {s}", .{path}), } } }; diff --git a/src/js/node/crypto.ts b/src/js/node/crypto.ts index 557809c6f8..d6c3218b87 100644 --- a/src/js/node/crypto.ts +++ b/src/js/node/crypto.ts @@ -1,29 +1,11 @@ // Hardcoded module "node:crypto" -const StreamModule = require("node:stream"); const StringDecoder = require("node:string_decoder").StringDecoder; const LazyTransform = require("internal/streams/lazy_transform"); +const { defineCustomPromisifyArgs } = require("internal/promisify"); +const Writable = require("internal/streams/writable"); const { CryptoHasher } = Bun; const { - symmetricKeySize, - asymmetricKeyDetails, - asymmetricKeyType, - equals, - exports, - createSecretKey, - createPublicKey, - createPrivateKey, - generateKeySync, - generateKeyPairSync, - publicEncrypt, - privateDecrypt, - privateEncrypt, - publicDecrypt, - X509Certificate, -} = $cpp("KeyObject.cpp", "createKeyObjectBinding"); - -const { - statelessDH, getCurves, certVerifySpkac, certExportPublicKey, @@ -37,8 +19,9 @@ const { Hmac: _Hmac, Hash: _Hash, ECDH, - DiffieHellman: _DiffieHellman, - DiffieHellmanGroup: _DiffieHellmanGroup, + DiffieHellman, + DiffieHellmanGroup, + diffieHellman, checkPrime, checkPrimeSync, generatePrime, @@ -46,6 +29,24 @@ const { Cipher, hkdf, hkdfSync, + + publicEncrypt, + publicDecrypt, + privateEncrypt, + privateDecrypt, + + KeyObject, + + createSecretKey, + createPublicKey, + createPrivateKey, + + generateKey, + generateKeySync, + generateKeyPair, + generateKeyPairSync, + + X509Certificate, } = $cpp("node_crypto_binding.cpp", "createNodeCryptoBinding"); const { @@ -68,7 +69,7 @@ const { const normalizeEncoding = $newZigFunction("node_util_binding.zig", "normalizeEncoding", 1); -const { validateObject, validateString } = require("internal/validators"); +const { validateString } = require("internal/validators"); const kHandle = Symbol("kHandle"); @@ -133,327 +134,21 @@ var crypto_exports: any = {}; crypto_exports.getRandomValues = value => crypto.getRandomValues(value); crypto_exports.constants = $processBindingConstants.crypto; -class KeyObject { - // we use $bunNativePtr so that util.types.isKeyObject can detect it - $bunNativePtr = undefined; - constructor(key) { - // TODO: check why this is fails - // if(!(key instanceof CryptoKey)) { - // throw new TypeError("The \"key\" argument must be an instance of CryptoKey."); - // } - if (typeof key !== "object") { - throw new TypeError('The "key" argument must be an instance of CryptoKey.'); - } - this.$bunNativePtr = key; - } - - get [Symbol.toStringTag]() { - return "KeyObject"; - } - - static from(key) { - if (key instanceof KeyObject) { - key = key.$bunNativePtr; - } - return new KeyObject(key); - } - - get asymmetricKeyDetails() { - return asymmetricKeyDetails(this.$bunNativePtr); - } - - get symmetricKeySize() { - return symmetricKeySize(this.$bunNativePtr); - } - - get asymmetricKeyType() { - return asymmetricKeyType(this.$bunNativePtr); - } - - ["export"](options) { - switch (arguments.length) { - case 0: - switch (this.type) { - case "secret": - options = { - format: "buffer", - }; - break; - case "public": - options = { - format: "pem", - type: "spki", - }; - break; - case "private": - options = { - format: "pem", - type: "pkcs8", - }; - break; - } - break; - case 1: - if (typeof options === "object" && !options.format) { - switch (this.type) { - case "secret": - options.format = "buffer"; - break; - default: - options.format = "pem"; - break; - } - } - } - return exports(this.$bunNativePtr, options); - } - - equals(otherKey) { - if (!(otherKey instanceof KeyObject)) { - throw new TypeError("otherKey must be a KeyObject"); - } - return equals(this.$bunNativePtr, otherKey.$bunNativePtr); - } - - get type() { - return this.$bunNativePtr.type; - } -} - -crypto_exports.generateKeySync = function (algorithm, options) { - return KeyObject.from(generateKeySync(algorithm, options?.length)); -}; - -crypto_exports.generateKey = function (algorithm, options, callback) { - try { - const key = KeyObject.from(generateKeySync(algorithm, options?.length)); - typeof callback === "function" && callback(null, KeyObject.from(key)); - } catch (err) { - typeof callback === "function" && callback(err); - } -}; - -function _generateKeyPairSync(algorithm, options) { - const result = generateKeyPairSync(algorithm, options); - if (result) { - const publicKeyEncoding = options?.publicKeyEncoding; - const privateKeyEncoding = options?.privateKeyEncoding; - result.publicKey = publicKeyEncoding - ? KeyObject.from(result.publicKey).export(publicKeyEncoding) - : KeyObject.from(result.publicKey); - result.privateKey = privateKeyEncoding - ? KeyObject.from(result.privateKey).export(privateKeyEncoding) - : KeyObject.from(result.privateKey); - } - return result; -} -crypto_exports.generateKeyPairSync = _generateKeyPairSync; - -function _generateKeyPair(algorithm, options, callback) { - try { - const result = _generateKeyPairSync(algorithm, options); - typeof callback === "function" && callback(null, result.publicKey, result.privateKey); - } catch (err) { - typeof callback === "function" && callback(err); - } -} -const { defineCustomPromisifyArgs } = require("internal/promisify"); -defineCustomPromisifyArgs(_generateKeyPair, ["publicKey", "privateKey"]); -crypto_exports.generateKeyPair = _generateKeyPair; - -crypto_exports.createSecretKey = function (key, encoding) { - if (key instanceof KeyObject || key instanceof CryptoKey) { - if (key.type !== "secret") { - const error = new TypeError( - `ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Invalid key object type ${key.type}, expected secret`, - ); - error.code = "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE"; - throw error; - } - return KeyObject.from(key); - } - - const buffer = getArrayBufferOrView(key, encoding || "utf8"); - return KeyObject.from(createSecretKey(buffer)); -}; - -function _createPrivateKey(key) { - if (typeof key === "string") { - key = Buffer.from(key, "utf8"); - return KeyObject.from(createPrivateKey({ key, format: "pem" })); - } else if (isAnyArrayBuffer(key) || isArrayBufferView(key)) { - return KeyObject.from(createPrivateKey({ key, format: "pem" })); - } else if (typeof key === "object") { - if (key instanceof KeyObject || key instanceof CryptoKey) { - const error = new TypeError(`ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Invalid key object type ${key.type}`); - error.code = "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE"; - throw error; - } else { - let actual_key = key.key; - if (typeof actual_key === "string") { - actual_key = Buffer.from(actual_key, key.encoding || "utf8"); - key.key = actual_key; - } else if (actual_key instanceof KeyObject || actual_key instanceof CryptoKey) { - const error = new TypeError(`ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Invalid key object type ${key.type}`); - error.code = "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE"; - throw error; - } - if (!isAnyArrayBuffer(actual_key) && !isArrayBufferView(actual_key) && typeof actual_key !== "object") { - var error = new TypeError( - `ERR_INVALID_ARG_TYPE: The "key" argument must be of type string or an instance of ArrayBuffer, Buffer, TypedArray, DataView or object. Received ` + - actual_key, - ); - error.code = "ERR_INVALID_ARG_TYPE"; - throw error; - } - if (!key.format) { - key.format = "pem"; - } - return KeyObject.from(createPrivateKey(key)); - } - } else { - var error = new TypeError( - `ERR_INVALID_ARG_TYPE: The "key" argument must be of type string or an instance of ArrayBuffer, Buffer, TypedArray, DataView or object. Received ` + - key, - ); - error.code = "ERR_INVALID_ARG_TYPE"; - throw error; - } -} -crypto_exports.createPrivateKey = _createPrivateKey; - -function _createPublicKey(key) { - if (typeof key === "string") { - key = Buffer.from(key, "utf8"); - return KeyObject.from(createPublicKey({ key, format: "pem" })); - } else if (isAnyArrayBuffer(key) || isArrayBufferView(key)) { - return KeyObject.from(createPublicKey({ key, format: "pem" })); - } else if (typeof key === "object") { - if (key instanceof KeyObject || key instanceof CryptoKey) { - if (key.type === "private") { - return KeyObject.from(createPublicKey({ key: key.$bunNativePtr || key, format: "" })); - } - const error = new TypeError( - `ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Invalid key object type ${key.type}, expected private`, - ); - error.code = "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE"; - throw error; - } else { - // must be an encrypted private key (this option is not documented at all) - if (key.passphrase) { - //TODO: handle encrypted keys in one native call - let actual_key = key.key; - if (typeof actual_key === "string") { - actual_key = Buffer.from(actual_key, key.encoding || "utf8"); - } - return KeyObject.from( - createPublicKey({ - key: createPrivateKey({ key: actual_key, format: key.format || "pem", passphrase: key.passphrase }), - format: "", - }), - ); - } - let actual_key = key.key; - if (typeof actual_key === "string") { - actual_key = Buffer.from(actual_key, key.encoding || "utf8"); - key.key = actual_key; - } else if (actual_key instanceof KeyObject || actual_key instanceof CryptoKey) { - if (actual_key.type === "private") { - return KeyObject.from(createPublicKey({ key: actual_key.$bunNativePtr || actual_key, format: "" })); - } - const error = new TypeError( - `ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Invalid key object type ${actual_key.type}, expected private`, - ); - error.code = "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE"; - throw error; - } - if (!isAnyArrayBuffer(actual_key) && !isArrayBufferView(actual_key) && typeof actual_key !== "object") { - var error = new TypeError( - `ERR_INVALID_ARG_TYPE: The "key" argument must be of type string or an instance of ArrayBuffer, Buffer, TypedArray, DataView or object. Received ` + - key, - ); - error.code = "ERR_INVALID_ARG_TYPE"; - throw error; - } - if (!key.format) { - key.format = "pem"; - } - return KeyObject.from(createPublicKey(key)); - } - } else { - var error = new TypeError( - `ERR_INVALID_ARG_TYPE: The "key" argument must be of type string or an instance of ArrayBuffer, Buffer, TypedArray, DataView or object. Received ` + - key, - ); - error.code = "ERR_INVALID_ARG_TYPE"; - throw error; - } -} -crypto_exports.createPublicKey = _createPublicKey; crypto_exports.KeyObject = KeyObject; + +crypto_exports.generateKey = generateKey; +crypto_exports.generateKeySync = generateKeySync; +defineCustomPromisifyArgs(generateKeyPair, ["publicKey", "privateKey"]); +crypto_exports.generateKeyPair = generateKeyPair; +crypto_exports.generateKeyPairSync = generateKeyPairSync; + +crypto_exports.createSecretKey = createSecretKey; +crypto_exports.createPublicKey = createPublicKey; +crypto_exports.createPrivateKey = createPrivateKey; + var webcrypto = crypto; var _subtle = webcrypto.subtle; -// 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); -}; - -function doAsymmetricSign(key, message, operation, isEncrypt) { - // Our crypto bindings expect the key to be a `JSCryptoKey` property within an object. - const cryptoKey = toCryptoKey(key, isEncrypt); - const buffer = typeof message === "string" ? Buffer.from(message, key.encoding) : message; - return operation(cryptoKey, buffer, key.padding); -} - -crypto_exports.privateEncrypt = function (key, message) { - return doAsymmetricSign(key, message, privateEncrypt, false); -}; - -crypto_exports.publicDecrypt = function (key, message) { - return doAsymmetricSign(key, message, publicDecrypt, true); -}; - crypto_exports.hash = function hash(algorithm, input, outputEncoding = "hex") { return CryptoHasher.hash(algorithm, input, outputEncoding); }; @@ -500,9 +195,9 @@ function Sign(algorithm, options): void { this[kHandle] = new _Sign(); this[kHandle].init(algorithm); - StreamModule.Writable.$apply(this, [options]); + Writable.$apply(this, [options]); } -$toClass(Sign, "Sign", StreamModule.Writable); +$toClass(Sign, "Sign", Writable); Sign.prototype._write = function _write(chunk, encoding, callback) { this.update(chunk, encoding); @@ -535,9 +230,9 @@ function Verify(algorithm, options): void { this[kHandle] = new _Verify(); this[kHandle].init(algorithm); - StreamModule.Writable.$apply(this, [options]); + Writable.$apply(this, [options]); } -$toClass(Verify, "Verify", StreamModule.Writable); +$toClass(Verify, "Verify", Writable); Verify.prototype._write = Sign.prototype._write; Verify.prototype.update = Sign.prototype.update; @@ -663,47 +358,15 @@ for (const rng of ["pseudoRandomBytes", "prng", "rng"]) { }); } -export default crypto_exports; -/*! safe-buffer. MIT License. Feross Aboukhadijeh */ - function createDiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding) { - return new _DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding); + return new DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding); } -crypto_exports.DiffieHellmanGroup = _DiffieHellmanGroup; -crypto_exports.getDiffieHellman = crypto_exports.createDiffieHellmanGroup = _DiffieHellmanGroup; +crypto_exports.DiffieHellmanGroup = DiffieHellmanGroup; +crypto_exports.getDiffieHellman = crypto_exports.createDiffieHellmanGroup = DiffieHellmanGroup; crypto_exports.createDiffieHellman = createDiffieHellman; -crypto_exports.DiffieHellman = _DiffieHellman; +crypto_exports.DiffieHellman = DiffieHellman; -crypto_exports.diffieHellman = function diffieHellman(options) { - validateObject(options, "options"); - - const { privateKey, publicKey } = options; - - if (!(privateKey instanceof KeyObject)) { - throw $ERR_INVALID_ARG_VALUE("options.privateKey", privateKey); - } - - if (!(publicKey instanceof KeyObject)) { - throw $ERR_INVALID_ARG_VALUE("options.publicKey", publicKey); - } - - if (privateKey.type !== "private") { - throw $ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(privateKey.type, "private"); - } - - const publicKeyType = publicKey.type; - if (publicKeyType !== "public" && publicKeyType !== "private") { - throw $ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(publicKeyType, "private or public"); - } - - const privateType = privateKey.asymmetricKeyType; - const publicType = publicKey.asymmetricKeyType; - if (privateType !== publicType || !["dh", "ec", "x448", "x25519"].includes(privateType)) { - throw $ERR_CRYPTO_INCOMPATIBLE_KEY("key types for Diffie-Hellman", `${privateType} and ${publicType}`); - } - - return statelessDH(privateKey.$bunNativePtr, publicKey.$bunNativePtr); -}; +crypto_exports.diffieHellman = diffieHellman; crypto_exports.ECDH = ECDH; crypto_exports.createECDH = function createECDH(curve) { @@ -834,3 +497,10 @@ crypto_exports.createECDH = function createECDH(curve) { crypto_exports.scrypt = scrypt; crypto_exports.scryptSync = scryptSync; + +crypto_exports.publicEncrypt = publicEncrypt; +crypto_exports.publicDecrypt = publicDecrypt; +crypto_exports.privateEncrypt = privateEncrypt; +crypto_exports.privateDecrypt = privateDecrypt; + +export default crypto_exports; diff --git a/test/js/node/crypto/crypto-rsa.test.js b/test/js/node/crypto/crypto-rsa.test.js index 57b698aca8..3d360b096e 100644 --- a/test/js/node/crypto/crypto-rsa.test.js +++ b/test/js/node/crypto/crypto-rsa.test.js @@ -369,7 +369,7 @@ describe("Invalid oaepHash and oaepLabel options", () => { ); }).toThrow( expect.objectContaining({ - code: "ERR_CRYPTO_INVALID_DIGEST", + code: "ERR_OSSL_EVP_INVALID_DIGEST", }), ); diff --git a/test/js/node/crypto/crypto.hmac.test.ts b/test/js/node/crypto/crypto.hmac.test.ts index 785dca9c9c..86fa1b3d8c 100644 --- a/test/js/node/crypto/crypto.hmac.test.ts +++ b/test/js/node/crypto/crypto.hmac.test.ts @@ -51,7 +51,7 @@ describe("crypto.Hmac", () => { test("createHmac should throw when using invalid options", async () => { expect(() => createHmac(null)).toThrow('The "hmac" argument must be of type string. Received null'); expect(() => createHmac("sha1", null)).toThrow( - 'The "key" argument must be of type ArrayBuffer, Buffer, TypedArray, DataView, string, CryptoKey, or KeyObject. Received null', + 'The "key" argument must be of type string or an instance of ArrayBuffer, Buffer, TypedArray, DataView, KeyObject, or CryptoKey. Received null', ); }); diff --git a/test/js/node/crypto/crypto.key-objects.test.ts b/test/js/node/crypto/crypto.key-objects.test.ts index c26f6c2134..f24aeaf9b0 100644 --- a/test/js/node/crypto/crypto.key-objects.test.ts +++ b/test/js/node/crypto/crypto.key-objects.test.ts @@ -340,7 +340,7 @@ describe("crypto.KeyObjects", () => { type: "pkcs1", }); createPrivateKey({ key, format: "der", type: "pkcs1" }); - }).toThrow("Invalid use of PKCS#1 as private key"); + }).toThrow("error:06000066:public key routines:OPENSSL_internal:DECODE_ERROR"); }); [ @@ -489,6 +489,8 @@ describe("crypto.KeyObjects", () => { x: "AaLFgjwZtznM3N7qsfb86awVXe6c6djUYOob1FN-kllekv0KEXV0bwcDjPGQz5f6MxL" + "CbhMeHRavUS6P10rsTtBn", y: "Ad3flexBeAfXceNzRBH128kFbOWD6W41NjwKRqqIF26vmgW_8COldGKZjFkOSEASxPB" + "cvA2iFJRUyQ3whC00j0Np", }, + // same as node + exportedD: "ABIIbmn3Gm_Y11uIDkC3g2ijpRxIrJEBY4i_JJYo5OougzTl3BX2ifRluPJMaaHcNerbQH_WdVkLLX86ShlHrRyJ", }, ].forEach(info => { const { keyType, namedCurve } = info; @@ -503,7 +505,12 @@ describe("crypto.KeyObjects", () => { expect(key.symmetricKeySize).toBe(undefined); expect(key.export({ type: "pkcs8", format: "pem" })).toEqual(info.private); const jwt = key.export({ format: "jwk" }); - expect(jwt).toEqual(info.jwk); + if (info.exportedD) { + expect(jwt.d).toEqual(info.exportedD); + jwt.d = info.jwk.d; + } else { + expect(jwt).toEqual(info.jwk); + } }); test(`${keyType} ${namedCurve} createPrivateKey from jwk should work`, async () => { @@ -514,6 +521,10 @@ describe("crypto.KeyObjects", () => { expect(key.symmetricKeySize).toBe(undefined); expect(key.export({ type: "pkcs8", format: "pem" })).toEqual(info.private); const jwt = key.export({ format: "jwk" }); + if (info.exportedD) { + expect(jwt.d).toEqual(info.exportedD); + jwt.d = info.jwk.d; + } expect(jwt).toEqual(info.jwk); }); @@ -631,7 +642,7 @@ describe("crypto.KeyObjects", () => { type: "pkcs8", passphrase: "super-secret", }); - }).toThrow(/cipher is required when passphrase is specified/); + }).toThrow("The property 'options.cipher' is invalid. Received undefined"); }); test("secret export buffer format (default)", async () => { @@ -658,7 +669,9 @@ describe("crypto.KeyObjects", () => { expect(createSecretKey(first).equals(createSecretKey(first))).toBeTrue(); expect(createSecretKey(first).equals(createSecretKey(second))).toBeFalse(); - expect(() => keyObject.equals(0)).toThrow(/otherKey must be a KeyObject/); + expect(() => keyObject.equals(0)).toThrow( + /The "otherKeyObject" argument must be an instance of KeyObject. Received type number \(0\)/, + ); expect(keyObject.equals(keyObject)).toBeTrue(); expect(keyObject.equals(createPublicKey(publicPem))).toBeFalse(); @@ -1510,7 +1523,7 @@ describe("crypto.KeyObjects", () => { }); }); -test("RSA-PSS should work", async () => { +test.todo("RSA-PSS should work", async () => { // Test RSA-PSS. const expectedKeyDetails = { modulusLength: 2048, diff --git a/test/js/node/crypto/fixtures/rsa_private_pkcs8.pem b/test/js/node/crypto/fixtures/rsa_private_pkcs8.pem new file mode 100644 index 0000000000..cd274ae6d9 --- /dev/null +++ b/test/js/node/crypto/fixtures/rsa_private_pkcs8.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC33FiIiiexwLe/ +P8DZx5HsqFlmUO7/lvJ7necJVNwqdZ3ax5jpQB0p6uxfqeOvzcN3k5V7UFb/Am+n +kSNZMAZhsWzCU2Z4Pjh50QYz3f0Hour7/yIGStOLyYY3hgLK2K8TbhgjQPhdkw9+ +QtKlpvbL8fLgONAoGrVOFnRQGcr70iFffsm79mgZhKVMgYiHPJqJgGHvCtkGg9zM +gS7p63+Q3ZWedtFS2RhMX3uCBy/mH6EOlRCNBbRmA4xxNzyf5GQaki3T+Iz9tOMj +dPP+CwV2LqEdylmBuik8vrfTb3qIHLKKBAI8lXN26wWtA3kN4L7NP+cbKlCRlqct +vhmylLH1AgMBAAECggEBAJLZ6ti7yDKgY+LcT/NiBDqKyEUBlbMNZIW5vAPnBKbh +JIDO9WIv9Fs7qSpLbnFHnr0OYtGIfMPXtUiYkyw0QJSc+upHZMvbno4llpes0eHc +jWVTBWETON4oywvj/Kz53vRc9eiKhxVuVWyagNcQgYSprjzLA+9UTcWeB67Guyrf +8YJUE2LC23RiMA5nGYoSHfVRl0c75gj7A0X9nwpAI+xw3kcaVHRIhA6WowA3Pj1o +pK2t692+NLVRylpvMMSS4rziDexomFykCFukYWYB/kZOOSSETSsTWoMXXl1KqsoZ +8IW06NR4rXtIgQ3sTfbYKGZNF5nWFgZ+hJVx0We1Qg0CgYEA8UovlB4nrBm7xH+u +7XXBMbqxADQm5vaEZxw9eluc+tP7cIAI4sglMIvL/FMpbd2pEeP/BkR76NTDzzDu +PAZvUGRavgEjy0O9j2NAs/WPK4tZF+vFdunhnSh4EHAF4Ij9kbsUi90NOpbGfVqP +dOaHqzgHKoR23Cuusk9wFQ2XTV8CgYEAwxHdEYT9xrpfrHPqSBQPpO0dWGKJEkrW +Ob+76rSfuL8wGR4OBNmQdhLuU9zTIh22pog+XPnLPAecC+4yu/wtJ2SPCKiKDbJB +re0CKPyRfGqzvA3njXwMxXazU4kGs+2Fg+xu/iKbaIjxXrclBLhkxhBtySrwAFhx +xOk6fFcPLSsCgYEAqS/Mdr5CMRGGMH0bKhPUWEtAixUGZhJaunX5wY71Xoc/Gh4c +nO+b7BNJ/+5L8WZog0vr6PgiLhrqBaCYm2wjpyoG2o2wDHm+NAlzN/wp3G2EFhrS +xdOux+S1c0kpRcyoiAO2n29rNDa+jOzwBBcU8ACEPdLOCQl0IEFFJO33tl8CgYBY +DOIqnEsovsucvh3MNzHwkg8i7CdPGHSmUIN0J9/ItpPxYn2VdtccVOM6+3xZ8+uU +M/9iXGZ+TDkFsZk4/VUsaNmfYOQf1oyLA2ZsNcU90bQbeHNCi/H/19qOJFXgNaCE +sd5P3DMl9lptFGIjRVBHjvbfTQBUR5fi+BusMGfrTQKBgQCTtzMEJP2sef883AJr +XuGVPLzwLi9eTBvPzc5r5pfkvh7mDDmWFxHZm5kctvavqgy32uUPsQgMi1Kz67bU +s5dY9MCVrN2elhTLD8LOiAz8836o3AxFefm5cUWGaU/aZWDYR0QtNqFdyHyRaodo +JJfnfK+oK1Eq7+PvpXfVN9BkYw== +-----END PRIVATE KEY----- diff --git a/test/js/node/crypto/node-crypto.test.js b/test/js/node/crypto/node-crypto.test.js index 5e0f2e1943..689b16d711 100644 --- a/test/js/node/crypto/node-crypto.test.js +++ b/test/js/node/crypto/node-crypto.test.js @@ -712,3 +712,18 @@ it("cipher.setAAD should not throw if encoding or plaintextLength is undefined # }); }).not.toThrow(); }); + +it("generatePrime(Sync) should return an ArrayBuffer", async () => { + const prime = crypto.generatePrimeSync(512); + expect(prime).toBeInstanceOf(ArrayBuffer); + + const { promise, resolve } = Promise.withResolvers(); + + crypto.generatePrime(512, (err, prime) => { + expect(err).toBeUndefined(); + expect(prime).toBeInstanceOf(ArrayBuffer); + resolve(); + }); + + await promise; +}); diff --git a/test/js/node/test/parallel/test-crypto-key-objects.js b/test/js/node/test/parallel/test-crypto-key-objects.js index a596cfd2b2..967ab6c4e2 100644 --- a/test/js/node/test/parallel/test-crypto-key-objects.js +++ b/test/js/node/test/parallel/test-crypto-key-objects.js @@ -1,7 +1,3 @@ -/* -Skipped test -https://github.com/electron/electron/blob/5680c628b6718385bbd975b51ec2640aa7df226b/script/node-disabled-tests.json#L20 - 'use strict'; const common = require('../common'); @@ -306,11 +302,11 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', }, hasOpenSSL3 ? { message: 'error:1E08010C:DECODER routines::unsupported', } : { - message: 'error:0909006C:PEM routines:get_name:no start line', - code: 'ERR_OSSL_PEM_NO_START_LINE', - reason: 'no start line', + message: /error:(0909006C|0900006e):PEM routines:(get_name|OPENSSL_internal):(no start line|NO_START_LINE)/, + code: /ERR_OSSL_(PEM_)?NO_START_LINE/, + reason: /no start line|NO_START_LINE/, library: 'PEM routines', - function: 'get_name', + function: /get_name|OPENSSL_internal/, }); // This should not abort either: https://github.com/nodejs/node/issues/29904 @@ -333,12 +329,12 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', message: /error:1E08010C:DECODER routines::unsupported/, library: 'DECODER routines' } : { - message: /asn1 encoding/, - library: 'asn1 encoding routines' + message: /asn1 encoding|public key routines/, + library: /asn1 encoding routines|public key routines/ }); } -[ +const keyTypeTests = [ { private: fixtures.readKey('ed25519_private.pem', 'ascii'), public: fixtures.readKey('ed25519_public.pem', 'ascii'), keyType: 'ed25519', @@ -348,17 +344,6 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', d: 'wVK6M3SMhQh3NK-7GRrSV-BVWQx1FO5pW8hhQeu_NdA', kty: 'OKP' } }, - { private: fixtures.readKey('ed448_private.pem', 'ascii'), - public: fixtures.readKey('ed448_public.pem', 'ascii'), - keyType: 'ed448', - jwk: { - crv: 'Ed448', - x: 'oX_ee5-jlcU53-BbGRsGIzly0V-SZtJ_oGXY0udf84q2hTW2RdstLktvwpkVJOoNb7o' + - 'Dgc2V5ZUA', - d: '060Ke71sN0GpIc01nnGgMDkp0sFNQ09woVo4AM1ffax1-mjnakK0-p-S7-Xf859QewX' + - 'jcR9mxppY', - kty: 'OKP' - } }, { private: fixtures.readKey('x25519_private.pem', 'ascii'), public: fixtures.readKey('x25519_public.pem', 'ascii'), keyType: 'x25519', @@ -368,18 +353,36 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', d: 'mL_IWm55RrALUGRfJYzw40gEYWMvtRkesP9mj8o8Omc', kty: 'OKP' } }, - { private: fixtures.readKey('x448_private.pem', 'ascii'), - public: fixtures.readKey('x448_public.pem', 'ascii'), - keyType: 'x448', - jwk: { - crv: 'X448', - x: 'ioHSHVpTs6hMvghosEJDIR7ceFiE3-Xccxati64oOVJ7NWjfozE7ae31PXIUFq6cVYg' + - 'vSKsDFPA', - d: 'tMNtrO_q8dlY6Y4NDeSTxNQ5CACkHiPvmukidPnNIuX_EkcryLEXt_7i6j6YZMKsrWy' + - 'S0jlSYJk', - kty: 'OKP' - } }, -].forEach((info) => { +]; + +if (!common.openSSLIsBoringSSL) { + keyTypeTests.push( + { private: fixtures.readKey('ed448_private.pem', 'ascii'), + public: fixtures.readKey('ed448_public.pem', 'ascii'), + keyType: 'ed448', + jwk: { + crv: 'Ed448', + x: 'oX_ee5-jlcU53-BbGRsGIzly0V-SZtJ_oGXY0udf84q2hTW2RdstLktvwpkVJOoNb7o' + + 'Dgc2V5ZUA', + d: '060Ke71sN0GpIc01nnGgMDkp0sFNQ09woVo4AM1ffax1-mjnakK0-p-S7-Xf859QewX' + + 'jcR9mxppY', + kty: 'OKP' + } }, + { private: fixtures.readKey('x448_private.pem', 'ascii'), + public: fixtures.readKey('x448_public.pem', 'ascii'), + keyType: 'x448', + jwk: { + crv: 'X448', + x: 'ioHSHVpTs6hMvghosEJDIR7ceFiE3-Xccxati64oOVJ7NWjfozE7ae31PXIUFq6cVYg' + + 'vSKsDFPA', + d: 'tMNtrO_q8dlY6Y4NDeSTxNQ5CACkHiPvmukidPnNIuX_EkcryLEXt_7i6j6YZMKsrWy' + + 'S0jlSYJk', + kty: 'OKP' + } }, + ); +} + +for (const info of keyTypeTests) { const keyType = info.keyType; { @@ -419,9 +422,9 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', key.export({ format: 'jwk' }), jwk); } } -}); +} -[ +const namedCurveEcTests = [ { private: fixtures.readKey('ec_p256_private.pem', 'ascii'), public: fixtures.readKey('ec_p256_public.pem', 'ascii'), keyType: 'ec', @@ -433,17 +436,6 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', x: 'X0mMYR_uleZSIPjNztIkAS3_ud5LhNpbiIFp6fNf2Gs', y: 'UbJuPy2Xi0lW7UYTBxPK3yGgDu9EAKYIecjkHX5s2lI' } }, - { private: fixtures.readKey('ec_secp256k1_private.pem', 'ascii'), - public: fixtures.readKey('ec_secp256k1_public.pem', 'ascii'), - keyType: 'ec', - namedCurve: 'secp256k1', - jwk: { - crv: 'secp256k1', - d: 'c34ocwTwpFa9NZZh3l88qXyrkoYSxvC0FEsU5v1v4IM', - kty: 'EC', - x: 'cOzhFSpWxhalCbWNdP2H_yUkdC81C9T2deDpfxK7owA', - y: '-A3DAZTk9IPppN-f03JydgHaFvL1fAHaoXf4SX4NXyo' - } }, { private: fixtures.readKey('ec_p384_private.pem', 'ascii'), public: fixtures.readKey('ec_p384_public.pem', 'ascii'), keyType: 'ec', @@ -469,7 +461,25 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', y: 'Ad3flexBeAfXceNzRBH128kFbOWD6W41NjwKRqqIF26vmgW_8COldGKZjFkOSEASxPB' + 'cvA2iFJRUyQ3whC00j0Np' } }, -].forEach((info) => { +]; + +if (!common.openSSLIsBoringSSL) { + namedCurveEcTests.push( + { private: fixtures.readKey('ec_secp256k1_private.pem', 'ascii'), + public: fixtures.readKey('ec_secp256k1_public.pem', 'ascii'), + keyType: 'ec', + namedCurve: 'secp256k1', + jwk: { + crv: 'secp256k1', + d: 'c34ocwTwpFa9NZZh3l88qXyrkoYSxvC0FEsU5v1v4IM', + kty: 'EC', + x: 'cOzhFSpWxhalCbWNdP2H_yUkdC81C9T2deDpfxK7owA', + y: '-A3DAZTk9IPppN-f03JydgHaFvL1fAHaoXf4SX4NXyo' + } }, + ); +} + +for (const info of namedCurveEcTests) { const { keyType, namedCurve } = info; { @@ -512,7 +522,7 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', key.export({ format: 'jwk' }), jwk); } } -}); +} { // Reading an encrypted key without a passphrase should fail. @@ -533,7 +543,7 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', format: 'pem', passphrase: Buffer.alloc(1025, 'a') }), hasOpenSSL3 ? { name: 'Error' } : { - code: 'ERR_OSSL_PEM_BAD_PASSWORD_READ', + code: /ERR_OSSL_(PEM_)?BAD_PASSWORD_READ/, name: 'Error' }); @@ -544,7 +554,7 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', format: 'pem', passphrase: Buffer.alloc(1024, 'a') }), { - message: /bad decrypt/ + message: /bad decrypt|BAD_DECRYPT/ }); const publicKey = createPublicKey(publicDsa); @@ -570,7 +580,7 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', { // Test RSA-PSS. - { + if (!common.openSSLIsBoringSSL){ // This key pair does not restrict the message digest algorithm or salt // length. const publicPem = fixtures.readKey('rsa_pss_public_2048.pem'); @@ -631,7 +641,7 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', }); } - { + if (!common.openSSLIsBoringSSL) { // This key pair enforces sha1 as the message digest and the MGF1 // message digest and a salt length of 20 bytes. @@ -663,7 +673,7 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); } - { + if (!common.openSSLIsBoringSSL) { // This key pair enforces sha256 as the message digest and the MGF1 // message digest and a salt length of at least 16 bytes. const publicPem = @@ -710,7 +720,7 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', } } - { + if (!common.openSSLIsBoringSSL) { // This key enforces sha512 as the message digest and sha256 as the MGF1 // message digest. const publicPem = @@ -863,7 +873,7 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', { const first = generateKeyPairSync('ed25519'); - const second = generateKeyPairSync('ed448'); + const second = generateKeyPairSync(common.openSSLIsBoringSSL ? 'x25519' : 'ed448'); assert(!first.publicKey.equals(second.publicKey)); assert(!first.publicKey.equals(second.privateKey)); @@ -892,5 +902,3 @@ const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem', }, { code: 'ERR_INVALID_ARG_TYPE', message: /The "key\.key" property must be of type object/ }); } } - -*/ \ No newline at end of file diff --git a/test/js/node/test/parallel/test-crypto-keygen-async-dsa-key-object.js b/test/js/node/test/parallel/test-crypto-keygen-async-dsa-key-object.js new file mode 100644 index 0000000000..884451d8b9 --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-keygen-async-dsa-key-object.js @@ -0,0 +1,37 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); + +const { hasOpenSSL3 } = require('../common/crypto'); + +// Test async DSA key object generation. + +// BoringSSL does not support DSA key generation. +if (!common.openSSLIsBoringSSL) { + generateKeyPair('dsa', { + modulusLength: hasOpenSSL3 ? 2048 : 512, + divisorLength: 256 + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'dsa'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + modulusLength: hasOpenSSL3 ? 2048 : 512, + divisorLength: 256 + }); + + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'dsa'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + modulusLength: hasOpenSSL3 ? 2048 : 512, + divisorLength: 256 + }); + })); +} diff --git a/test/js/node/test/parallel/test-crypto-keygen-async-dsa.js b/test/js/node/test/parallel/test-crypto-keygen-async-dsa.js new file mode 100644 index 0000000000..935d9a0465 --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-keygen-async-dsa.js @@ -0,0 +1,68 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); +const { + assertApproximateSize, + testSignVerify, + spkiExp, +} = require('../common/crypto'); + +const { hasOpenSSL3 } = require('../common/crypto'); + +// Test async DSA key generation. + +// BoringSSL does not support DSA key generation. +if (!common.openSSLIsBoringSSL) { + const privateKeyEncoding = { + type: 'pkcs8', + format: 'der' + }; + + generateKeyPair('dsa', { + modulusLength: hasOpenSSL3 ? 2048 : 512, + divisorLength: 256, + publicKeyEncoding: { + type: 'spki', + format: 'pem' + }, + privateKeyEncoding: { + cipher: 'aes-128-cbc', + passphrase: 'secret', + ...privateKeyEncoding + } + }, common.mustSucceed((publicKey, privateKeyDER) => { + assert.strictEqual(typeof publicKey, 'string'); + assert.match(publicKey, spkiExp); + // The private key is DER-encoded. + assert(Buffer.isBuffer(privateKeyDER)); + + assertApproximateSize(publicKey, hasOpenSSL3 ? 1194 : 440); + assertApproximateSize(privateKeyDER, hasOpenSSL3 ? 721 : 336); + + // Since the private key is encrypted, signing shouldn't work anymore. + assert.throws(() => { + return testSignVerify(publicKey, { + key: privateKeyDER, + ...privateKeyEncoding + }); + }, { + name: 'TypeError', + code: 'ERR_MISSING_PASSPHRASE', + message: 'Passphrase required for encrypted key' + }); + + // Signing should work with the correct password. + testSignVerify(publicKey, { + key: privateKeyDER, + ...privateKeyEncoding, + passphrase: 'secret' + }); + })); +} diff --git a/test/js/node/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk-ec.js b/test/js/node/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk-ec.js new file mode 100644 index 0000000000..9877d4a2ca --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk-ec.js @@ -0,0 +1,37 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); + +// Test async elliptic curve key generation with 'jwk' encoding and named +// curve. +// BoringSSL does not support secp256k1. +// ['P-384', 'P-256', 'P-521', 'secp256k1'].forEach((curve) => { +['P-384', 'P-256', 'P-521'].forEach((curve) => { + generateKeyPair('ec', { + namedCurve: curve, + publicKeyEncoding: { + format: 'jwk' + }, + privateKeyEncoding: { + format: 'jwk' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'object'); + assert.strictEqual(typeof privateKey, 'object'); + assert.strictEqual(publicKey.x, privateKey.x); + assert.strictEqual(publicKey.y, privateKey.y); + assert(!publicKey.d); + assert(privateKey.d); + assert.strictEqual(publicKey.kty, 'EC'); + assert.strictEqual(publicKey.kty, privateKey.kty); + assert.strictEqual(publicKey.crv, curve); + assert.strictEqual(publicKey.crv, privateKey.crv); + })); +}); diff --git a/test/js/node/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk.js b/test/js/node/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk.js new file mode 100644 index 0000000000..27949f0483 --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-keygen-async-elliptic-curve-jwk.js @@ -0,0 +1,42 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); + +// Test async elliptic curve key generation with 'jwk' encoding. +{ + [ + 'ed25519', + // BoringSSL does not support ed448 key generation. + // 'ed448', + 'x25519', + // BoringSSL does not support x448 key generation. + // 'x448', + ].forEach((type) => { + generateKeyPair(type, { + publicKeyEncoding: { + format: 'jwk' + }, + privateKeyEncoding: { + format: 'jwk' + } + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(typeof publicKey, 'object'); + assert.strictEqual(typeof privateKey, 'object'); + assert.strictEqual(publicKey.x, privateKey.x); + assert(!publicKey.d); + assert(privateKey.d); + assert.strictEqual(publicKey.kty, 'OKP'); + assert.strictEqual(publicKey.kty, privateKey.kty); + const expectedCrv = `${type.charAt(0).toUpperCase()}${type.slice(1)}`; + assert.strictEqual(publicKey.crv, expectedCrv); + assert.strictEqual(publicKey.crv, privateKey.crv); + })); + }); +} diff --git a/test/js/node/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted-p256.js b/test/js/node/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted-p256.js index 55aa3831c4..e89cec7bf0 100644 --- a/test/js/node/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted-p256.js +++ b/test/js/node/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted-p256.js @@ -32,7 +32,12 @@ const { hasOpenSSL3 } = require('../common/crypto'); cipher: 'aes-128-cbc', passphrase: 'top secret' } - }, common.mustSucceed((publicKey, privateKey) => { + }, common.mustCall((err, publicKey, privateKey) => { + if (common.openSSLIsBoringSSL) { + // BoringSSL does not support 'explicit' param encoding. + assert.strictEqual(err.message, 'error:06000085:public key routines:OPENSSL_internal:INVALID_PARAMETERS') + return; + } assert.strictEqual(typeof publicKey, 'string'); assert.match(publicKey, spkiExp); assert.strictEqual(typeof privateKey, 'string'); diff --git a/test/js/node/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted.js.js b/test/js/node/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted.js.js index 8a55d4338b..21f3aa1b0d 100644 --- a/test/js/node/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted.js.js +++ b/test/js/node/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve-encrypted.js.js @@ -31,7 +31,14 @@ const { cipher: 'aes-128-cbc', passphrase: 'secret' } - }, common.mustSucceed((publicKey, privateKey) => { + }, common.mustCall((err, publicKey, privateKey) => { + if (common.openSSLIsBoringSSL) { + // BoringSSL does not support 'explicit' param encoding. + assert.strictEqual(err.message, 'error:06000085:public key routines:OPENSSL_internal:INVALID_PARAMETERS') + return; + } + + assert.ifError(err); assert.strictEqual(typeof publicKey, 'string'); assert.match(publicKey, spkiExp); assert.strictEqual(typeof privateKey, 'string'); diff --git a/test/js/node/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve.js b/test/js/node/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve.js index 46223f08d7..99fd450c3a 100644 --- a/test/js/node/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve.js +++ b/test/js/node/test/parallel/test-crypto-keygen-async-explicit-elliptic-curve.js @@ -28,7 +28,12 @@ const { type: 'sec1', format: 'pem' } - }, common.mustSucceed((publicKey, privateKey) => { + }, common.mustCall((err, publicKey, privateKey) => { + if (common.openSSLIsBoringSSL) { + // BoringSSL does not support 'explicit' param encoding. + assert.strictEqual(err.message, 'error:06000085:public key routines:OPENSSL_internal:INVALID_PARAMETERS') + return; + } assert.strictEqual(typeof publicKey, 'string'); assert.match(publicKey, spkiExp); assert.strictEqual(typeof privateKey, 'string'); diff --git a/test/js/node/test/parallel/test-crypto-keygen-bit-length.js b/test/js/node/test/parallel/test-crypto-keygen-bit-length.js new file mode 100644 index 0000000000..8846ede27e --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-keygen-bit-length.js @@ -0,0 +1,42 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); +const { hasOpenSSL3 } = require('../common/crypto'); + +// This tests check that generateKeyPair returns correct bit length in +// KeyObject's asymmetricKeyDetails. +// https://github.com/nodejs/node/issues/46102#issuecomment-1372153541 +{ + generateKeyPair('rsa', { + modulusLength: 513, + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(privateKey.asymmetricKeyDetails.modulusLength, common.openSSLIsBoringSSL ? 512 : 513); + assert.strictEqual(publicKey.asymmetricKeyDetails.modulusLength, common.openSSLIsBoringSSL ? 512 : 513); + })); + + if (!common.openSSLIsBoringSSL) { + generateKeyPair('rsa-pss', { + modulusLength: 513, + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(privateKey.asymmetricKeyDetails.modulusLength, 513); + assert.strictEqual(publicKey.asymmetricKeyDetails.modulusLength, 513); + })); + + if (hasOpenSSL3) { + generateKeyPair('dsa', { + modulusLength: 2049, + divisorLength: 256, + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(privateKey.asymmetricKeyDetails.modulusLength, 2049); + assert.strictEqual(publicKey.asymmetricKeyDetails.modulusLength, 2049); + })); + } + } +} diff --git a/test/js/node/test/parallel/test-crypto-keygen-dh-classic.js b/test/js/node/test/parallel/test-crypto-keygen-dh-classic.js new file mode 100644 index 0000000000..cef41965cb --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-keygen-dh-classic.js @@ -0,0 +1,28 @@ +/* +Skipped test. 'dh' key generation is not supported by BoringSSL. + +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); + +// Test classic Diffie-Hellman key generation. +{ + generateKeyPair('dh', { + primeLength: 512 + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'dh'); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'dh'); + })); +} + +*/ \ No newline at end of file diff --git a/test/js/node/test/parallel/test-crypto-keygen-duplicate-deprecated-option.js b/test/js/node/test/parallel/test-crypto-keygen-duplicate-deprecated-option.js new file mode 100644 index 0000000000..5040756b68 --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-keygen-duplicate-deprecated-option.js @@ -0,0 +1,45 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); + +// This test makes sure deprecated and new options may be used +// simultaneously so long as they're identical values. + +// BoringSSL does not support RSA-PSS key generation. +if (!common.openSSLIsBoringSSL) { + generateKeyPair('rsa-pss', { + modulusLength: 512, + saltLength: 16, + hash: 'sha256', + hashAlgorithm: 'sha256', + mgf1Hash: 'sha256', + mgf1HashAlgorithm: 'sha256' + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }); + })); +} diff --git a/test/js/node/test/parallel/test-crypto-keygen-invalid-parameter-encoding-dsa.js b/test/js/node/test/parallel/test-crypto-keygen-invalid-parameter-encoding-dsa.js new file mode 100644 index 0000000000..076c6d605b --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-keygen-invalid-parameter-encoding-dsa.js @@ -0,0 +1,30 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); + +const { + generateKeyPairSync, +} = require('crypto'); + +// Test invalid parameter encoding. + +// BoringSSL does not support DSA key generation. +if (!common.openSSLIsBoringSSL) { + assert.throws(() => generateKeyPairSync('dsa', { + modulusLength: 1024, + publicKeyEncoding: { + format: 'jwk' + }, + privateKeyEncoding: { + format: 'jwk' + } + }), { + name: 'Error', + code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE', + message: 'Unsupported JWK Key Type.' + }); +} diff --git a/test/js/node/test/parallel/test-crypto-keygen-no-rsassa-pss-params.js b/test/js/node/test/parallel/test-crypto-keygen-no-rsassa-pss-params.js new file mode 100644 index 0000000000..2cb23ba5d9 --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-keygen-no-rsassa-pss-params.js @@ -0,0 +1,37 @@ +// rsa-pss unsupported +/* + +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); + +// 'rsa-pss' should not add a RSASSA-PSS-params sequence by default. +// Regression test for: https://github.com/nodejs/node/issues/39936 +{ + generateKeyPair('rsa-pss', { + modulusLength: 512 + }, common.mustSucceed((publicKey, privateKey) => { + const expectedKeyDetails = { + modulusLength: 512, + publicExponent: 65537n + }; + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); + + // To allow backporting the fix to versions that do not support + // asymmetricKeyDetails for RSA-PSS params, also verify that the exported + // AlgorithmIdentifier member of the SubjectPublicKeyInfo has the expected + // length of 11 bytes (as opposed to > 11 bytes if node added params). + const spki = publicKey.export({ format: 'der', type: 'spki' }); + assert.strictEqual(spki[3], 11, spki.toString('hex')); + })); +} + +*/ diff --git a/test/js/node/test/parallel/test-crypto-keygen-rfc8017-9-1.js b/test/js/node/test/parallel/test-crypto-keygen-rfc8017-9-1.js new file mode 100644 index 0000000000..6395fed362 --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-keygen-rfc8017-9-1.js @@ -0,0 +1,34 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); + +// RFC 8017, 9.1.: "Assuming that the mask generation function is based on a +// hash function, it is RECOMMENDED that the hash function be the same as the +// one that is applied to the message." + +// BoringSSL does not support RSA-PSS key generation. +if (!common.openSSLIsBoringSSL) { + + generateKeyPair('rsa-pss', { + modulusLength: 512, + hashAlgorithm: 'sha256', + saltLength: 16 + }, common.mustSucceed((publicKey, privateKey) => { + const expectedKeyDetails = { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }; + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); + })); +} diff --git a/test/js/node/test/parallel/test-crypto-keygen-rfc8017-a-2-3.js b/test/js/node/test/parallel/test-crypto-keygen-rfc8017-a-2-3.js new file mode 100644 index 0000000000..9aeb3a56c5 --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-keygen-rfc8017-a-2-3.js @@ -0,0 +1,48 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPair, +} = require('crypto'); + +// RFC 8017, A.2.3.: "For a given hashAlgorithm, the default value of +// saltLength is the octet length of the hash value." + +// BoringSSL does not support RSA-PSS key generation. +if (!common.openSSLIsBoringSSL) { + generateKeyPair('rsa-pss', { + modulusLength: 512, + hashAlgorithm: 'sha512' + }, common.mustSucceed((publicKey, privateKey) => { + const expectedKeyDetails = { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha512', + mgf1HashAlgorithm: 'sha512', + saltLength: 64 + }; + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); + })); + + // It is still possible to explicitly set saltLength to 0. + generateKeyPair('rsa-pss', { + modulusLength: 512, + hashAlgorithm: 'sha512', + saltLength: 0 + }, common.mustSucceed((publicKey, privateKey) => { + const expectedKeyDetails = { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha512', + mgf1HashAlgorithm: 'sha512', + saltLength: 0 + }; + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); + })); +} diff --git a/test/js/node/test/parallel/test-crypto-keygen-rsa-pss.js b/test/js/node/test/parallel/test-crypto-keygen-rsa-pss.js new file mode 100644 index 0000000000..af0bba2216 --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-keygen-rsa-pss.js @@ -0,0 +1,66 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + constants, + generateKeyPair, +} = require('crypto'); +const { + testEncryptDecrypt, + testSignVerify, +} = require('../common/crypto'); + +// Test RSA-PSS. + +// BoringSSL does not support RSA-PSS key generation. +if (!common.openSSLIsBoringSSL) { + generateKeyPair('rsa-pss', { + modulusLength: 512, + saltLength: 16, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256' + }, common.mustSucceed((publicKey, privateKey) => { + assert.strictEqual(publicKey.type, 'public'); + assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }); + + assert.strictEqual(privateKey.type, 'private'); + assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + modulusLength: 512, + publicExponent: 65537n, + hashAlgorithm: 'sha256', + mgf1HashAlgorithm: 'sha256', + saltLength: 16 + }); + + // Unlike RSA, RSA-PSS does not allow encryption. + assert.throws(() => { + testEncryptDecrypt(publicKey, privateKey); + }, /operation not supported for this keytype/); + + // RSA-PSS also does not permit signing with PKCS1 padding. + assert.throws(() => { + testSignVerify({ + key: publicKey, + padding: constants.RSA_PKCS1_PADDING + }, { + key: privateKey, + padding: constants.RSA_PKCS1_PADDING + }); + }, /illegal or unsupported padding mode/); + + // The padding should correctly default to RSA_PKCS1_PSS_PADDING now. + testSignVerify(publicKey, privateKey); + })); +} diff --git a/test/js/node/test/parallel/test-crypto-private-decrypt-gh32240.js b/test/js/node/test/parallel/test-crypto-private-decrypt-gh32240.js new file mode 100644 index 0000000000..1ff5b565d6 --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-private-decrypt-gh32240.js @@ -0,0 +1,43 @@ +'use strict'; + +// Verify that privateDecrypt() does not leave an error on the +// openssl error stack that is visible to subsequent operations. + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { + generateKeyPairSync, + publicEncrypt, + privateDecrypt, +} = require('crypto'); + +const { hasOpenSSL3 } = require('../common/crypto'); + +const pair = generateKeyPairSync('rsa', { modulusLength: 512 }); + +const expected = Buffer.from('shibboleth'); +const encrypted = publicEncrypt(pair.publicKey, expected); + +const pkey = pair.privateKey.export({ type: 'pkcs1', format: 'pem' }); +const pkeyEncrypted = + pair.privateKey.export({ + type: 'pkcs1', + format: 'pem', + cipher: 'aes-128-cbc', + passphrase: 'secret', + }); + +function decrypt(key) { + const decrypted = privateDecrypt(key, encrypted); + assert.deepStrictEqual(decrypted, expected); +} + +decrypt(pkey); +assert.throws(() => decrypt(pkeyEncrypted), hasOpenSSL3 ? + { message: 'error:07880109:common libcrypto routines::interrupted or ' + + 'cancelled' } : + { code: 'ERR_MISSING_PASSPHRASE' }); +decrypt(pkey); // Should not throw. diff --git a/test/js/node/test/parallel/test-crypto-secret-keygen.js b/test/js/node/test/parallel/test-crypto-secret-keygen.js new file mode 100644 index 0000000000..0bd45f653e --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-secret-keygen.js @@ -0,0 +1,137 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); + +const { + generateKey, + generateKeySync +} = require('crypto'); + +[1, true, [], {}, Infinity, null, undefined].forEach((i) => { + assert.throws(() => generateKey(i, 1, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "type" argument must be / + }); + assert.throws(() => generateKeySync(i, 1), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "type" argument must be / + }); +}); + +['', true, [], null, undefined].forEach((i) => { + assert.throws(() => generateKey('aes', i, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options" argument must be / + }); + assert.throws(() => generateKeySync('aes', i), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options" argument must be / + }); +}); + +['', true, {}, [], null, undefined].forEach((length) => { + assert.throws(() => generateKey('hmac', { length }, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options\.length" property must be / + }); + assert.throws(() => generateKeySync('hmac', { length }), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options\.length" property must be / + }); +}); + +assert.throws(() => generateKey('aes', { length: 256 }), { + code: 'ERR_INVALID_ARG_TYPE' +}); + +assert.throws(() => generateKey('hmac', { length: -1 }, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE' +}); + +assert.throws(() => generateKey('hmac', { length: 4 }, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE' +}); + +assert.throws(() => generateKey('hmac', { length: 7 }, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE' +}); + +assert.throws( + () => generateKey('hmac', { length: 2 ** 31 }, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE' + }); + +assert.throws(() => generateKeySync('hmac', { length: -1 }), { + code: 'ERR_OUT_OF_RANGE' +}); + +assert.throws(() => generateKeySync('hmac', { length: 4 }), { + code: 'ERR_OUT_OF_RANGE' +}); + +assert.throws(() => generateKeySync('hmac', { length: 7 }), { + code: 'ERR_OUT_OF_RANGE' +}); + +assert.throws( + () => generateKeySync('hmac', { length: 2 ** 31 }), { + code: 'ERR_OUT_OF_RANGE' + }); + +assert.throws(() => generateKeySync('aes', { length: 123 }), { + code: 'ERR_INVALID_ARG_VALUE', + message: /The property 'options\.length' must be one of: 128, 192, 256/ +}); + +{ + const key = generateKeySync('aes', { length: 128 }); + assert(key); + const keybuf = key.export(); + assert.strictEqual(keybuf.byteLength, 128 / 8); + + generateKey('aes', { length: 128 }, common.mustSucceed((key) => { + assert(key); + const keybuf = key.export(); + assert.strictEqual(keybuf.byteLength, 128 / 8); + })); +} + +{ + const key = generateKeySync('aes', { length: 256 }); + assert(key); + const keybuf = key.export(); + assert.strictEqual(keybuf.byteLength, 256 / 8); + + generateKey('aes', { length: 256 }, common.mustSucceed((key) => { + assert(key); + const keybuf = key.export(); + assert.strictEqual(keybuf.byteLength, 256 / 8); + })); +} + +{ + const key = generateKeySync('hmac', { length: 123 }); + assert(key); + const keybuf = key.export(); + assert.strictEqual(keybuf.byteLength, Math.floor(123 / 8)); + + generateKey('hmac', { length: 123 }, common.mustSucceed((key) => { + assert(key); + const keybuf = key.export(); + assert.strictEqual(keybuf.byteLength, Math.floor(123 / 8)); + })); +} + +assert.throws( + () => generateKey('unknown', { length: 123 }, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_VALUE', + message: /The argument 'type' must be a supported key type/ + }); + +assert.throws(() => generateKeySync('unknown', { length: 123 }), { + code: 'ERR_INVALID_ARG_VALUE', + message: /The argument 'type' must be a supported key type/ +}); diff --git a/test/js/node/test/parallel/test-crypto-worker-thread.js b/test/js/node/test/parallel/test-crypto-worker-thread.js new file mode 100644 index 0000000000..0aebf5384e --- /dev/null +++ b/test/js/node/test/parallel/test-crypto-worker-thread.js @@ -0,0 +1,39 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +// Issue https://github.com/nodejs/node/issues/35263 +// Description: Test that passing keyobject to worker thread does not crash. +const { + generateKeySync, + generateKeyPairSync, +} = require('crypto'); +const { subtle } = globalThis.crypto; + +const assert = require('assert'); + +const { Worker, isMainThread, workerData } = require('worker_threads'); + +if (isMainThread) { + (async () => { + const secretKey = generateKeySync('aes', { length: 128 }); + const { publicKey, privateKey } = generateKeyPairSync('rsa', { + modulusLength: 1024 + }); + const cryptoKey = await subtle.generateKey( + { name: 'AES-CBC', length: 128 }, false, ['encrypt']); + + for (const key of [secretKey, publicKey, privateKey, cryptoKey]) { + new Worker(__filename, { workerData: key }); + } + })().then(common.mustCall()); +} else { + console.log(workerData); + assert.notDeepStrictEqual(workerData, {}); + if (workerData instanceof CryptoKey) { + assert.deepStrictEqual(structuredClone(workerData), workerData); + } else { + assert(workerData.equals(structuredClone(workerData))); + } +}