Skip to content
Merged
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
57 changes: 47 additions & 10 deletions builtins/web/crypto/crypto-key.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

namespace builtins::web::crypto {

CryptoKeyUsages::CryptoKeyUsages(uint8_t mask) : mask(mask) { };
CryptoKeyUsages::CryptoKeyUsages(uint16_t mask) : mask(mask) { };
CryptoKeyUsages::CryptoKeyUsages(bool encrypt, bool decrypt, bool sign, bool verify,
bool derive_key, bool derive_bits, bool wrap_key,
bool unwrap_key) {
Expand Down Expand Up @@ -46,7 +46,7 @@ CryptoKeyUsages::CryptoKeyUsages(bool encrypt, bool decrypt, bool sign, bool ver
};

CryptoKeyUsages CryptoKeyUsages::from(const std::vector<std::string>& key_usages) {
uint8_t mask = 0;
uint16_t mask = 0;
for (const auto &usage : key_usages) {
if (usage == "encrypt") {
mask |= encrypt_flag;
Expand All @@ -64,6 +64,14 @@ CryptoKeyUsages CryptoKeyUsages::from(const std::vector<std::string>& key_usages
mask |= wrap_key_flag;
} else if (usage == "unwrapKey") {
mask |= unwrap_key_flag;
} else if (usage == "encapsulateKey") {
mask |= encapsulate_key_flag;
} else if (usage == "encapsulateBits") {
mask |= encapsulate_bits_flag;
} else if (usage == "decapsulateKey") {
mask |= decapsulate_key_flag;
} else if (usage == "decapsulateBits") {
mask |= decapsulate_bits_flag;
}
}
return {mask};
Expand All @@ -88,7 +96,7 @@ JS::Result<CryptoKeyUsages> CryptoKeyUsages::from(JSContext *cx, JS::HandleValue
if (!JS::GetArrayLength(cx, array, &key_usages_length)) {
return JS::Result<CryptoKeyUsages>(JS::Error());
}
uint8_t mask = 0;
uint16_t mask = 0;
for (uint32_t index = 0; index < key_usages_length; index++) {
JS::RootedValue val(cx);
if (!JS_GetElement(cx, array, index, &val)) {
Expand Down Expand Up @@ -118,12 +126,21 @@ JS::Result<CryptoKeyUsages> CryptoKeyUsages::from(JSContext *cx, JS::HandleValue
mask |= wrap_key_flag;
} else if (usage == "unwrapKey") {
mask |= unwrap_key_flag;
} else if (usage == "encapsulateKey") {
mask |= encapsulate_key_flag;
} else if (usage == "encapsulateBits") {
mask |= encapsulate_bits_flag;
} else if (usage == "decapsulateKey") {
mask |= decapsulate_key_flag;
} else if (usage == "decapsulateBits") {
mask |= decapsulate_bits_flag;
} else {
api::throw_error(
cx, api::Errors::TypeError, "crypto.subtle.importKey",
"each value in the 'keyUsages' list",
"be one of 'encrypt', 'decrypt', 'sign', 'verify', 'deriveKey', 'deriveBits', "
"'wrapKey', or 'unwrapKey'");
"'wrapKey', 'unwrapKey', 'encapsulateKey', 'encapsulateBits', 'decapsulateKey', or "
"'decapsulateBits'");
return JS::Result<CryptoKeyUsages>(JS::Error());
}
}
Expand Down Expand Up @@ -223,8 +240,8 @@ bool CryptoKey::usages_get(JSContext *cx, unsigned argc, JS::Value *vp) {
// Else, grab the CryptoKeyUsages value from Slots::Usages and convert
// it into a JS Array and store the result in Slots::UsagesArray.
auto usages = JS::GetReservedSlot(self, std::to_underlying(Slots::Usages)).toInt32();
MOZ_ASSERT(std::in_range<std::uint8_t>(usages));
auto usage = CryptoKeyUsages(static_cast<uint8_t>(usages));
MOZ_ASSERT(std::in_range<std::uint16_t>(usages));
auto usage = CryptoKeyUsages(static_cast<uint16_t>(usages));
// The result is ordered alphabetically.
JS::RootedValueVector result(cx);
JS::RootedString str(cx);
Expand All @@ -239,6 +256,16 @@ bool CryptoKey::usages_get(JSContext *cx, unsigned argc, JS::Value *vp) {
return true;
};

if (usage.canDecapsulateBits()) {
if (!append("decapsulateBits")) {
return false;
}
}
if (usage.canDecapsulateKey()) {
if (!append("decapsulateKey")) {
return false;
}
}
if (usage.canDecrypt()) {
if (!append("decrypt")) {
return false;
Expand All @@ -254,6 +281,16 @@ bool CryptoKey::usages_get(JSContext *cx, unsigned argc, JS::Value *vp) {
return false;
}
}
if (usage.canEncapsulateBits()) {
if (!append("encapsulateBits")) {
return false;
}
}
if (usage.canEncapsulateKey()) {
if (!append("encapsulateKey")) {
return false;
}
}
if (usage.canEncrypt()) {
if (!append("encrypt")) {
return false;
Expand Down Expand Up @@ -882,16 +919,16 @@ JS::Result<bool> CryptoKey::is_algorithm(JSContext *cx, JS::HandleObject self,
bool CryptoKey::canSign(JS::HandleObject self) {
MOZ_ASSERT(is_instance(self));
auto usages = JS::GetReservedSlot(self, std::to_underlying(Slots::Usages)).toInt32();
MOZ_ASSERT(std::in_range<std::uint8_t>(usages));
auto usage = CryptoKeyUsages(static_cast<uint8_t>(usages));
MOZ_ASSERT(std::in_range<std::uint16_t>(usages));
auto usage = CryptoKeyUsages(static_cast<uint16_t>(usages));
return usage.canSign();
}

bool CryptoKey::canVerify(JS::HandleObject self) {
MOZ_ASSERT(is_instance(self));
auto usages = JS::GetReservedSlot(self, std::to_underlying(Slots::Usages)).toInt32();
MOZ_ASSERT(std::in_range<std::uint8_t>(usages));
auto usage = CryptoKeyUsages(static_cast<uint8_t>(usages));
MOZ_ASSERT(std::in_range<std::uint16_t>(usages));
auto usage = CryptoKeyUsages(static_cast<uint16_t>(usages));
return usage.canVerify();
}

Expand Down
30 changes: 19 additions & 11 deletions builtins/web/crypto/crypto-key.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,30 @@ enum class CryptoKeyFormat : uint8_t { Raw, Spki, Pkcs8, Jwk };

class CryptoKeyUsages {
private:
uint8_t mask = 0;
uint16_t mask = 0;

public:
static constexpr const uint8_t encrypt_flag = 1 << 0;
static constexpr const uint8_t decrypt_flag = 1 << 1;
static constexpr const uint8_t sign_flag = 1 << 2;
static constexpr const uint8_t verify_flag = 1 << 3;
static constexpr const uint8_t derive_key_flag = 1 << 4;
static constexpr const uint8_t derive_bits_flag = 1 << 5;
static constexpr const uint8_t wrap_key_flag = 1 << 6;
static constexpr const uint8_t unwrap_key_flag = 1 << 7;
static constexpr const uint16_t encrypt_flag = 1 << 0;
static constexpr const uint16_t decrypt_flag = 1 << 1;
static constexpr const uint16_t sign_flag = 1 << 2;
static constexpr const uint16_t verify_flag = 1 << 3;
static constexpr const uint16_t derive_key_flag = 1 << 4;
static constexpr const uint16_t derive_bits_flag = 1 << 5;
static constexpr const uint16_t wrap_key_flag = 1 << 6;
static constexpr const uint16_t unwrap_key_flag = 1 << 7;
static constexpr const uint16_t encapsulate_key_flag = 1 << 8;
static constexpr const uint16_t encapsulate_bits_flag = 1 << 9;
static constexpr const uint16_t decapsulate_key_flag = 1 << 10;
static constexpr const uint16_t decapsulate_bits_flag = 1 << 11;

CryptoKeyUsages() = default;
CryptoKeyUsages(uint8_t mask);
CryptoKeyUsages(uint16_t mask);
CryptoKeyUsages(bool encrypt, bool decrypt, bool sign, bool verify, bool derive_key,
bool derive_bits, bool wrap_key, bool unwrap_key);
static CryptoKeyUsages from(const std::vector<std::string>& key_usages);
static JS::Result<CryptoKeyUsages> from(JSContext *cx, JS::HandleValue key_usages);

[[nodiscard]] uint8_t toInt() const { return this->mask; };
[[nodiscard]] uint16_t toInt() const { return this->mask; };

[[nodiscard]] bool isEmpty() const { return this->mask == 0; };
bool isSuperSetOf(CryptoKeyUsages &other) const { return (this->mask & other.mask) != 0; };
Expand All @@ -54,6 +58,10 @@ class CryptoKeyUsages {
[[nodiscard]] bool canDeriveBits() const { return (this->mask & derive_bits_flag) != 0; };
[[nodiscard]] bool canWrapKey() const { return (this->mask & wrap_key_flag) != 0; };
[[nodiscard]] bool canUnwrapKey() const { return (this->mask & unwrap_key_flag) != 0; };
[[nodiscard]] bool canEncapsulateKey() const { return (this->mask & encapsulate_key_flag) != 0; };
[[nodiscard]] bool canEncapsulateBits() const { return (this->mask & encapsulate_bits_flag) != 0; };
[[nodiscard]] bool canDecapsulateKey() const { return (this->mask & decapsulate_key_flag) != 0; };
[[nodiscard]] bool canDecapsulateBits() const { return (this->mask & decapsulate_bits_flag) != 0; };

[[nodiscard]] bool canOnlyEncrypt() const { return this->mask == encrypt_flag; };
[[nodiscard]] bool canOnlyDecrypt() const { return this->mask == decrypt_flag; };
Expand Down
7 changes: 5 additions & 2 deletions builtins/web/crypto/json-web-key.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,14 @@ std::unique_ptr<JsonWebKey> JsonWebKey::parse(JSContext *cx, JS::HandleValue val
std::string op(op_chars.begin(), op_chars.len);

if (op != "encrypt" && op != "decrypt" && op != "sign" && op != "verify" &&
op != "deriveKey" && op != "deriveBits" && op != "wrapKey" && op != "unwrapKey") {
op != "deriveKey" && op != "deriveBits" && op != "wrapKey" && op != "unwrapKey" &&
op != "encapsulateKey" && op != "encapsulateBits" && op != "decapsulateKey" &&
op != "decapsulateBits") {
dom_exception::DOMException::raise(cx,
"crypto.subtle.importKey parameter 'keyData': "
"each value in the 'key_ops' list must be one of 'encrypt', 'decrypt', "
"'sign', 'verify', 'deriveKey', 'deriveBits', 'wrapKey', or 'unwrapKey'",
"'sign', 'verify', 'deriveKey', 'deriveBits', 'wrapKey', 'unwrapKey', "
"'encapsulateKey', 'encapsulateBits', 'decapsulateKey', or 'decapsulateBits'",
"DataError");
return nullptr;
}
Expand Down
25 changes: 25 additions & 0 deletions tests/integration/crypto/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -1139,6 +1139,31 @@ export const handler = serveTest(async (t) => {
);
}, TypeError);
});
// https://wicg.github.io/webcrypto-modern-algos/ adds these values to the KeyUsage enum, so
// they must reach the algorithm's usage check (SyntaxError) instead of failing enum
// conversion (TypeError).
const modernKeyUsages = ["encapsulateKey", "encapsulateBits", "decapsulateKey", "decapsulateBits"];
await t.test("subtle.importKey.fifth-parameter-modern-algorithms-usages", async () => {
for (const usage of modernKeyUsages) {
const publicEcdsaJsonWebKeyData = createPublicEcdsaJsonWebKeyData();
await rejects(async () => {
await crypto.subtle.importKey(
"jwk",
publicEcdsaJsonWebKeyData,
ecdsaJsonWebKeyAlgorithm,
publicEcdsaJsonWebKeyData.ext,
["verify", usage],
);
}, DOMException, undefined, "SyntaxError");
}
});
await t.test("subtle.importKey.jwk-key_ops-modern-algorithms-usages", async () => {
for (const usage of modernKeyUsages) {
const jwk = { ...createPublicEcdsaJsonWebKeyData(), key_ops: ["verify", usage] };
const key = await crypto.subtle.importKey("jwk", jwk, ecdsaJsonWebKeyAlgorithm, true, ["verify"]);
deepStrictEqual(key.usages, ["verify"], `key.usages with key_ops ["verify", "${usage}"]`);
}
});
await t.test(
"subtle.importKey.fifth-parameter-duplicate-operations",
async () => {
Expand Down
Loading
Loading