Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 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
8 changes: 8 additions & 0 deletions src/jsc/bindings/webcrypto/CryptoAlgorithmHMAC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,17 @@ void CryptoAlgorithmHMAC::importKey(CryptoKeyFormat format, KeyData&& data, cons
RefPtr<CryptoKeyHMAC> result;
switch (format) {
case CryptoKeyFormat::Raw:
if (hmacParameters.length && !hmacParameters.length.value()) {
exceptionCallback(DataError, ""_s);
return;
}
result = CryptoKeyHMAC::importRaw(hmacParameters.length.value_or(0), hmacParameters.hashIdentifier, WTF::move(std::get<Vector<uint8_t>>(data)), extractable, usages);
break;
case CryptoKeyFormat::Jwk: {
if (hmacParameters.length && !hmacParameters.length.value()) {
exceptionCallback(DataError, ""_s);
return;
}
auto checkAlgCallback = [](CryptoAlgorithmIdentifier hash, const String& alg) -> bool {
switch (hash) {
case CryptoAlgorithmIdentifier::SHA_1:
Expand Down
58 changes: 58 additions & 0 deletions test/js/web/crypto/web-crypto.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,64 @@ describe("Web Crypto", () => {
expect(isSigValid).toBe(true);
});

// https://w3c.github.io/webcrypto/#hmac-operations
describe("HMAC importKey length", () => {
const outcome = (p: Promise<CryptoKey>) =>
p.then(
k => ({ length: (k.algorithm as HmacKeyAlgorithm).length }),
e => ({ error: e.name }),
);

it("rejects length:0 with DataError and accepts an absent length (raw)", async () => {
const data = new Uint8Array(20);
const algo = { name: "HMAC", hash: "SHA-256" } as const;
expect({
absent: await outcome(crypto.subtle.importKey("raw", data, algo, true, ["sign"])),
zero: await outcome(crypto.subtle.importKey("raw", data, { ...algo, length: 0 }, true, ["sign"])),
one: await outcome(crypto.subtle.importKey("raw", data, { ...algo, length: 1 }, true, ["sign"])),
short: await outcome(crypto.subtle.importKey("raw", data, { ...algo, length: 159 }, true, ["sign"])),
exact: await outcome(crypto.subtle.importKey("raw", data, { ...algo, length: 160 }, true, ["sign"])),
}).toEqual({
absent: { length: 160 },
zero: { error: "DataError" },
one: { error: "DataError" },
short: { error: "DataError" },
exact: { length: 160 },
});
});

it("rejects length:0 with DataError and accepts an absent length (jwk)", async () => {
const jwk = { kty: "oct", k: "AAAAAAAAAAAAAAAAAAAAAAAAAAA" };
const algo = { name: "HMAC", hash: "SHA-256" } as const;
expect({
absent: await outcome(crypto.subtle.importKey("jwk", jwk, algo, true, ["sign"])),
zero: await outcome(crypto.subtle.importKey("jwk", jwk, { ...algo, length: 0 }, true, ["sign"])),
}).toEqual({
absent: { length: 160 },
zero: { error: "DataError" },
});
});

// An unsupported format must reject with NotSupportedError before the
// length member is even considered.
it.each(["pkcs8", "spki"] as const)(
"rejects the %s format with NotSupportedError regardless of length",
async format => {
const data = new Uint8Array(20);
const algo = { name: "HMAC", hash: "SHA-256" } as const;
expect({
absent: await outcome(crypto.subtle.importKey(format, data, algo, true, ["sign"])),
zero: await outcome(crypto.subtle.importKey(format, data, { ...algo, length: 0 }, true, ["sign"])),
one: await outcome(crypto.subtle.importKey(format, data, { ...algo, length: 1 }, true, ["sign"])),
}).toEqual({
absent: { error: "NotSupportedError" },
zero: { error: "NotSupportedError" },
one: { error: "NotSupportedError" },
});
},
);
});

describe("unwrapKey JWK error handling", () => {
// Setup: AES-GCM key that can encrypt arbitrary bytes and also unwrap keys.
// We encrypt payloads that decrypt to invalid JWK data so the JWK parse path
Expand Down
Loading