Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
84 changes: 84 additions & 0 deletions bench/snippets/webcrypto-keys.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { bench, group, run } from "../runner.mjs";

const { subtle } = crypto;

const raw = new Uint8Array(32).fill(1);
const k = Buffer.from(raw).toString("base64url");
const usages = ["encrypt", "decrypt"];
const everyUsage = [
...usages,
"sign",
"verify",
"deriveKey",
"deriveBits",
"wrapKey",
"unwrapKey",
"encapsulateKey",
"encapsulateBits",
"decapsulateKey",
"decapsulateBits",
];
const aesJwk = key_ops => ({ kty: "oct", k, key_ops });
const aesJwks = [
["no key_ops", aesJwk(undefined)],
["1 key_ops", aesJwk(["encrypt"])],
["2 key_ops", aesJwk(usages)],
["12 key_ops", aesJwk(everyUsage)],
["2 key_ops and one that is not a KeyUsage", aesJwk([...usages, "urn:example:op"])],
];
const hmac = { name: "HMAC", hash: "SHA-256" };
const hmacJwk = { kty: "oct", k, key_ops: ["sign", "verify"] };
const ecdsa = { name: "ECDSA", namedCurve: "P-256" };
const ecdsaJwk = {
kty: "EC",
crv: "P-256",
x: "3DyR3pdz7BDZtoAVvm4Mn55Ashdl84LCJB9kcUI3IrQ",
y: "r6xDaTpWl7d958Y4HMq6OR4F8PLe1wl7m41I2ZX1yoU",
key_ops: ["verify"],
};

const aesKey = await subtle.importKey("raw", raw, "AES-GCM", true, usages);
const ecdsaKey = await subtle.importKey("jwk", ecdsaJwk, ecdsa, true, ["verify"]);
const wrapAlgorithm = { name: "AES-GCM", iv: new Uint8Array(12).fill(2) };
const wrappingKey = await subtle.importKey("raw", raw, "AES-GCM", false, ["wrapKey", "unwrapKey"]);
const wrapped = await subtle.wrapKey("jwk", aesKey, wrappingKey, wrapAlgorithm);

group("importKey", () => {
bench("importKey raw AES-GCM", async () => {
await subtle.importKey("raw", raw, "AES-GCM", true, usages);
});
for (const [name, jwk] of aesJwks) {
bench(`importKey jwk AES-GCM, ${name}`, async () => {
await subtle.importKey("jwk", jwk, "AES-GCM", true, ["encrypt"]);
});
}
bench("importKey jwk HMAC, 2 key_ops", async () => {
await subtle.importKey("jwk", hmacJwk, hmac, true, ["sign"]);
});
bench("importKey jwk ECDSA public, 1 key_ops", async () => {
await subtle.importKey("jwk", ecdsaJwk, ecdsa, true, ["verify"]);
});
});

group("unwrapKey", () => {
bench("unwrapKey jwk AES-GCM, 2 key_ops", async () => {
await subtle.unwrapKey("jwk", wrapped, wrappingKey, wrapAlgorithm, "AES-GCM", true, usages);
});
});

group("exportKey", () => {
bench("exportKey jwk AES-GCM", async () => {
await subtle.exportKey("jwk", aesKey);
});
bench("exportKey jwk ECDSA public", async () => {
await subtle.exportKey("jwk", ecdsaKey);
});
});

group("generateKey", () => {
bench("generateKey AES-GCM 256", async () => {
await subtle.generateKey({ name: "AES-GCM", length: 256 }, true, usages);
});
});

await run();
2 changes: 1 addition & 1 deletion src/jsc/bindings/webcrypto/CryptoAlgorithmAKPShared.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ inline void importAkpKey(CryptoAlgorithmIdentifier identifier, const AkpAlgorith
exceptionCallback(DataError, "Invalid JWK \"use\" Parameter"_s);
return;
}
if (hasDuplicateJwkKeyOps(jwk.key_ops)) {
if (hasDuplicateJwkKeyOps(jwk)) {
exceptionCallback(DataError, "Duplicate key operation"_s);
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ void CryptoAlgorithmChaCha20Poly1305::importKey(CryptoKeyFormat format, KeyData&
exceptionCallback(DataError, "Invalid JWK \"use\" Parameter"_s);
return;
}
if (hasDuplicateJwkKeyOps(jwk.key_ops)) {
if (hasDuplicateJwkKeyOps(jwk)) {
exceptionCallback(DataError, "Duplicate key operation"_s);
return;
}
Expand Down Expand Up @@ -215,7 +215,7 @@ void CryptoAlgorithmChaCha20Poly1305::exportKey(CryptoKeyFormat format, Ref<Cryp
jwk.kty = "oct"_s;
jwk.k = Bun::base64URLEncodeToString(rawKey.key());
jwk.alg = String(ALG);
jwk.key_ops = rawKey.usages();
jwk.key_ops = toJwkKeyOps(rawKey.usagesBitmap());
jwk.ext = rawKey.extractable();
result = WTF::move(jwk);
break;
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/webcrypto/CryptoAlgorithmHMAC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ void CryptoAlgorithmHMAC::importKey(CryptoKeyFormat format, KeyData&& data, cons
exceptionCallback(DataError, "Invalid JWK \"use\" Parameter"_s);
return;
}
if (hasDuplicateJwkKeyOps(jwk.key_ops)) {
if (hasDuplicateJwkKeyOps(jwk)) {
exceptionCallback(DataError, "Duplicate key operation"_s);
return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/webcrypto/CryptoKeyAES.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ JsonWebKey CryptoKeyAES::exportJwk() const
JsonWebKey result {};
result.kty = "oct"_s;
result.k = Bun::base64URLEncodeToString(m_key);
result.key_ops = usages();
result.key_ops = toJwkKeyOps(usagesBitmap());
result.ext = extractable();
return result;
}
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/webcrypto/CryptoKeyAKP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ ExceptionOr<JsonWebKey> CryptoKeyAKP::exportJwk() const
JsonWebKey result;
result.kty = "AKP"_s;
result.alg = CryptoAlgorithmRegistry::singleton().name(algorithmIdentifier());
result.key_ops = usages();
result.key_ops = toJwkKeyOps(usagesBitmap());
result.ext = extractable();

if (type() == CryptoKeyType::Private) {
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/webcrypto/CryptoKeyEC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ ExceptionOr<JsonWebKey> CryptoKeyEC::exportJwk() const
result.crv = P521;
break;
}
result.key_ops = usages();
result.key_ops = toJwkKeyOps(usagesBitmap());
result.ext = extractable();
if (!platformAddFieldElements(result))
return Exception { OperationError };
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/webcrypto/CryptoKeyHMAC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ JsonWebKey CryptoKeyHMAC::exportJwk() const
JsonWebKey result {};
result.kty = "oct"_s;
result.k = Bun::base64URLEncodeToString(m_key);
result.key_ops = usages();
result.key_ops = toJwkKeyOps(usagesBitmap());
result.ext = extractable();
return result;
}
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/webcrypto/CryptoKeyOKP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ ExceptionOr<JsonWebKey> CryptoKeyOKP::exportJwk() const
break;
}

result.key_ops = usages();
result.key_ops = toJwkKeyOps(usagesBitmap());
result.ext = extractable();

switch (type()) {
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/webcrypto/CryptoKeyRSA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ JsonWebKey CryptoKeyRSA::exportJwk() const
{
JsonWebKey result {};
result.kty = "RSA"_s;
result.key_ops = usages();
result.key_ops = toJwkKeyOps(usagesBitmap());
result.ext = extractable();

auto rsaComponents = exportData();
Expand Down
7 changes: 6 additions & 1 deletion src/jsc/bindings/webcrypto/JSCryptoKeyUsage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@ template<> JSString* convertEnumerationToJS(JSGlobalObject& lexicalGlobalObject,

template<> std::optional<CryptoKeyUsage> parseEnumeration<CryptoKeyUsage>(JSGlobalObject& lexicalGlobalObject, JSValue value)
{
auto stringValue = value.toWTFString(&lexicalGlobalObject);
return parseEnumerationFromString<CryptoKeyUsage>(value.toWTFString(&lexicalGlobalObject));
}

// NEVER_INLINE: inlined into its two callers, this left the table scan as an out-of-line std::__find_if.
template<> NEVER_INLINE std::optional<CryptoKeyUsage> parseEnumerationFromString<CryptoKeyUsage>(const String& stringValue)
{
static constexpr SortedArrayMap enumerationMapping { std::to_array<std::pair<ComparableASCIILiteral, CryptoKeyUsage>>({
{ "decapsulateBits"_s, CryptoKeyUsage::DecapsulateBits },
{ "decapsulateKey"_s, CryptoKeyUsage::DecapsulateKey },
Expand Down
1 change: 1 addition & 0 deletions src/jsc/bindings/webcrypto/JSCryptoKeyUsage.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ String convertEnumerationToString(CryptoKeyUsage);
template<> JSC::JSString* convertEnumerationToJS(JSC::JSGlobalObject&, CryptoKeyUsage);

template<> std::optional<CryptoKeyUsage> parseEnumeration<CryptoKeyUsage>(JSC::JSGlobalObject&, JSC::JSValue);
template<> std::optional<CryptoKeyUsage> parseEnumerationFromString<CryptoKeyUsage>(const String&);

} // namespace WebCore

Expand Down
8 changes: 3 additions & 5 deletions src/jsc/bindings/webcrypto/JSJsonWebKey.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@

#include "JSJsonWebKey.h"

#include "JSCryptoKeyUsage.h"
#include "JSDOMConvertBoolean.h"
#include "JSDOMConvertEnumeration.h"
#include "JSDOMConvertSequences.h"
#include "JSDOMConvertStrings.h"
#include "JSDOMGlobalObject.h"
Expand Down Expand Up @@ -147,7 +145,7 @@ template<> JsonWebKey convertDictionary<JsonWebKey>(JSGlobalObject& lexicalGloba
RETURN_IF_EXCEPTION(throwScope, {});
}
if (!key_opsValue.isUndefined()) {
result.key_ops = convert<IDLSequence<IDLEnumeration<CryptoKeyUsage>>>(lexicalGlobalObject, key_opsValue);
result.key_ops = convert<IDLSequence<IDLDOMString>>(lexicalGlobalObject, key_opsValue);
RETURN_IF_EXCEPTION(throwScope, {});
}
JSValue ktyValue;
Expand Down Expand Up @@ -321,8 +319,8 @@ JSC::JSObject* convertDictionaryToJS(JSC::JSGlobalObject& lexicalGlobalObject, J
RETURN_IF_EXCEPTION(throwScope, {});
Bun::putDirectNamed(vm, result, "k"_s, kValue);
}
if (!IDLSequence<IDLEnumeration<CryptoKeyUsage>>::isNullValue(dictionary.key_ops)) {
auto key_opsValue = toJS<IDLSequence<IDLEnumeration<CryptoKeyUsage>>>(lexicalGlobalObject, globalObject, throwScope, IDLSequence<IDLEnumeration<CryptoKeyUsage>>::extractValueFromNullable(dictionary.key_ops));
if (!IDLSequence<IDLDOMString>::isNullValue(dictionary.key_ops)) {
auto key_opsValue = toJS<IDLSequence<IDLDOMString>>(lexicalGlobalObject, globalObject, throwScope, IDLSequence<IDLDOMString>::extractValueFromNullable(dictionary.key_ops));
RETURN_IF_EXCEPTION(throwScope, {});
Bun::putDirectNamed(vm, result, "key_ops"_s, key_opsValue);
}
Expand Down
9 changes: 0 additions & 9 deletions src/jsc/bindings/webcrypto/JSRsaOtherPrimesInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@ template<> RsaOtherPrimesInfo convertDictionary<RsaOtherPrimesInfo>(JSGlobalObje
if (!dValue.isUndefined()) {
result.d = convert<IDLDOMString>(lexicalGlobalObject, dValue);
RETURN_IF_EXCEPTION(throwScope, {});
} else {
throwRequiredMemberTypeError(lexicalGlobalObject, throwScope, "d"_s, "RsaOtherPrimesInfo"_s, "DOMString"_s);
return {};
}
JSValue rValue;
if (isNullOrUndefined)
Expand All @@ -69,9 +66,6 @@ template<> RsaOtherPrimesInfo convertDictionary<RsaOtherPrimesInfo>(JSGlobalObje
if (!rValue.isUndefined()) {
result.r = convert<IDLDOMString>(lexicalGlobalObject, rValue);
RETURN_IF_EXCEPTION(throwScope, {});
} else {
throwRequiredMemberTypeError(lexicalGlobalObject, throwScope, "r"_s, "RsaOtherPrimesInfo"_s, "DOMString"_s);
return {};
}
JSValue tValue;
if (isNullOrUndefined)
Expand All @@ -83,9 +77,6 @@ template<> RsaOtherPrimesInfo convertDictionary<RsaOtherPrimesInfo>(JSGlobalObje
if (!tValue.isUndefined()) {
result.t = convert<IDLDOMString>(lexicalGlobalObject, tValue);
RETURN_IF_EXCEPTION(throwScope, {});
} else {
throwRequiredMemberTypeError(lexicalGlobalObject, throwScope, "t"_s, "RsaOtherPrimesInfo"_s, "DOMString"_s);
return {};
}
return result;
}
Expand Down
29 changes: 15 additions & 14 deletions src/jsc/bindings/webcrypto/JsonWebKey.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include "CryptoKeyUsage.h"
#include "RsaOtherPrimesInfo.h"
#include <bit>
#include <wtf/Vector.h>

#if ENABLE(WEB_CRYPTO)
Expand All @@ -36,8 +37,9 @@ namespace WebCore {
struct JsonWebKey {
String kty;
String use;
// FIXME: Consider merging key_ops and usages.
std::optional<Vector<CryptoKeyUsage>> key_ops;
// Any string is a valid entry (RFC 7517 section 4.3), so not WebKit's Vector<CryptoKeyUsage>.
std::optional<Vector<String>> key_ops;
// The key_ops entries that name a KeyUsage, set by normalizeJsonWebKey().
CryptoKeyUsageBitmap usages;
String alg;

Expand All @@ -61,27 +63,26 @@ struct JsonWebKey {
String priv;
};

// The key_ops of an exported key: its usages, in KeyUsage order.
Vector<String> toJwkKeyOps(CryptoKeyUsageBitmap);

bool hasRepeatedJwkKeyOp(const Vector<String>&);

// Node's validateKeyOps rejects a repeated key_ops entry for every algorithm
// before the operations/usages mismatch check.
inline bool hasDuplicateJwkKeyOps(const std::optional<Vector<CryptoKeyUsage>>& keyOps)
inline bool hasDuplicateJwkKeyOps(const JsonWebKey& jwk)
{
if (!keyOps)
if (!jwk.key_ops)
return false;
CryptoKeyUsageBitmap seenOps = 0;
for (auto op : *keyOps) {
// The binding enum order matches the bitmap bit order.
CryptoKeyUsageBitmap bit = 1 << static_cast<int>(op);
if (seenOps & bit)
return true;
seenOps |= bit;
}
return false;
size_t distinctKeyUsageCount = std::popcount(static_cast<unsigned>(jwk.usages));
bool everyEntryIsADistinctKeyUsage = jwk.key_ops->size() == distinctKeyUsageCount;
return !everyEntryIsADistinctKeyUsage && hasRepeatedJwkKeyOp(*jwk.key_ops);
}

// Whether `key_ops`, if the key has any, is well formed and covers `usages`.
inline bool jwkKeyOpsAllow(const JsonWebKey& jwk, CryptoKeyUsageBitmap usages)
{
return !jwk.key_ops || (!hasDuplicateJwkKeyOps(jwk.key_ops) && (jwk.usages & usages) == usages);
return !jwk.key_ops || (!hasDuplicateJwkKeyOps(jwk) && (jwk.usages & usages) == usages);
}

} // namespace WebCore
Expand Down
2 changes: 1 addition & 1 deletion src/jsc/bindings/webcrypto/JsonWebKey.idl
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
// The following fields are defined in Section 3.1 of JSON Web Key
DOMString kty;
DOMString use;
sequence<CryptoKeyUsage> key_ops;
sequence<DOMString> key_ops;
DOMString alg;

// The following fields are defined in JSON Web Key Parameters Registration
Expand Down
6 changes: 3 additions & 3 deletions src/jsc/bindings/webcrypto/RsaOtherPrimesInfo.idl
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
JSGenerateToJSObject,
] dictionary RsaOtherPrimesInfo {
// The following fields are defined in Section 6.3.2.7 of JSON Web Algorithms
required DOMString r;
required DOMString d;
required DOMString t;
DOMString r;
DOMString d;
DOMString t;
};
Loading
Loading