From 53ee2d77b2c1596a64fc949ee85cbc36c13b2c17 Mon Sep 17 00:00:00 2001 From: Ciro Spaciari Date: Fri, 24 Nov 2023 23:43:17 -0300 Subject: [PATCH] fix(crypto) oneshot Sign and Verify (#7256) * WIP * native oneshot sign * add native verify * fallback rsa to non-native * WIP der dsaEncoding * pass encoding * RSA-PSS padding and saltLength * oopies * improve RSA-PSS support * accepts hash identifiers like nodejs and add options.hashAlgorithm support * fix string check * tests * define hash for ECDSA * fix compilation --- src/bun.js/bindings/KeyObject.cpp | 597 +++++++++++++++++- src/bun.js/bindings/KeyObject.h | 3 + src/bun.js/bindings/ZigGlobalObject.cpp | 3 + .../bindings/webcrypto/CryptoAlgorithmECDSA.h | 4 +- .../webcrypto/CryptoAlgorithmECDSAOpenSSL.cpp | 90 ++- .../webcrypto/CryptoAlgorithmEcdsaParams.h | 8 +- .../webcrypto/CryptoAlgorithmEd25519.h | 5 +- .../bindings/webcrypto/CryptoAlgorithmHMAC.h | 3 + .../webcrypto/CryptoAlgorithmHMACOpenSSL.cpp | 24 + .../CryptoAlgorithmRSASSA_PKCS1_v1_5.h | 10 +- ...ryptoAlgorithmRSASSA_PKCS1_v1_5OpenSSL.cpp | 40 +- .../webcrypto/CryptoAlgorithmRSA_PSS.h | 9 +- .../CryptoAlgorithmRSA_PSSOpenSSL.cpp | 115 +++- .../webcrypto/CryptoAlgorithmRegistry.cpp | 3 +- .../webcrypto/CryptoAlgorithmRegistry.h | 5 + .../CryptoAlgorithmRegistryOpenSSL.cpp | 10 +- .../webcrypto/CryptoAlgorithmRsaPssParams.h | 2 + .../bindings/webcrypto/CryptoAlgorithmSHA1.h | 2 + .../webcrypto/CryptoAlgorithmSHA224.h | 2 + .../webcrypto/CryptoAlgorithmSHA256.h | 2 + .../webcrypto/CryptoAlgorithmSHA384.h | 2 + .../webcrypto/CryptoAlgorithmSHA512.h | 2 + src/js/node/crypto.js | 79 ++- .../js/node/crypto/crypto.key-objects.test.ts | 281 +++++---- 24 files changed, 1087 insertions(+), 214 deletions(-) diff --git a/src/bun.js/bindings/KeyObject.cpp b/src/bun.js/bindings/KeyObject.cpp index e097c2dfd2..7f24e50c54 100644 --- a/src/bun.js/bindings/KeyObject.cpp +++ b/src/bun.js/bindings/KeyObject.cpp @@ -42,7 +42,14 @@ #include #include #include "JSBuffer.h" - +#include "CryptoAlgorithmHMAC.h" +#include "CryptoAlgorithmEd25519.h" +#include "CryptoAlgorithmRSA_PSS.h" +#include "CryptoAlgorithmRSASSA_PKCS1_v1_5.h" +#include "CryptoAlgorithmECDSA.h" +#include "CryptoAlgorithmEcdsaParams.h" +#include "CryptoAlgorithmRsaPssParams.h" +#include "CryptoAlgorithmRegistry.h"; using namespace JSC; using namespace Bun; using JSGlobalObject @@ -1239,6 +1246,492 @@ JSC::EncodedJSValue KeyObject__createSecretKey(JSC::JSGlobalObject* lexicalGloba return JSValue::encode(JSC::jsUndefined()); } +static 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 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; + } + return Vector((uint8_t*)data, byteLength); + } + 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; + } + return Vector((uint8_t*)data, byteLength); + } + default: { + break; + } + } + return Exception { OperationError }; +} +JSC::EncodedJSValue KeyObject__Sign(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) +{ + auto count = callFrame->argumentCount(); + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (count < 3) { + JSC::throwTypeError(globalObject, scope, "sign requires 3 arguments"_s); + return JSC::JSValue::encode(JSC::JSValue {}); + } + + auto* key = jsDynamicCast(callFrame->argument(0)); + if (!key) { + // No JSCryptoKey instance + JSC::throwTypeError(globalObject, scope, "expected CryptoKey as first argument"_s); + return JSC::JSValue::encode(JSC::JSValue {}); + } + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + auto vectorData = buffer.releaseReturnValue(); + auto& wrapped = key->wrapped(); + auto key_type = wrapped.type(); + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + auto algorithm_str = algorithm.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, encodedJSValue()); + + auto identifier = CryptoAlgorithmRegistry::singleton().identifier(algorithm_str); + if (UNLIKELY(!identifier)) { + JSC::throwTypeError(globalObject, scope, "digest not allowed"_s); + return JSC::JSValue::encode(JSC::JSValue {}); + } + + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + } + } + + 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 size = resultData.size(); + auto* buffer = jsCast(JSValue::decode(JSBuffer__bufferFromLength(globalObject, size))); + if (size > 0) + memcpy(buffer->vector(), resultData.data(), size); + + 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 size = resultData.size(); + auto* buffer = jsCast(JSValue::decode(JSBuffer__bufferFromLength(globalObject, size))); + if (size > 0) + memcpy(buffer->vector(), resultData.data(), size); + + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + auto encoding_str = encoding.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, encodedJSValue()); + + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + } + } + 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 size = resultData.size(); + auto* buffer = jsCast(JSValue::decode(JSBuffer__bufferFromLength(globalObject, size))); + if (size > 0) + memcpy(buffer->vector(), resultData.data(), size); + + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + 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 size = resultData.size(); + auto* buffer = jsCast(JSValue::decode(JSBuffer__bufferFromLength(globalObject, size))); + if (size > 0) + memcpy(buffer->vector(), resultData.data(), size); + + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + 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 size = resultData.size(); + auto* buffer = jsCast(JSValue::decode(JSBuffer__bufferFromLength(globalObject, size))); + if (size > 0) + memcpy(buffer->vector(), resultData.data(), size); + + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + } + } + case CryptoKeyClass::AES: { + JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Sign not supported for AES key type"_s); + return JSC::JSValue::encode(JSC::JSValue {}); + } + case CryptoKeyClass::Raw: { + JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Sign not supported for Raw key type"_s); + return JSC::JSValue::encode(JSC::JSValue {}); + } + default: { + JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Sign not supported for this key type"_s); + return JSC::JSValue::encode(JSC::JSValue {}); + } + } +} + +JSC::EncodedJSValue KeyObject__Verify(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) +{ + auto count = callFrame->argumentCount(); + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (count < 4) { + JSC::throwTypeError(globalObject, scope, "verify requires 4 arguments"_s); + return JSC::JSValue::encode(JSC::JSValue {}); + } + + auto* key = jsDynamicCast(callFrame->argument(0)); + if (!key) { + // No JSCryptoKey instance + JSC::throwTypeError(globalObject, scope, "expected CryptoKey as first argument"_s); + return JSC::JSValue::encode(JSC::JSValue {}); + } + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + auto signatureData = signatureBuffer.releaseReturnValue(); + + auto& wrapped = key->wrapped(); + auto key_type = wrapped.type(); + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + auto algorithm_str = algorithm.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, encodedJSValue()); + + auto identifier = CryptoAlgorithmRegistry::singleton().identifier(algorithm_str); + if (UNLIKELY(!identifier)) { + JSC::throwTypeError(globalObject, scope, "digest not allowed"_s); + return JSC::JSValue::encode(JSC::JSValue {}); + } + + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + } + } + + 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()) { + WebCore::propagateException(*globalObject, scope, result.releaseException()); + 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()) { + WebCore::propagateException(*globalObject, scope, result.releaseException()); + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + auto encoding_str = encoding.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, encodedJSValue()); + + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + } + } + auto result = WebCore::CryptoAlgorithmECDSA::platformVerify(params, ec, signatureData, vectorData); + if (result.hasException()) { + WebCore::propagateException(*globalObject, scope, result.releaseException()); + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + 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()) { + WebCore::propagateException(*globalObject, scope, result.releaseException()); + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + 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()) { + WebCore::propagateException(*globalObject, scope, result.releaseException()); + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + } + } + case CryptoKeyClass::AES: { + JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Verify not supported for AES key type"_s); + return JSC::JSValue::encode(JSC::JSValue {}); + } + case CryptoKeyClass::Raw: { + JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Verify not supported for Raw key type"_s); + return JSC::JSValue::encode(JSC::JSValue {}); + } + default: { + JSC::throwTypeError(globalObject, scope, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE: Verify not supported for this key type"_s); + return JSC::JSValue::encode(JSC::JSValue {}); + } + } +} + JSC::EncodedJSValue KeyObject__Exports(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) { @@ -1316,11 +1809,16 @@ JSC::EncodedJSValue KeyObject__Exports(JSC::JSGlobalObject* globalObject, JSC::C 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 JSC::JSValue::encode(JSC::JSValue {}); + } 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); @@ -1329,6 +1827,12 @@ JSC::EncodedJSValue KeyObject__Exports(JSC::JSGlobalObject* globalObject, JSC::C type = typeJSValue.toWTFString(globalObject); RETURN_IF_EXCEPTION(scope, encodedJSValue()); } + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + } auto* bio = BIO_new(BIO_s_mem()); auto* rsaKey = rsa.platformKey(); @@ -2044,7 +2548,6 @@ JSC::EncodedJSValue KeyObject__generateKeyPairSync(JSC::JSGlobalObject* lexicalG Zig::GlobalObject* zigGlobalObject = reinterpret_cast(lexicalGlobalObject); auto* structure = zigGlobalObject->JSCryptoKeyStructure(); - // TODO: rsa-pss if (type_str == "rsa"_s) { if (count == 1) { JSC::throwTypeError(lexicalGlobalObject, scope, "options.modulusLength are required for rsa"_s); @@ -2068,6 +2571,54 @@ JSC::EncodedJSValue KeyObject__generateKeyPairSync(JSC::JSGlobalObject* lexicalG JSC::throwTypeError(lexicalGlobalObject, scope, "options.publicExponent is expected to be a number"_s); return JSC::JSValue::encode(JSC::JSValue {}); } + + 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 = [&]() { + 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((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 JSC::JSValue::encode(JSC::JSValue {}); + } + auto* options = jsDynamicCast(callFrame->argument(1)); + if (options == nullptr) { + JSC::throwTypeError(lexicalGlobalObject, scope, "options is expected to be a object"_s); + return JSC::JSValue::encode(JSC::JSValue {}); + } + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + 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 JSC::JSValue::encode(JSC::JSValue {}); + } uint8_t publicExponentArray[4]; publicExponentArray[0] = (uint8_t)(publicExponent >> 24); publicExponentArray[1] = (uint8_t)(publicExponent >> 16); @@ -2085,11 +2636,49 @@ JSC::EncodedJSValue KeyObject__generateKeyPairSync(JSC::JSGlobalObject* lexicalG 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 JSC::JSValue::encode(JSC::JSValue {}); + } + hasHash = true; + auto hashAlgo = hashAlgoJS.toWTFString(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, encodedJSValue()); + + auto identifier = CryptoAlgorithmRegistry::singleton().identifier(hashAlgo); + if (UNLIKELY(!identifier)) { + JSC::throwTypeError(lexicalGlobalObject, scope, "options.hashAlgorithm is invalid"_s); + return JSC::JSValue::encode(JSC::JSValue {}); + } + + 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 JSC::JSValue::encode(JSC::JSValue {}); + } + } + } + + auto saltLengthJS = options->getIfPropertyExists(lexicalGlobalObject, PropertyName(Identifier::fromString(vm, "hashAlgorithm"_s))); + auto failureCallback = [&]() { 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((uint8_t*)&publicExponentArray, 4), true, CryptoKeyUsageEncrypt | CryptoKeyUsageDecrypt, WTFMove(keyPairCallback), WTFMove(failureCallback), zigGlobalObject->scriptExecutionContext()); + CryptoKeyRSA::generatePair(CryptoAlgorithmIdentifier::RSA_PSS, hash, hasHash, modulusLength, Vector((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) { @@ -2152,7 +2741,7 @@ JSC::EncodedJSValue KeyObject__generateKeyPairSync(JSC::JSGlobalObject* lexicalG 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', 'ec', 'x25519' or 'ed25519'"_s)); + throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "algorithm should be 'rsa', 'rsa-pss', 'ec', 'x25519' or 'ed25519'"_s)); return JSValue::encode(JSC::jsUndefined()); } return JSValue::encode(JSC::jsUndefined()); diff --git a/src/bun.js/bindings/KeyObject.h b/src/bun.js/bindings/KeyObject.h index c9b172e3b7..bb28847ae9 100644 --- a/src/bun.js/bindings/KeyObject.h +++ b/src/bun.js/bindings/KeyObject.h @@ -15,4 +15,7 @@ JSC::EncodedJSValue KeyObject__createPublicKey(JSC::JSGlobalObject* lexicalGloba JSC::EncodedJSValue KeyObject__createPrivateKey(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC::EncodedJSValue KeyObject__generateKeySync(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC::EncodedJSValue KeyObject__generateKeyPairSync(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC::EncodedJSValue KeyObject__Sign(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame); +JSC::EncodedJSValue KeyObject__Verify(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame); + } \ No newline at end of file diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 5516cb716e..7a258ba524 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -1754,6 +1754,9 @@ JSC_DEFINE_HOST_FUNCTION(functionLazyLoad, 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); + return JSValue::encode(obj); } diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmECDSA.h b/src/bun.js/bindings/webcrypto/CryptoAlgorithmECDSA.h index b7c7889f8e..00687cd9a9 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmECDSA.h +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmECDSA.h @@ -40,6 +40,8 @@ public: static constexpr CryptoAlgorithmIdentifier s_identifier = CryptoAlgorithmIdentifier::ECDSA; static Ref create(); + static ExceptionOr> platformSign(const CryptoAlgorithmEcdsaParams&, const CryptoKeyEC&, const Vector&); + static ExceptionOr platformVerify(const CryptoAlgorithmEcdsaParams&, const CryptoKeyEC&, const Vector&, const Vector&); private: CryptoAlgorithmECDSA() = default; CryptoAlgorithmIdentifier identifier() const final; @@ -50,8 +52,6 @@ private: void importKey(CryptoKeyFormat, KeyData&&, const CryptoAlgorithmParameters&, bool extractable, CryptoKeyUsageBitmap, KeyCallback&&, ExceptionCallback&&) final; void exportKey(CryptoKeyFormat, Ref&&, KeyDataCallback&&, ExceptionCallback&&) final; - static ExceptionOr> platformSign(const CryptoAlgorithmEcdsaParams&, const CryptoKeyEC&, const Vector&); - static ExceptionOr platformVerify(const CryptoAlgorithmEcdsaParams&, const CryptoKeyEC&, const Vector&, const Vector&); }; } // namespace WebCore diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmECDSAOpenSSL.cpp b/src/bun.js/bindings/webcrypto/CryptoAlgorithmECDSAOpenSSL.cpp index 46aec1869e..de6f773f56 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmECDSAOpenSSL.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmECDSAOpenSSL.cpp @@ -56,46 +56,80 @@ ExceptionOr> CryptoAlgorithmECDSA::platformSign(const CryptoAlgo if (!sig) return Exception { OperationError }; - const BIGNUM* r; - const BIGNUM* s; - ECDSA_SIG_get0(sig.get(), &r, &s); + if (parameters.encoding == CryptoAlgorithmECDSAEncoding::DER) { + int derSigLength = i2d_ECDSA_SIG(sig.get(), nullptr); + if (derSigLength <= 0) + return Exception { OperationError }; + Vector signature(derSigLength); + uint8_t* p = signature.data(); + if(i2d_ECDSA_SIG(sig.get(), &p) != derSigLength) + return Exception { OperationError }; + return signature; + } else { - // Concatenate r and s, expanding r and s to keySizeInBytes. - Vector signature = convertToBytesExpand(r, keySizeInBytes); - signature.appendVector(convertToBytesExpand(s, keySizeInBytes)); + const BIGNUM* r; + const BIGNUM* s; + ECDSA_SIG_get0(sig.get(), &r, &s); - return signature; + // Concatenate r and s, expanding r and s to keySizeInBytes. + Vector signature = convertToBytesExpand(r, keySizeInBytes); + signature.appendVector(convertToBytesExpand(s, keySizeInBytes)); + return signature; + } } ExceptionOr CryptoAlgorithmECDSA::platformVerify(const CryptoAlgorithmEcdsaParams& parameters, const CryptoKeyEC& key, const Vector& signature, const Vector& data) { - size_t keySizeInBytes = (key.keySizeInBits() + 7) / 8; + if (parameters.encoding == CryptoAlgorithmECDSAEncoding::DER) { + const uint8_t* p = signature.data(); - // Bail if the signature size isn't double the key size (i.e. concatenated r and s components). - if (signature.size() != keySizeInBytes * 2) - return false; - - auto sig = ECDSASigPtr(ECDSA_SIG_new()); - auto r = BN_bin2bn(signature.data(), keySizeInBytes, nullptr); - auto s = BN_bin2bn(signature.data() + keySizeInBytes, keySizeInBytes, nullptr); + auto sig = ECDSASigPtr(d2i_ECDSA_SIG(nullptr, &p, signature.size())); + if (!sig) + return Exception { OperationError }; - if (!ECDSA_SIG_set0(sig.get(), r, s)) - return Exception { OperationError }; + const EVP_MD* md = digestAlgorithm(parameters.hashIdentifier); + if (!md) + return Exception { NotSupportedError }; - const EVP_MD* md = digestAlgorithm(parameters.hashIdentifier); - if (!md) - return Exception { NotSupportedError }; + std::optional> digest = calculateDigest(md, data); + if (!digest) + return Exception { OperationError }; - std::optional> digest = calculateDigest(md, data); - if (!digest) - return Exception { OperationError }; + EC_KEY* ecKey = EVP_PKEY_get0_EC_KEY(key.platformKey()); + if (!ecKey) + return Exception { OperationError }; - EC_KEY* ecKey = EVP_PKEY_get0_EC_KEY(key.platformKey()); - if (!ecKey) - return Exception { OperationError }; + int ret = ECDSA_do_verify(digest->data(), digest->size(), sig.get(), ecKey); + return ret == 1; + } else { + size_t keySizeInBytes = (key.keySizeInBits() + 7) / 8; - int ret = ECDSA_do_verify(digest->data(), digest->size(), sig.get(), ecKey); - return ret == 1; + // Bail if the signature size isn't double the key size (i.e. concatenated r and s components). + if (signature.size() != keySizeInBytes * 2) + return false; + + auto sig = ECDSASigPtr(ECDSA_SIG_new()); + auto r = BN_bin2bn(signature.data(), keySizeInBytes, nullptr); + auto s = BN_bin2bn(signature.data() + keySizeInBytes, keySizeInBytes, nullptr); + + if (!ECDSA_SIG_set0(sig.get(), r, s)) + return Exception { OperationError }; + + const EVP_MD* md = digestAlgorithm(parameters.hashIdentifier); + if (!md) + return Exception { NotSupportedError }; + + std::optional> digest = calculateDigest(md, data); + if (!digest) + return Exception { OperationError }; + + EC_KEY* ecKey = EVP_PKEY_get0_EC_KEY(key.platformKey()); + if (!ecKey) + return Exception { OperationError }; + + int ret = ECDSA_do_verify(digest->data(), digest->size(), sig.get(), ecKey); + return ret == 1; + } } } // namespace WebCore diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmEcdsaParams.h b/src/bun.js/bindings/webcrypto/CryptoAlgorithmEcdsaParams.h index e08de2802d..18155fdb2d 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmEcdsaParams.h +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmEcdsaParams.h @@ -34,11 +34,17 @@ namespace WebCore { +enum CryptoAlgorithmECDSAEncoding { + IeeeP1363, + DER, +}; class CryptoAlgorithmEcdsaParams final : public CryptoAlgorithmParameters { public: // FIXME: Consider merging hash and hashIdentifier. std::variant, String> hash; CryptoAlgorithmIdentifier hashIdentifier; + // WebCrypto default is IeeeP1363. + CryptoAlgorithmECDSAEncoding encoding { CryptoAlgorithmECDSAEncoding::IeeeP1363 }; Class parametersClass() const final { return Class::EcdsaParams; } @@ -47,7 +53,7 @@ public: CryptoAlgorithmEcdsaParams result; result.identifier = identifier; result.hashIdentifier = hashIdentifier; - + result.encoding = encoding; return result; } }; diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmEd25519.h b/src/bun.js/bindings/webcrypto/CryptoAlgorithmEd25519.h index b96d578317..9b2eb8cc64 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmEd25519.h +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmEd25519.h @@ -37,6 +37,8 @@ public: static constexpr CryptoAlgorithmIdentifier s_identifier = CryptoAlgorithmIdentifier::Ed25519; static Ref create(); + static ExceptionOr> platformSign(const CryptoKeyOKP&, const Vector&); + static ExceptionOr platformVerify(const CryptoKeyOKP&, const Vector&, const Vector&); private: CryptoAlgorithmEd25519() = default; CryptoAlgorithmIdentifier identifier() const final; @@ -46,9 +48,6 @@ private: void verify(const CryptoAlgorithmParameters&, Ref&&, Vector&& signature, Vector&&, BoolCallback&&, ExceptionCallback&&, ScriptExecutionContext&, WorkQueue&) final; void importKey(CryptoKeyFormat, KeyData&&, const CryptoAlgorithmParameters&, bool extractable, CryptoKeyUsageBitmap, KeyCallback&&, ExceptionCallback&&) final; void exportKey(CryptoKeyFormat, Ref&&, KeyDataCallback&&, ExceptionCallback&&) final; - - static ExceptionOr> platformSign(const CryptoKeyOKP&, const Vector&); - static ExceptionOr platformVerify(const CryptoKeyOKP&, const Vector&, const Vector&); }; inline CryptoAlgorithmIdentifier CryptoAlgorithmEd25519::identifier() const diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmHMAC.h b/src/bun.js/bindings/webcrypto/CryptoAlgorithmHMAC.h index 9f1084698a..22064797ae 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmHMAC.h +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmHMAC.h @@ -41,7 +41,10 @@ public: // Operations can be performed directly. static ExceptionOr> platformSign(const CryptoKeyHMAC&, const Vector&); + static ExceptionOr> platformSignWithAlgorithm(const CryptoKeyHMAC&, CryptoAlgorithmIdentifier, const Vector&); static ExceptionOr platformVerify(const CryptoKeyHMAC&, const Vector&, const Vector&); + static ExceptionOr platformVerifyWithAlgorithm(const CryptoKeyHMAC&, CryptoAlgorithmIdentifier, const Vector&, const Vector&); + private: CryptoAlgorithmHMAC() = default; diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmHMACOpenSSL.cpp b/src/bun.js/bindings/webcrypto/CryptoAlgorithmHMACOpenSSL.cpp index ad35cafe5a..c71751b360 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmHMACOpenSSL.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmHMACOpenSSL.cpp @@ -59,6 +59,18 @@ static std::optional> calculateSignature(const EVP_MD* algorithm return cipherText; } +ExceptionOr> CryptoAlgorithmHMAC::platformSignWithAlgorithm(const CryptoKeyHMAC& key, CryptoAlgorithmIdentifier algorithmIdentifier, const Vector& data) { + + auto algorithm = digestAlgorithm(algorithmIdentifier); + if (!algorithm) + return Exception { OperationError }; + + auto result = calculateSignature(algorithm, key.key(), data.data(), data.size()); + if (!result) + return Exception { OperationError }; + return WTFMove(*result); +} + ExceptionOr> CryptoAlgorithmHMAC::platformSign(const CryptoKeyHMAC& key, const Vector& data) { auto algorithm = digestAlgorithm(key.hashAlgorithmIdentifier()); @@ -71,6 +83,18 @@ ExceptionOr> CryptoAlgorithmHMAC::platformSign(const CryptoKeyHM return WTFMove(*result); } +ExceptionOr CryptoAlgorithmHMAC::platformVerifyWithAlgorithm(const CryptoKeyHMAC& key, CryptoAlgorithmIdentifier algorithmIdentifier, const Vector& signature, const Vector& data) { + + auto algorithm = digestAlgorithm(algorithmIdentifier); + if (!algorithm) + return Exception { OperationError }; + + auto expectedSignature = calculateSignature(algorithm, key.key(), data.data(), data.size()); + if (!expectedSignature) + return Exception { OperationError }; + // Using a constant time comparison to prevent timing attacks. + return signature.size() == expectedSignature->size() && !constantTimeMemcmp(expectedSignature->data(), signature.data(), expectedSignature->size()); +} ExceptionOr CryptoAlgorithmHMAC::platformVerify(const CryptoKeyHMAC& key, const Vector& signature, const Vector& data) { auto algorithm = digestAlgorithm(key.hashAlgorithmIdentifier()); diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSASSA_PKCS1_v1_5.h b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSASSA_PKCS1_v1_5.h index d185d3a4bd..a08f08854a 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSASSA_PKCS1_v1_5.h +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSASSA_PKCS1_v1_5.h @@ -39,6 +39,13 @@ public: static constexpr CryptoAlgorithmIdentifier s_identifier = CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5; static Ref create(); + static ExceptionOr> platformSign(const CryptoKeyRSA&, const Vector&); + static ExceptionOr> platformSignWithAlgorithm(const CryptoKeyRSA&, CryptoAlgorithmIdentifier, const Vector&); + + static ExceptionOr platformVerify(const CryptoKeyRSA&, const Vector&, const Vector&); + static ExceptionOr platformVerifyWithAlgorithm(const CryptoKeyRSA&, CryptoAlgorithmIdentifier, const Vector&, const Vector&); + + private: CryptoAlgorithmRSASSA_PKCS1_v1_5() = default; CryptoAlgorithmIdentifier identifier() const final; @@ -48,9 +55,6 @@ private: void generateKey(const CryptoAlgorithmParameters&, bool extractable, CryptoKeyUsageBitmap, KeyOrKeyPairCallback&&, ExceptionCallback&&, ScriptExecutionContext&) final; void importKey(CryptoKeyFormat, KeyData&&, const CryptoAlgorithmParameters&, bool extractable, CryptoKeyUsageBitmap, KeyCallback&&, ExceptionCallback&&) final; void exportKey(CryptoKeyFormat, Ref&&, KeyDataCallback&&, ExceptionCallback&&) final; - - static ExceptionOr> platformSign(const CryptoKeyRSA&, const Vector&); - static ExceptionOr platformVerify(const CryptoKeyRSA&, const Vector&, const Vector&); }; } // namespace WebCore diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSASSA_PKCS1_v1_5OpenSSL.cpp b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSASSA_PKCS1_v1_5OpenSSL.cpp index acfeee790f..f31585a3a4 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSASSA_PKCS1_v1_5OpenSSL.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSASSA_PKCS1_v1_5OpenSSL.cpp @@ -33,11 +33,8 @@ namespace WebCore { -ExceptionOr> CryptoAlgorithmRSASSA_PKCS1_v1_5::platformSign(const CryptoKeyRSA& key, const Vector& data) -{ - const EVP_MD* md = digestAlgorithm(key.hashAlgorithmIdentifier()); - if (!md) - return Exception { NotSupportedError }; + +static ExceptionOr> signWithEVP_MD(const CryptoKeyRSA& key, const EVP_MD* md, const Vector& data) { std::optional> digest = calculateDigest(md, data); if (!digest) @@ -68,12 +65,25 @@ ExceptionOr> CryptoAlgorithmRSASSA_PKCS1_v1_5::platformSign(cons return signature; } -ExceptionOr CryptoAlgorithmRSASSA_PKCS1_v1_5::platformVerify(const CryptoKeyRSA& key, const Vector& signature, const Vector& data) +ExceptionOr> CryptoAlgorithmRSASSA_PKCS1_v1_5::platformSignWithAlgorithm(const CryptoKeyRSA& key, CryptoAlgorithmIdentifier algorithm, const Vector& data) { + + const EVP_MD* md = digestAlgorithm(algorithm); + if (!md) + return Exception { NotSupportedError }; + + return signWithEVP_MD(key, md, data); +} +ExceptionOr> CryptoAlgorithmRSASSA_PKCS1_v1_5::platformSign(const CryptoKeyRSA& key, const Vector& data) { const EVP_MD* md = digestAlgorithm(key.hashAlgorithmIdentifier()); if (!md) return Exception { NotSupportedError }; + return signWithEVP_MD(key, md, data); +} + + +static ExceptionOr verifyWithEVP_MD(const CryptoKeyRSA& key, const EVP_MD* md, const Vector& signature, const Vector& data) { std::optional> digest = calculateDigest(md, data); if (!digest) return Exception { OperationError }; @@ -96,6 +106,24 @@ ExceptionOr CryptoAlgorithmRSASSA_PKCS1_v1_5::platformVerify(const CryptoK return ret == 1; } +ExceptionOr CryptoAlgorithmRSASSA_PKCS1_v1_5::platformVerifyWithAlgorithm(const CryptoKeyRSA& key, CryptoAlgorithmIdentifier algorithm, const Vector& signature, const Vector& data) { + const EVP_MD* md = digestAlgorithm(algorithm); + if (!md) + return Exception { NotSupportedError }; + + return verifyWithEVP_MD(key, md, signature, data); +} + + +ExceptionOr CryptoAlgorithmRSASSA_PKCS1_v1_5::platformVerify(const CryptoKeyRSA& key, const Vector& signature, const Vector& data) +{ + const EVP_MD* md = digestAlgorithm(key.hashAlgorithmIdentifier()); + if (!md) + return Exception { NotSupportedError }; + + return verifyWithEVP_MD(key, md, signature, data); +} + } // namespace WebCore #endif // ENABLE(WEB_CRYPTO) diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSA_PSS.h b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSA_PSS.h index b1f7f772ce..5b79811796 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSA_PSS.h +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSA_PSS.h @@ -40,6 +40,13 @@ public: static constexpr CryptoAlgorithmIdentifier s_identifier = CryptoAlgorithmIdentifier::RSA_PSS; static Ref create(); + static ExceptionOr> platformSign(const CryptoAlgorithmRsaPssParams&, const CryptoKeyRSA&, const Vector&); + static ExceptionOr> platformSignWithAlgorithm(const CryptoAlgorithmRsaPssParams&, CryptoAlgorithmIdentifier, const CryptoKeyRSA&, const Vector&); + + static ExceptionOr platformVerify(const CryptoAlgorithmRsaPssParams&, const CryptoKeyRSA&, const Vector&, const Vector&); + static ExceptionOr platformVerifyWithAlgorithm(const CryptoAlgorithmRsaPssParams&, CryptoAlgorithmIdentifier, const CryptoKeyRSA&, const Vector&, const Vector&); + + private: CryptoAlgorithmRSA_PSS() = default; CryptoAlgorithmIdentifier identifier() const final; @@ -50,8 +57,6 @@ private: void importKey(CryptoKeyFormat, KeyData&&, const CryptoAlgorithmParameters&, bool extractable, CryptoKeyUsageBitmap, KeyCallback&&, ExceptionCallback&&) final; void exportKey(CryptoKeyFormat, Ref&&, KeyDataCallback&&, ExceptionCallback&&) final; - static ExceptionOr> platformSign(const CryptoAlgorithmRsaPssParams&, const CryptoKeyRSA&, const Vector&); - static ExceptionOr platformVerify(const CryptoAlgorithmRsaPssParams&, const CryptoKeyRSA&, const Vector&, const Vector&); }; } // namespace WebCore diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSA_PSSOpenSSL.cpp b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSA_PSSOpenSSL.cpp index 149560a392..c768bc519c 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSA_PSSOpenSSL.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRSA_PSSOpenSSL.cpp @@ -34,13 +34,12 @@ namespace WebCore { -ExceptionOr> CryptoAlgorithmRSA_PSS::platformSign(const CryptoAlgorithmRsaPssParams& parameters, const CryptoKeyRSA& key, const Vector& data) +static ExceptionOr> signWithMD(const CryptoAlgorithmRsaPssParams& parameters, const CryptoKeyRSA& key, const Vector& data, const EVP_MD* md) { -#if 1 // defined(EVP_PKEY_CTX_set_rsa_pss_saltlen) && defined(EVP_PKEY_CTX_set_rsa_mgf1_md) - const EVP_MD* md = digestAlgorithm(key.hashAlgorithmIdentifier()); - if (!md) - return Exception { NotSupportedError }; - + auto padding = parameters.padding; + if(padding == 0) { + padding = RSA_PKCS1_PSS_PADDING; + } std::optional> digest = calculateDigest(md, data); if (!digest) return Exception { OperationError }; @@ -52,11 +51,13 @@ ExceptionOr> CryptoAlgorithmRSA_PSS::platformSign(const CryptoAl if (EVP_PKEY_sign_init(ctx.get()) <= 0) return Exception { OperationError }; - if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_PSS_PADDING) <= 0) + if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), padding) <= 0) return Exception { OperationError }; - if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx.get(), parameters.saltLength) <= 0) - return Exception { OperationError }; + if(padding == RSA_PKCS1_PSS_PADDING) { + if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx.get(), parameters.saltLength) <= 0) + return Exception { OperationError }; + } if (EVP_PKEY_CTX_set_signature_md(ctx.get(), md) <= 0) return Exception { OperationError }; @@ -74,11 +75,80 @@ ExceptionOr> CryptoAlgorithmRSA_PSS::platformSign(const CryptoAl signature.shrink(signatureLen); return signature; +} +ExceptionOr> CryptoAlgorithmRSA_PSS::platformSignWithAlgorithm(const CryptoAlgorithmRsaPssParams& parameters, CryptoAlgorithmIdentifier hash, const CryptoKeyRSA& key, const Vector& data) +{ +#if 1 // defined(EVP_PKEY_CTX_set_rsa_pss_saltlen) && defined(EVP_PKEY_CTX_set_rsa_mgf1_md) + const EVP_MD* md = digestAlgorithm(hash); + if (!md) + return Exception { NotSupportedError }; + + return signWithMD(parameters, key, data, md); #else return Exception { NotSupportedError }; #endif } + +ExceptionOr> CryptoAlgorithmRSA_PSS::platformSign(const CryptoAlgorithmRsaPssParams& parameters, const CryptoKeyRSA& key, const Vector& data) +{ +#if 1 // defined(EVP_PKEY_CTX_set_rsa_pss_saltlen) && defined(EVP_PKEY_CTX_set_rsa_mgf1_md) + const EVP_MD* md = digestAlgorithm(key.hashAlgorithmIdentifier()); + if (!md) + return Exception { NotSupportedError }; + + return signWithMD(parameters, key, data, md); +#else + return Exception { NotSupportedError }; +#endif +} + +static ExceptionOr verifyWithMD(const CryptoAlgorithmRsaPssParams& parameters, const CryptoKeyRSA& key, const Vector& signature, const Vector& data, const EVP_MD* md) +{ + auto padding = parameters.padding; + if(padding == 0) { + padding = RSA_PKCS1_PSS_PADDING; + } + + auto ctx = EvpPKeyCtxPtr(EVP_PKEY_CTX_new(key.platformKey(), nullptr)); + if (!ctx) + return Exception { OperationError }; + + if (EVP_PKEY_verify_init(ctx.get()) <= 0) + return Exception { OperationError }; + + if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), padding) <= 0) + return Exception { OperationError }; + + if(padding == RSA_PKCS1_PSS_PADDING) { + if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx.get(), parameters.saltLength) <= 0) + return Exception { OperationError }; + } + + if (EVP_PKEY_CTX_set_signature_md(ctx.get(), md) <= 0) + return Exception { OperationError }; + + if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx.get(), md) <= 0) + return Exception { OperationError }; + + std::optional> digest = calculateDigest(md, data); + if (!digest) + return Exception { OperationError }; + + int ret = EVP_PKEY_verify(ctx.get(), signature.data(), signature.size(), digest->data(), digest->size()); + + return ret == 1; +} +ExceptionOr CryptoAlgorithmRSA_PSS::platformVerifyWithAlgorithm(const CryptoAlgorithmRsaPssParams& parameters, CryptoAlgorithmIdentifier hash, const CryptoKeyRSA& key, const Vector& signature, const Vector& data) +{ + const EVP_MD* md = digestAlgorithm(hash); + if (!md) + return Exception { NotSupportedError }; + + return verifyWithMD(parameters, key, signature, data, md); + +} + ExceptionOr CryptoAlgorithmRSA_PSS::platformVerify(const CryptoAlgorithmRsaPssParams& parameters, const CryptoKeyRSA& key, const Vector& signature, const Vector& data) { #if 1 // defined(EVP_PKEY_CTX_set_rsa_pss_saltlen) && defined(EVP_PKEY_CTX_set_rsa_mgf1_md) @@ -86,32 +156,7 @@ ExceptionOr CryptoAlgorithmRSA_PSS::platformVerify(const CryptoAlgorithmRs if (!md) return Exception { NotSupportedError }; - std::optional> digest = calculateDigest(md, data); - if (!digest) - return Exception { OperationError }; - - auto ctx = EvpPKeyCtxPtr(EVP_PKEY_CTX_new(key.platformKey(), nullptr)); - if (!ctx) - return Exception { OperationError }; - - if (EVP_PKEY_verify_init(ctx.get()) <= 0) - return Exception { OperationError }; - - if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_PSS_PADDING) <= 0) - return Exception { OperationError }; - - if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx.get(), parameters.saltLength) <= 0) - return Exception { OperationError }; - - if (EVP_PKEY_CTX_set_signature_md(ctx.get(), md) <= 0) - return Exception { OperationError }; - - if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx.get(), md) <= 0) - return Exception { OperationError }; - - int ret = EVP_PKEY_verify(ctx.get(), signature.data(), signature.size(), digest->data(), digest->size()); - - return ret == 1; + return verifyWithMD(parameters, key, signature, data, md); #else return Exception { NotSupportedError }; #endif diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistry.cpp b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistry.cpp index 92c90fbe41..d8192bc920 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistry.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistry.cpp @@ -90,7 +90,8 @@ void CryptoAlgorithmRegistry::registerAlgorithm(const String& name, CryptoAlgori Locker locker { m_lock }; ASSERT(!m_identifiers.contains(name)); - ASSERT(!m_constructors.contains(static_cast(identifier))); + // hashs can contains 2 names (SHA-256 and SHA256) + // ASSERT(!m_constructors.contains(static_cast(identifier))); m_identifiers.add(name, identifier); m_constructors.add(static_cast(identifier), std::make_pair(name, constructor)); diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistry.h b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistry.h index 49d75b2afe..44506300dc 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistry.h +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistry.h @@ -60,6 +60,11 @@ private: { registerAlgorithm(AlgorithmClass::s_name, AlgorithmClass::s_identifier, AlgorithmClass::create); } + template void registerAlgorithmWithAlternativeName() + { + registerAlgorithm(AlgorithmClass::s_name, AlgorithmClass::s_identifier, AlgorithmClass::create); + registerAlgorithm(AlgorithmClass::s_alternative_name, AlgorithmClass::s_identifier, AlgorithmClass::create); + } void registerAlgorithm(const String& name, CryptoAlgorithmIdentifier, CryptoAlgorithmConstructor); diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistryOpenSSL.cpp b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistryOpenSSL.cpp index 8e588d8bf2..42dce3a002 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistryOpenSSL.cpp +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistryOpenSSL.cpp @@ -67,11 +67,11 @@ void CryptoAlgorithmRegistry::platformRegisterAlgorithms() registerAlgorithm(); registerAlgorithm(); registerAlgorithm(); - registerAlgorithm(); - registerAlgorithm(); - registerAlgorithm(); - registerAlgorithm(); - registerAlgorithm(); + registerAlgorithmWithAlternativeName(); + registerAlgorithmWithAlternativeName(); + registerAlgorithmWithAlternativeName(); + registerAlgorithmWithAlternativeName(); + registerAlgorithmWithAlternativeName(); registerAlgorithm(); } diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRsaPssParams.h b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRsaPssParams.h index 412f4ce053..a1cda97d5f 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmRsaPssParams.h +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmRsaPssParams.h @@ -34,6 +34,7 @@ namespace WebCore { class CryptoAlgorithmRsaPssParams final : public CryptoAlgorithmParameters { public: size_t saltLength; + size_t padding = 0; // 0 = default Class parametersClass() const final { return Class::RsaPssParams; } @@ -42,6 +43,7 @@ public: CryptoAlgorithmRsaPssParams result; result.identifier = identifier; result.saltLength = saltLength; + result.padding = padding; return result; } diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA1.h b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA1.h index 1fd775a813..7728da1d5e 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA1.h +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA1.h @@ -34,6 +34,8 @@ namespace WebCore { class CryptoAlgorithmSHA1 final : public CryptoAlgorithm { public: static constexpr ASCIILiteral s_name = "SHA-1"_s; + static constexpr ASCIILiteral s_alternative_name = "SHA1"_s; + static constexpr CryptoAlgorithmIdentifier s_identifier = CryptoAlgorithmIdentifier::SHA_1; static Ref create(); diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA224.h b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA224.h index 493e162ab3..d574706d47 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA224.h +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA224.h @@ -34,6 +34,8 @@ namespace WebCore { class CryptoAlgorithmSHA224 final : public CryptoAlgorithm { public: static constexpr ASCIILiteral s_name = "SHA-224"_s; + static constexpr ASCIILiteral s_alternative_name = "SHA224"_s; + static constexpr CryptoAlgorithmIdentifier s_identifier = CryptoAlgorithmIdentifier::SHA_224; static Ref create(); diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA256.h b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA256.h index f5a8543c82..c24db6a849 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA256.h +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA256.h @@ -34,6 +34,8 @@ namespace WebCore { class CryptoAlgorithmSHA256 final : public CryptoAlgorithm { public: static constexpr ASCIILiteral s_name = "SHA-256"_s; + static constexpr ASCIILiteral s_alternative_name = "SHA256"_s; + static const CryptoAlgorithmIdentifier s_identifier = CryptoAlgorithmIdentifier::SHA_256; static Ref create(); diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA384.h b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA384.h index e5bf2232ac..c6dac1a524 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA384.h +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA384.h @@ -34,6 +34,8 @@ namespace WebCore { class CryptoAlgorithmSHA384 final : public CryptoAlgorithm { public: static constexpr ASCIILiteral s_name = "SHA-384"_s; + static constexpr ASCIILiteral s_alternative_name = "SHA384"_s; + static constexpr CryptoAlgorithmIdentifier s_identifier = CryptoAlgorithmIdentifier::SHA_384; static Ref create(); diff --git a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA512.h b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA512.h index 05f98fc4a8..968dd4c4d6 100644 --- a/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA512.h +++ b/src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA512.h @@ -34,6 +34,8 @@ namespace WebCore { class CryptoAlgorithmSHA512 final : public CryptoAlgorithm { public: static constexpr ASCIILiteral s_name = "SHA-512"_s; + static constexpr ASCIILiteral s_alternative_name = "SHA512"_s; + static constexpr CryptoAlgorithmIdentifier s_identifier = CryptoAlgorithmIdentifier::SHA_512; static Ref create(); diff --git a/src/js/node/crypto.js b/src/js/node/crypto.js index 40d5d712c1..9ce1bca1c8 100644 --- a/src/js/node/crypto.js +++ b/src/js/node/crypto.js @@ -11291,7 +11291,6 @@ var require_sign = __commonJS({ curves = require_curves2(); function sign(hash, key, hashType, signType, tag) { var priv = parseKeys(getKeyFrom(key, "private")); - if (priv.curve) { if (signType !== "ecdsa" && signType !== "ecdsa/rsa") throw new Error("wrong private key type"); return ecSign(hash, priv); @@ -12018,7 +12017,6 @@ const harcoded_curves = [ "secp224r1", "prime256v1", "prime192v1", - "ed25519", "secp384r1", "secp521r1", ]; @@ -12037,6 +12035,8 @@ const { createPrivateKey, generateKeySync, generateKeyPairSync, + sign: nativeSign, + verify: nativeVerify, } = $lazy("internal/crypto"); const kCryptoKey = Symbol.for("::bunKeyObjectCryptoKey::"); @@ -12293,34 +12293,95 @@ function _createPublicKey(key) { } crypto_exports.createPublicKey = _createPublicKey; crypto_exports.KeyObject = KeyObject; +var webcrypto = crypto; +var _subtle = webcrypto.subtle; const _createSign = crypto_exports.createSign; -crypto_exports.sign = function (algorithm, data, key, encoding, callback) { + +crypto_exports.sign = function (algorithm, data, key, callback) { + // TODO: move this to native + var dsaEncoding, padding, saltLength; + // key must be a KeyObject + if (!(key instanceof KeyObject)) { + if ($isObject(key) && key.key) { + padding = key.padding; + saltLength = key.saltLength; + dsaEncoding = key.dsaEncoding; + } + if (key.key instanceof KeyObject) { + key = key.key; + } else { + key = _createPrivateKey(key); + } + } if (typeof callback === "function") { try { - const result = _createSign(algorithm).update(data, encoding).sign(key, encoding); + let result; + if (key.asymmetricKeyType === "rsa") { + // RSA-PSS is supported by native but other RSA algorithms are not + result = _createSign(algorithm || "sha256") + .update(data) + .sign(key); + } else { + result = nativeSign(key[kCryptoKey], data, algorithm, dsaEncoding, padding, saltLength); + } callback(null, result); } catch (err) { callback(err); } } else { - return _createSign(algorithm).update(data, encoding).sign(key, encoding); + if (key.asymmetricKeyType === "rsa") { + return _createSign(algorithm || "sha256") + .update(data) + .sign(key); + } else { + return nativeSign(key[kCryptoKey], data, algorithm, dsaEncoding, padding, saltLength); + } } }; const _createVerify = crypto_exports.createVerify; + crypto_exports.verify = function (algorithm, data, key, signature, callback) { + // TODO: move this to native + var dsaEncoding, padding, saltLength; + // key must be a KeyObject + if (!(key instanceof KeyObject)) { + if ($isObject(key) && key.key) { + padding = key.padding; + saltLength = key.saltLength; + dsaEncoding = key.dsaEncoding; + } + if (key.key instanceof KeyObject && key.key.type === "public") { + key = key.key; + } else { + key = _createPublicKey(key); + } + } if (typeof callback === "function") { try { - const result = _createVerify(algorithm).update(data).verify(key, signature); + let result; + if (key.asymmetricKeyType === "rsa") { + // RSA-PSS is supported by native but other RSA algorithms are not + result = _createVerify(algorithm || "sha256") + .update(data) + .verify(key, signature); + } else { + result = nativeVerify(key[kCryptoKey], data, signature, algorithm, dsaEncoding, padding, saltLength); + } callback(null, result); } catch (err) { callback(err); } } else { - return _createVerify(algorithm).update(data).verify(key, signature); + if (key.asymmetricKeyType === "rsa") { + return _createVerify(algorithm || "sha256") + .update(data) + .verify(key, signature); + } else { + return nativeVerify(key[kCryptoKey], data, signature, algorithm, dsaEncoding, padding, saltLength); + } } }; -var webcrypto = crypto; __export(crypto_exports, { DEFAULT_ENCODING: () => DEFAULT_ENCODING, getRandomValues: () => getRandomValues, @@ -12331,7 +12392,7 @@ __export(crypto_exports, { scryptSync: () => scryptSync, timingSafeEqual: () => timingSafeEqual, webcrypto: () => webcrypto, - subtle: () => webcrypto.subtle, + subtle: () => _subtle, }); export default crypto_exports; diff --git a/test/js/node/crypto/crypto.key-objects.test.ts b/test/js/node/crypto/crypto.key-objects.test.ts index b124ca4796..0598c25ba6 100644 --- a/test/js/node/crypto/crypto.key-objects.test.ts +++ b/test/js/node/crypto/crypto.key-objects.test.ts @@ -1430,7 +1430,6 @@ describe("crypto.KeyObjects", () => { } ); - assertApproximateSize(publicKeyDER, 74); const publicKey = { @@ -1476,122 +1475,105 @@ describe("crypto.KeyObjects", () => { }); }); -test.todo("RSA-PSS should work", async () => { +test("RSA-PSS should work", async () => { // Test RSA-PSS. + const expectedKeyDetails = { + modulusLength: 2048, + publicExponent: 65537n, + }; { - // This key pair does not restrict the message digest algorithm or salt - // length. - // const publicPem = fs.readFileSync(path.join(import.meta.dir, "fixtures", "rsa_pss_public_2048.pem"), "ascii"); - // const privatePem = fs.readFileSync(path.join(import.meta.dir, "fixtures", "rsa_pss_private_2048.pem"), "ascii"); - // const publicKey = createPublicKey(publicPem); - // const privateKey = createPrivateKey(privatePem); - // // Because no RSASSA-PSS-params appears in the PEM, no defaults should be - // // added for the PSS parameters. This is different from an empty - // // RSASSA-PSS-params sequence (see test below). - // const expectedKeyDetails = { - // modulusLength: 2048, - // publicExponent: 65537n, - // }; - // expect(publicKey.type).toBe("public"); - // expect(publicKey.asymmetricKeyType).toBe("rsa-pss"); - // expect(publicKey.asymmetricKeyDetails).toBe(expectedKeyDetails); - // expect(privateKey.type).toBe("private"); - // expect(privateKey.asymmetricKeyType).toBe("rsa-pss"); - // expect(privateKey.asymmetricKeyDetails).toBe(expectedKeyDetails); - // assert.throws( - // () => publicKey.export({ format: 'jwk' }), - // { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' }); - // assert.throws( - // () => privateKey.export({ format: 'jwk' }), - // { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' }); - // for (const key of [privatePem, privateKey]) { - // // Any algorithm should work. - // for (const algo of ['sha1', 'sha256']) { - // // Any salt length should work. - // for (const saltLength of [undefined, 8, 10, 12, 16, 18, 20]) { - // const signature = createSign(algo) - // .update('foo') - // .sign({ key, saltLength }); - // for (const pkey of [key, publicKey, publicPem]) { - // const okay = createVerify(algo) - // .update('foo') - // .verify({ key: pkey, saltLength }, signature); - // assert.ok(okay); - // } - // } - // } - // } - // // Exporting the key using PKCS#1 should not work since this would discard - // // any algorithm restrictions. - // assert.throws(() => { - // publicKey.export({ format: 'pem', type: 'pkcs1' }); - // }, { - // code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS' - // }); - // { - // // This key pair enforces sha1 as the message digest and the MGF1 - // // message digest and a salt length of 20 bytes. - // const publicPem = fixtures.readKey('rsa_pss_public_2048_sha1_sha1_20.pem'); - // const privatePem = - // fixtures.readKey('rsa_pss_private_2048_sha1_sha1_20.pem'); - // const publicKey = createPublicKey(publicPem); - // const privateKey = createPrivateKey(privatePem); - // // Unlike the previous key pair, this key pair contains an RSASSA-PSS-params - // // sequence. However, because all values in the RSASSA-PSS-params are set to - // // their defaults (see RFC 3447), the ASN.1 structure contains an empty - // // sequence. Node.js should add the default values to the key details. - // const expectedKeyDetails = { - // modulusLength: 2048, - // publicExponent: 65537n, - // hashAlgorithm: 'sha1', - // mgf1HashAlgorithm: 'sha1', - // saltLength: 20 - // }; - // assert.strictEqual(publicKey.type, 'public'); - // assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); - // assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails); - // assert.strictEqual(privateKey.type, 'private'); - // assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); - // assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails); - // } - // { - // // 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 = - // fixtures.readKey('rsa_pss_public_2048_sha256_sha256_16.pem'); - // const privatePem = - // fixtures.readKey('rsa_pss_private_2048_sha256_sha256_16.pem'); - // const publicKey = createPublicKey(publicPem); - // const privateKey = createPrivateKey(privatePem); - // assert.strictEqual(publicKey.type, 'public'); - // assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss'); - // assert.strictEqual(privateKey.type, 'private'); - // assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss'); - // for (const key of [privatePem, privateKey]) { - // // Signing with anything other than sha256 should fail. - // assert.throws(() => { - // createSign('sha1').sign(key); - // }, /digest not allowed/); - // // Signing with salt lengths less than 16 bytes should fail. - // for (const saltLength of [8, 10, 12]) { - // assert.throws(() => { - // createSign('sha1').sign({ key, saltLength }); - // }, /pss saltlen too small/); - // } - // // Signing with sha256 and appropriate salt lengths should work. - // for (const saltLength of [undefined, 16, 18, 20]) { - // const signature = createSign('sha256') - // .update('foo') - // .sign({ key, saltLength }); - // for (const pkey of [key, publicKey, publicPem]) { - // const okay = createVerify('sha256') - // .update('foo') - // .verify({ key: pkey, saltLength }, signature); - // assert.ok(okay); - // } - // } - // } - // } + const { privateKey, publicKey } = generateKeyPairSync("rsa-pss", { + modulusLength: 2048, + publicExponent: 65537, + }); + expect(publicKey.type).toBe("public"); + expect(publicKey.asymmetricKeyType).toBe("rsa-pss"); + expect(publicKey.asymmetricKeyDetails).toEqual(expectedKeyDetails); + expect(privateKey.type).toBe("private"); + expect(privateKey.asymmetricKeyType).toBe("rsa-pss"); + expect(privateKey.asymmetricKeyDetails).toEqual(expectedKeyDetails); + expect(() => publicKey.export({ format: "jwk" })).toThrow(/ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE/); + expect(() => privateKey.export({ format: "jwk" })).toThrow(/ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE/); + + for (const key of [privateKey]) { + // Any algorithm should work. + for (const algo of ["sha1", "sha256"]) { + // Any salt length should work. + for (const saltLength of [undefined, 8, 10, 12, 16, 18, 20]) { + const signature = sign(algo, Buffer.from("foo"), { key, saltLength }); + for (const pkey of [key, publicKey]) { + const okay = verify(algo, Buffer.from("foo"), { key: pkey, saltLength }, signature); + expect(okay).toBeTrue(); + } + } + } + } + // Exporting the key using PKCS#1 should not work since this would discard + // any algorithm restrictions. + expect(() => { + publicKey.export({ format: "pem", type: "pkcs1" }); + }).toThrow(/ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE/); + + { + // Unlike the previous key pair, this key pair contains an RSASSA-PSS-params + // sequence. However, because all values in the RSASSA-PSS-params are set to + // their defaults (see RFC 3447), the ASN.1 structure contains an empty + // sequence. Node.js should add the default values to the key details. + const { privateKey, publicKey } = generateKeyPairSync("rsa-pss", { + modulusLength: 2048, + publicExponent: 65537, + hashAlgorithm: "sha1", + mgf1HashAlgorithm: "sha1", + saltLength: 20, + }); + + expect(publicKey.type).toBe("public"); + expect(publicKey.asymmetricKeyType).toBe("rsa-pss"); + // RSA_get0_pss_params returns NULL. In OpenSSL, this function retries RSA-PSS + // parameters associated with |RSA| objects, but BoringSSL does not support + // the id-RSASSA-PSS key encoding. + // We expect only modulusLength and publicExponent to be present. + expect(publicKey.asymmetricKeyDetails).toEqual(expectedKeyDetails); + expect(privateKey.type).toBe("private"); + expect(privateKey.asymmetricKeyType).toBe("rsa-pss"); + } + { + // This key pair enforces sha256 as the message digest and the MGF1 + // message digest and a salt length of at least 16 bytes. + const { privateKey, publicKey } = generateKeyPairSync("rsa-pss", { + modulusLength: 2048, + publicExponent: 65537, + hashAlgorithm: "sha256", + saltLength: 16, + }); + expect(publicKey.type).toBe("public"); + expect(publicKey.asymmetricKeyType).toBe("rsa-pss"); + expect(privateKey.type).toBe("private"); + expect(privateKey.asymmetricKeyType).toBe("rsa-pss"); + for (const key of [privateKey]) { + // Signing with anything other than sha256 should fail. + expect(() => { + sign("sha1", Buffer.from("foo"), key); + }).toThrow(/digest not allowed/); + // Signing with salt lengths less than 16 bytes should fail. + // We don't enforce this yet because of BoringSSL's limitations. TODO: check this + // for (const saltLength of [8, 10, 12]) { + // expect(() => { + // createSign("sha1").sign({ key, saltLength }); + // }).toThrow(/pss saltlen too small/); + // } + // Signing with sha256 and appropriate salt lengths should work. + for (const saltLength of [undefined, 16, 18, 20]) { + const signature = sign("sha256", Buffer.from("foo"), { key, saltLength }); + for (const pkey of [key, publicKey]) { + const okay = verify("sha256", Buffer.from("foo"), { key: pkey, saltLength }, signature); + expect(okay).toBeTrue(); + } + } + } + } + + // TODO: check how to use MGF1 and saltLength using BoringSSL // { // // This key enforces sha512 as the message digest and sha256 as the MGF1 // // message digest. @@ -1641,3 +1623,72 @@ test.todo("RSA-PSS should work", async () => { // } } }); + +test("Ed25519 should work", async () => { + const { publicKey, privateKey } = generateKeyPairSync("ed25519"); + + expect(publicKey.type).toBe("public"); + expect(publicKey.asymmetricKeyType).toBe("ed25519"); + expect(publicKey.asymmetricKeyDetails).toEqual({ namedCurve: "Ed25519" }); + expect(privateKey.type).toBe("private"); + expect(privateKey.asymmetricKeyType).toBe("ed25519"); + expect(privateKey.asymmetricKeyDetails).toEqual({ namedCurve: "Ed25519" }); + + { + const signature = sign(undefined, Buffer.from("foo"), privateKey); + const okay = verify(undefined, Buffer.from("foo"), publicKey, signature); + expect(okay).toBeTrue(); + } +}); + +test("ECDSA should work", async () => { + const { publicKey, privateKey } = generateKeyPairSync("ec", { namedCurve: "prime256v1" }); + + expect(publicKey.type).toBe("public"); + expect(publicKey.asymmetricKeyType).toBe("ec"); + expect(publicKey.asymmetricKeyDetails).toEqual({ namedCurve: "prime256v1" }); + expect(privateKey.type).toBe("private"); + expect(privateKey.asymmetricKeyType).toBe("ec"); + expect(privateKey.asymmetricKeyDetails).toEqual({ namedCurve: "prime256v1" }); + + // default format (DER) + { + const signature = sign("sha256", Buffer.from("foo"), privateKey); + expect(signature.byteLength).not.toBe(64); + const okay = verify("sha256", Buffer.from("foo"), publicKey, signature); + expect(okay).toBeTrue(); + } + // IeeeP1363 format + { + const signature = sign("sha256", Buffer.from("foo"), { key: privateKey, dsaEncoding: "ieee-p1363" }); + expect(signature.byteLength).toBe(64); + + const okay = verify("sha256", Buffer.from("foo"), { key: publicKey, dsaEncoding: "ieee-p1363" }, signature); + expect(okay).toBeTrue(); + } + // DER format + { + const signature = sign("sha256", Buffer.from("foo"), { key: privateKey, dsaEncoding: "der" }); + expect(signature.byteLength).not.toBe(64); + + const okay = verify("sha256", Buffer.from("foo"), { key: publicKey, dsaEncoding: "der" }, signature); + expect(okay).toBeTrue(); + } + + expect(() => { + //@ts-ignore + sign("sha256", Buffer.from("foo"), { key: privateKey, dsaEncoding: "kjshdakjshd" }); + }).toThrow(/invalid dsaEncoding/); + + expect(() => { + const signature = sign("sha256", Buffer.from("foo"), privateKey); + //@ts-ignore + verify("sha256", Buffer.from("foo"), { key: publicKey, dsaEncoding: "ieee-p136" }, signature); + }).toThrow(/invalid dsaEncoding/); + + expect(() => { + //@ts-ignore + const signature = sign("sha256", Buffer.from("foo"), { key: privateKey, dsaEncoding: "ieee-p136" }); + verify("sha256", Buffer.from("foo"), { key: publicKey, dsaEncoding: "der" }, signature); + }).toThrow(/invalid dsaEncoding/); +});