Skip to content
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
c011038
webcrypto: match Node v26.3.0 error semantics and KeyUsage ordering
cirospaciari Jul 17, 2026
0100ab7
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 17, 2026
0ac6582
webcrypto: report Node's import and derive errors, drop the non-stand…
cirospaciari Jul 17, 2026
19a7b0d
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 17, 2026
18aadc0
test(fetch): fill body fixtures through a Uint8Array view
cirospaciari Jul 17, 2026
facf342
wasi: random_get should fill the requested window, not all of linear …
cirospaciari Jul 17, 2026
2ec69d0
wasi: random_get returns WASI_ESUCCESS, not bufLen
robobun Jul 17, 2026
968a771
Merge remote-tracking branch 'origin/main' into claude/webcrypto-node…
robobun Jul 17, 2026
31add41
style: trim two new comments to 3 lines
robobun Jul 17, 2026
2a5f4d7
webcrypto: check the throw scope after normalizeCryptoAlgorithmParame…
robobun Jun 28, 2026
a6ce715
webcrypto: hold a RefPtr across remove-then-reject in the wrapKey cal…
robobun Jun 28, 2026
39645f9
test: trim exception-scope describe comment to 3 lines
robobun Jul 17, 2026
cd10a4a
webcrypto: report key-type mismatch for OKP cross-curve imports
robobun Jul 17, 2026
22ec506
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 17, 2026
f2d7540
webcrypto: carry Node's empty-usages message to sibling sites, report…
robobun Jul 17, 2026
5e68bc0
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 17, 2026
4082e03
webcrypto: carry ECDH's 'key algorithm mismatch' to X25519 deriveBits
robobun Jul 17, 2026
a5e6385
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 17, 2026
9cb0754
webcrypto: carry 'Unsupported key usage' to RSA importKey, fix RSAES …
robobun Jul 17, 2026
066c563
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 17, 2026
781f06f
webcrypto: use 'an' before ECDSA/ECDH/Ed25519/X25519 in usage messages
robobun Jul 17, 2026
e239db7
webcrypto: add ChaCha20-Poly1305 and the raw-secret key format
cirospaciari Jul 20, 2026
a0e449c
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 20, 2026
06eb822
webcrypto: vendor test-webcrypto-cryptokey-hidden-slots from Node v26…
cirospaciari Jul 18, 2026
ad82e7a
test: quarantine worker-terminate ASAN crashes, matching main
cirospaciari Jul 20, 2026
cd20e69
test: widen mimalloc page-count bound for JSC heap noise, matching main
cirospaciari Jul 20, 2026
a47bc95
webcrypto: add ML-DSA and ML-KEM, the v26 SubtleCrypto surface, and K…
cirospaciari Jul 21, 2026
62fb33f
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 21, 2026
1cde5be
crypto: avoid bssl::ScopedCBB in marshalAsymmetricKey
cirospaciari Jul 21, 2026
ba2e030
crypto: replace remaining bssl::ScopedCBB uses with a shared marshal …
cirospaciari Jul 21, 2026
33d186e
Merge remote-tracking branch 'origin/main' into wt-conflicts-34838
cirospaciari Jul 21, 2026
722e53a
webcrypto: address review findings across the ChaCha20-Poly1305 and A…
cirospaciari Jul 22, 2026
72f1824
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 22, 2026
897b1aa
webcrypto: drain the AKP error queue in unwrapKey's inner import; mak…
cirospaciari Jul 23, 2026
167c012
webcrypto: dedupe the AKP import/export flows and finish the review r…
cirospaciari Jul 23, 2026
13e9164
webcrypto: share the usage-bitmap mapping with KeyObject; cover every…
cirospaciari Jul 23, 2026
5ce0b8c
webcrypto: test structuredClone DataCloneError and encapsulate result…
cirospaciari Jul 23, 2026
0754ddb
webcrypto: clone-guard test also covers ML-KEM keys
cirospaciari Jul 23, 2026
8c0eca6
webcrypto: node's type error for object keyData; coerce format once
cirospaciari Jul 23, 2026
5a38a32
webcrypto: single-pass HMAC JWK validation with node's member errors
cirospaciari Jul 23, 2026
78d9af6
webcrypto: drop the null-alg arms from the HMAC alg matcher
cirospaciari Jul 23, 2026
18d22af
webcrypto: order wrapKey's extractable check first; node's raw-import…
cirospaciari Jul 23, 2026
2a6b128
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 23, 2026
0447528
webcrypto: reject duplicate JWK key_ops for ChaCha20 and HMAC
cirospaciari Jul 23, 2026
031b0e2
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 23, 2026
7d33045
webcrypto: pick the importKey keyData converter by format
cirospaciari Jul 23, 2026
2bd652e
webcrypto: structured clone for ChaCha20-Poly1305 and ML-DSA/ML-KEM keys
cirospaciari Jul 23, 2026
731b81c
webcrypto: clear the BoringSSL queue when deserializing a corrupt AKP…
cirospaciari Jul 23, 2026
d894781
webcrypto: pin the undersized-decrypt message for AES-GCM too
cirospaciari Jul 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/jsc/bindings/AsymmetricKeyValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "ZigGlobalObject.h"
#include "webcrypto/JSCryptoKey.h"
#include "webcrypto/JSSubtleCrypto.h"
#include "webcrypto/CryptoKeyAKP.h"
#include "webcrypto/CryptoKeyOKP.h"
#include "webcrypto/CryptoKeyEC.h"
#include "webcrypto/CryptoKeyRSA.h"
Expand Down Expand Up @@ -101,6 +102,13 @@ AsymmetricKeyValue::AsymmetricKeyValue(WebCore::CryptoKey& cryptoKey)
case CryptoAlgorithmIdentifier::ECDH:
key = downcast<WebCore::CryptoKeyEC>(cryptoKey).platformKey();
break;
case CryptoAlgorithmIdentifier::ML_DSA_44:
case CryptoAlgorithmIdentifier::ML_DSA_65:
case CryptoAlgorithmIdentifier::ML_DSA_87:
case CryptoAlgorithmIdentifier::ML_KEM_768:
case CryptoAlgorithmIdentifier::ML_KEM_1024:
key = downcast<WebCore::CryptoKeyAKP>(cryptoKey).platformKey();
break;
case CryptoAlgorithmIdentifier::X25519:
case CryptoAlgorithmIdentifier::Ed25519: {
const auto& okpKey = downcast<WebCore::CryptoKeyOKP>(cryptoKey);
Expand Down Expand Up @@ -132,6 +140,7 @@ AsymmetricKeyValue::AsymmetricKeyValue(WebCore::CryptoKey& cryptoKey)
case CryptoAlgorithmIdentifier::SHA3_512:
case CryptoAlgorithmIdentifier::HKDF:
case CryptoAlgorithmIdentifier::PBKDF2:
case CryptoAlgorithmIdentifier::ChaCha20_Poly1305:
case CryptoAlgorithmIdentifier::None:
key = nullptr;
break;
Expand Down
18 changes: 15 additions & 3 deletions src/jsc/bindings/ZigGlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1659,7 +1659,16 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionDispatchEvent, (JSGlobalObject * lexicalGloba

JSC_DEFINE_CUSTOM_GETTER(getterSubtleCrypto, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
{
return JSValue::encode(static_cast<Zig::GlobalObject*>(lexicalGlobalObject)->subtleCrypto());
// Node brand-checks the receiver: Crypto.prototype.subtle on anything but
// the crypto global throws ERR_INVALID_THIS. Resolve through
// defaultGlobalObject so vm-context lexical globals still find the singleton.
auto* global = defaultGlobalObject(lexicalGlobalObject);
if (JSValue::decode(thisValue) != global->cryptoObject()) [[unlikely]] {
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
return Bun::throwError(lexicalGlobalObject, scope, Bun::ErrorCode::ERR_INVALID_THIS, "Value of \"this\" must be of type Crypto"_s);
}
return JSValue::encode(global->subtleCrypto());
}

extern "C" JSC::EncodedJSValue ExpectMatcherUtils_createSigleton(JSC::JSGlobalObject* lexicalGlobalObject);
Expand Down Expand Up @@ -2099,11 +2108,14 @@ void GlobalObject::finishCreation(VM& vm)
[](const Initializer<JSObject>& init) {
JSC::JSGlobalObject* globalObject = init.owner;
JSObject* crypto = JSValue::decode(CryptoObject__create(globalObject)).getObject();
crypto->putDirectCustomAccessor(
// Node defines `subtle` on Crypto.prototype with a brand check, not on
// the instance; the getter above enforces the brand.
JSObject* prototype = crypto->getPrototypeDirect().getObject();
prototype->putDirectCustomAccessor(
init.vm,
Identifier::fromString(init.vm, "subtle"_s),
JSC::CustomGetterSetter::create(init.vm, getterSubtleCrypto, setterSubtleCrypto),
PropertyAttribute::DontDelete | 0);
PropertyAttribute::DontDelete | PropertyAttribute::CustomAccessor);
Comment thread
cirospaciari marked this conversation as resolved.
Resolved

init.set(crypto);
});
Expand Down
32 changes: 16 additions & 16 deletions src/jsc/bindings/node/crypto/JSPrivateKeyObjectPrototype.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const JSC::ClassInfo JSPrivateKeyObjectPrototype::s_info = { "PrivateKeyObject"_
// (JSAsymmetricKeyObjectPrototype.cpp), matching Node's prototype chain.
static const JSC::HashTableValue JSPrivateKeyObjectPrototypeTableValues[] = {
{ "export"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsPrivateKeyObjectPrototype_export, 1 } },
// { "toCryptoKey"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsPrivateKeyObjectPrototype_toCryptoKey, 3 } },
{ "toCryptoKey"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsPrivateKeyObjectPrototype_toCryptoKey, 3 } },
};

void JSPrivateKeyObjectPrototype::finishCreation(JSC::VM& vm)
Expand Down Expand Up @@ -50,21 +50,21 @@ JSC_DEFINE_HOST_FUNCTION(jsPrivateKeyObjectPrototype_export, (JSGlobalObject * g
return JSValue::encode(handle.exportAsymmetric(globalObject, scope, optionsValue, CryptoKeyType::Private));
}

// JSC_DEFINE_HOST_FUNCTION(jsPrivateKeyObjectPrototype_toCryptoKey, (JSGlobalObject * globalObject, CallFrame* callFrame))
// {
// VM& vm = globalObject->vm();
// ThrowScope scope = DECLARE_THROW_SCOPE(vm);
JSC_DEFINE_HOST_FUNCTION(jsPrivateKeyObjectPrototype_toCryptoKey, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
ThrowScope scope = DECLARE_THROW_SCOPE(vm);

// JSPrivateKeyObject* privateKeyObject = dynamicDowncast<JSPrivateKeyObject>(callFrame->thisValue());
// if (!privateKeyObject) {
// throwThisTypeError(*globalObject, scope, "PrivateKeyObject"_s, "toCryptoKey"_s);
// return {};
// }
JSPrivateKeyObject* privateKeyObject = dynamicDowncast<JSPrivateKeyObject>(callFrame->thisValue());
if (!privateKeyObject) {
throwThisTypeError(*globalObject, scope, "PrivateKeyObject"_s, "toCryptoKey"_s);
return {};
}

// KeyObject& handle = privateKeyObject->handle();
// JSValue algorithmValue = callFrame->argument(0);
// JSValue extractableValue = callFrame->argument(1);
// JSValue keyUsagesValue = callFrame->argument(2);
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));
// }
return JSValue::encode(handle.toCryptoKey(globalObject, scope, algorithmValue, extractableValue, keyUsagesValue));
}
32 changes: 16 additions & 16 deletions src/jsc/bindings/node/crypto/JSPublicKeyObjectPrototype.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const JSC::ClassInfo JSPublicKeyObjectPrototype::s_info = { "PublicKeyObject"_s,
// (JSAsymmetricKeyObjectPrototype.cpp), matching Node's prototype chain.
static const JSC::HashTableValue JSPublicKeyObjectPrototypeTableValues[] = {
{ "export"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsPublicKeyObjectPrototype_export, 1 } },
// { "toCryptoKey"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsPublicKeyObjectPrototype_toCryptoKey, 3 } },
{ "toCryptoKey"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsPublicKeyObjectPrototype_toCryptoKey, 3 } },
};

void JSPublicKeyObjectPrototype::finishCreation(JSC::VM& vm)
Expand Down Expand Up @@ -50,21 +50,21 @@ JSC_DEFINE_HOST_FUNCTION(jsPublicKeyObjectPrototype_export, (JSGlobalObject * gl
return JSValue::encode(handle.exportAsymmetric(globalObject, scope, optionsValue, CryptoKeyType::Public));
}

// JSC_DEFINE_HOST_FUNCTION(jsPublicKeyObjectPrototype_toCryptoKey, (JSGlobalObject * globalObject, CallFrame* callFrame))
// {
// VM& vm = globalObject->vm();
// ThrowScope scope = DECLARE_THROW_SCOPE(vm);
JSC_DEFINE_HOST_FUNCTION(jsPublicKeyObjectPrototype_toCryptoKey, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
ThrowScope scope = DECLARE_THROW_SCOPE(vm);

// JSPublicKeyObject* publicKeyObject = dynamicDowncast<JSPublicKeyObject>(callFrame->thisValue());
// if (!publicKeyObject) {
// throwThisTypeError(*globalObject, scope, "PublicKeyObject"_s, "toCryptoKey"_s);
// return {};
// }
JSPublicKeyObject* publicKeyObject = dynamicDowncast<JSPublicKeyObject>(callFrame->thisValue());
if (!publicKeyObject) {
throwThisTypeError(*globalObject, scope, "PublicKeyObject"_s, "toCryptoKey"_s);
return {};
}

// KeyObject& handle = publicKeyObject->handle();
// JSValue algorithmValue = callFrame->argument(0);
// JSValue extractableValue = callFrame->argument(1);
// JSValue keyUsagesValue = callFrame->argument(2);
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));
// }
return JSValue::encode(handle.toCryptoKey(globalObject, scope, algorithmValue, extractableValue, keyUsagesValue));
}
32 changes: 16 additions & 16 deletions src/jsc/bindings/node/crypto/JSSecretKeyObjectPrototype.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const JSC::ClassInfo JSSecretKeyObjectPrototype::s_info = { "SecretKeyObject"_s,
static const JSC::HashTableValue JSSecretKeyObjectPrototypeTableValues[] = {
{ "export"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSecretKeyObjectExport, 1 } },
{ "symmetricKeySize"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::GetterSetterType, jsSecretKeyObjectSymmetricKeySize, 0 } },
// { "toCryptoKey"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSecretKeyObjectToCryptoKey, 3 } },
{ "toCryptoKey"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSecretKeyObjectToCryptoKey, 3 } },
};

void JSSecretKeyObjectPrototype::finishCreation(JSC::VM& vm)
Expand Down Expand Up @@ -69,21 +69,21 @@ JSC_DEFINE_CUSTOM_GETTER(jsSecretKeyObjectSymmetricKeySize, (JSGlobalObject * gl
return JSValue::encode(jsNumber(symmetricKeySize));
}

// JSC_DEFINE_HOST_FUNCTION(jsSecretKeyObjectToCryptoKey, (JSGlobalObject * globalObject, CallFrame* callFrame))
// {
// VM& vm = globalObject->vm();
// ThrowScope scope = DECLARE_THROW_SCOPE(vm);
JSC_DEFINE_HOST_FUNCTION(jsSecretKeyObjectToCryptoKey, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
ThrowScope scope = DECLARE_THROW_SCOPE(vm);

// JSSecretKeyObject* secretKeyObject = dynamicDowncast<JSSecretKeyObject>(callFrame->thisValue());
// if (!secretKeyObject) {
// throwThisTypeError(*globalObject, scope, "SecretKeyObject"_s, "toCryptoKey"_s);
// return {};
// }
JSSecretKeyObject* secretKeyObject = dynamicDowncast<JSSecretKeyObject>(callFrame->thisValue());
if (!secretKeyObject) {
throwThisTypeError(*globalObject, scope, "SecretKeyObject"_s, "toCryptoKey"_s);
return {};
}

// KeyObject& handle = secretKeyObject->handle();
// JSValue algorithmValue = callFrame->argument(0);
// JSValue extractableValue = callFrame->argument(1);
// JSValue keyUsagesValue = callFrame->argument(2);
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));
// }
return JSValue::encode(handle.toCryptoKey(globalObject, scope, algorithmValue, extractableValue, keyUsagesValue));
}
143 changes: 141 additions & 2 deletions src/jsc/bindings/node/crypto/KeyObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,26 @@
#include "ErrorCode.h"
#include "NodeValidator.h"
#include "AsymmetricKeyValue.h"
#include "CryptoAlgorithm.h"
#include "CryptoAlgorithmParameters.h"
#include "CryptoAlgorithmRegistry.h"
#include "CryptoKeyAES.h"
#include "CryptoKeyAKP.h"
#include "CryptoKeyHMAC.h"
#include "CryptoKeyRaw.h"
#include "CryptoKey.h"
#include "CryptoKeyType.h"
#include "JSCryptoKey.h"
#include "JSCryptoKeyUsage.h"
#include "SubtleCrypto.h"
#include "JSDOMConvertInterface.h"
#include "JSDOMConvertObject.h"
#include "JSDOMConvertSequences.h"
#include "JSDOMConvertStrings.h"
#include "JSDOMConvertUnion.h"
#include "JSDOMConvertEnumeration.h"
#include "JSDOMExceptionHandling.h"
#include "OpenSSLUtilities.h"
#include "CryptoGenKeyPair.h"
#include "JSBuffer.h"
#include "BunString.h"
Expand Down Expand Up @@ -897,9 +911,134 @@ std::optional<bool> KeyObject::equals(const KeyObject& other) const
}
}

JSValue KeyObject::toCryptoKey(JSGlobalObject* globalObject, ThrowScope& scope, JSValue algorithmValue, JSValue extractableValue, JSValue keyUsagesValue)
static std::optional<Vector<uint8_t>> marshalAsymmetricKey(const ncrypto::EVPKeyPointer& pkey, bool isPublic)
{
return jsUndefined();
return WebCore::marshalEVPKey(pkey.get(), isPublic);
}

// KeyObject.prototype.toCryptoKey, following Node's per-algorithm dispatch in
// lib/internal/crypto/keys.js.
JSValue KeyObject::toCryptoKey(JSGlobalObject* lexicalGlobalObject, ThrowScope& scope, JSValue algorithmValue, JSValue extractableValue, JSValue keyUsagesValue)
{
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);

auto algorithm = WebCore::convert<WebCore::IDLUnion<WebCore::IDLObject, WebCore::IDLDOMString>>(*lexicalGlobalObject, algorithmValue);
RETURN_IF_EXCEPTION(scope, {});
bool extractable = extractableValue.toBoolean(lexicalGlobalObject);
auto keyUsages = WebCore::convert<WebCore::IDLSequence<WebCore::IDLEnumeration<WebCore::CryptoKeyUsage>>>(*lexicalGlobalObject, keyUsagesValue);
RETURN_IF_EXCEPTION(scope, {});
auto usagesBitmap = WebCore::SubtleCrypto::toCryptoKeyUsageBitmap(keyUsages);

auto throwDOMException = [&](WebCore::ExceptionCode code, const String& message) -> JSValue {
WebCore::propagateException(*lexicalGlobalObject, scope, WebCore::Exception { code, message });
return {};
};

auto paramsOrException = WebCore::SubtleCrypto::normalizeImportParameters(*lexicalGlobalObject, WTF::move(algorithm));
RETURN_IF_EXCEPTION(scope, {});
if (paramsOrException.hasException()) {
WebCore::propagateException(*lexicalGlobalObject, scope, paramsOrException.releaseException());
return {};
}
auto params = paramsOrException.releaseReturnValue();
auto identifier = params->identifier;

RefPtr<WebCore::CryptoKey> result;
std::optional<WebCore::ExceptionCode> failureCode;
String failureMessage;
auto keyCallback = [&](WebCore::CryptoKey& key) { result = &key; };
auto exceptionCallback = [&](WebCore::ExceptionCode code, const String& message) {
failureCode = code;
failureMessage = message;
};

if (type() == CryptoKeyType::Secret) {
WebCore::CryptoKeyFormat format = WebCore::CryptoKeyFormat::Raw;
switch (identifier) {
case CryptoAlgorithmIdentifier::HMAC:
case CryptoAlgorithmIdentifier::AES_CTR:
case CryptoAlgorithmIdentifier::AES_CBC:
case CryptoAlgorithmIdentifier::AES_GCM:
case CryptoAlgorithmIdentifier::AES_KW:
case CryptoAlgorithmIdentifier::HKDF:
case CryptoAlgorithmIdentifier::PBKDF2:
break;
case CryptoAlgorithmIdentifier::ChaCha20_Poly1305:
format = WebCore::CryptoKeyFormat::RawSecret;
break;
default:
// AES-CFB lands here on purpose: node v26.3.0 has no AES-CFB in
// webcrypto, so toCryptoKey rejects it even though Bun's
// subtle.importKey accepts it as an extension.
return throwDOMException(WebCore::NotSupportedError, "Unrecognized algorithm name"_s);
}
Comment thread
cirospaciari marked this conversation as resolved.
Resolved

Vector<uint8_t> keyData;
keyData.appendVector(symmetricKey());
auto importAlgorithm = WebCore::CryptoAlgorithmRegistry::singleton().create(identifier);
importAlgorithm->importKey(format, WebCore::KeyData { WTF::move(keyData) }, *params, extractable, usagesBitmap, WTF::move(keyCallback), WTF::move(exceptionCallback));
} else {
bool isPublic = type() == CryptoKeyType::Public;
switch (identifier) {
case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5:
case CryptoAlgorithmIdentifier::RSA_PSS:
case CryptoAlgorithmIdentifier::RSA_OAEP:
case CryptoAlgorithmIdentifier::ECDSA:
case CryptoAlgorithmIdentifier::ECDH:
case CryptoAlgorithmIdentifier::Ed25519:
case CryptoAlgorithmIdentifier::X25519: {
// Round-trip through DER so the WebCrypto import performs the same
// validation as importKey.
auto der = marshalAsymmetricKey(asymmetricKey(), isPublic);
if (!der)
return throwDOMException(WebCore::OperationError, ""_s);
auto importAlgorithm = WebCore::CryptoAlgorithmRegistry::singleton().create(identifier);
importAlgorithm->importKey(isPublic ? WebCore::CryptoKeyFormat::Spki : WebCore::CryptoKeyFormat::Pkcs8, WebCore::KeyData { WTF::move(*der) }, *params, extractable, usagesBitmap, WTF::move(keyCallback), WTF::move(exceptionCallback));
break;
}
case CryptoAlgorithmIdentifier::ML_DSA_44:
case CryptoAlgorithmIdentifier::ML_DSA_65:
case CryptoAlgorithmIdentifier::ML_DSA_87:
case CryptoAlgorithmIdentifier::ML_KEM_768:
case CryptoAlgorithmIdentifier::ML_KEM_1024: {
// Node's 'KeyObject' import path wraps the handle directly.
WebCore::CryptoKeyUsageBitmap allowedUsages;
if (WebCore::CryptoKeyAKP::isMlDsa(identifier))
allowedUsages = isPublic ? WebCore::CryptoKeyUsageVerify : WebCore::CryptoKeyUsageSign;
else
allowedUsages = isPublic
? WebCore::CryptoKeyUsageEncapsulateKey | WebCore::CryptoKeyUsageEncapsulateBits
: WebCore::CryptoKeyUsageDecapsulateKey | WebCore::CryptoKeyUsageDecapsulateBits;
if (usagesBitmap & ~allowedUsages)
// Node v26.3.0 says "for a ML-..." here (toCryptoKey) but "for an ML-..." in generateKey.
return throwDOMException(WebCore::SyntaxError, makeString("Unsupported key usage for a "_s, WebCore::CryptoAlgorithmRegistry::singleton().name(identifier), " key"_s));
if (EVP_PKEY_id(asymmetricKey().get()) != WebCore::CryptoKeyAKP::nidForIdentifier(identifier))
return throwDOMException(WebCore::DataError, "Invalid key type"_s);
EVP_PKEY_up_ref(asymmetricKey().get());
WebCore::EvpPKeyPtr platformKey(asymmetricKey().get());
result = WebCore::CryptoKeyAKP::create(identifier, type(), WTF::move(platformKey), extractable, usagesBitmap);
if (!result)
return throwDOMException(WebCore::OperationError, ""_s);
break;
}
default:
return throwDOMException(WebCore::NotSupportedError, "Unrecognized algorithm name"_s);
}
}

if (failureCode)
return throwDOMException(*failureCode, failureMessage);
if (!result)
return throwDOMException(WebCore::OperationError, ""_s);

if ((result->type() == WebCore::CryptoKeyType::Private || result->type() == WebCore::CryptoKeyType::Secret) && !result->usagesBitmap()) {
return throwDOMException(WebCore::SyntaxError,
result->type() == WebCore::CryptoKeyType::Private
? "Usages cannot be empty when importing a private key."_s
: "Usages cannot be empty when importing a secret key."_s);
}

return WebCore::toJS(lexicalGlobalObject, globalObject, *result);
}

static std::optional<const Vector<uint8_t>*> getSymmetricKey(const WebCore::CryptoKey& key)
Expand Down
Loading
Loading