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
308 changes: 304 additions & 4 deletions builtins/web/crypto/crypto-algorithm.cpp

Large diffs are not rendered by default.

58 changes: 58 additions & 0 deletions builtins/web/crypto/crypto-algorithm.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#ifndef BUILTINS_WEB_CRYPTO_CRYPTO_ALGORITHM_H
#define BUILTINS_WEB_CRYPTO_CRYPTO_ALGORITHM_H
#include <optional>
#include <span>

#include "builtin.h"
Expand Down Expand Up @@ -46,6 +47,14 @@ enum class NamedCurve : uint8_t {
const char *algorithmName(CryptoAlgorithmIdentifier algorithm);

/// The base class that all algorithm implementations should derive from.
// Converts `value` to a WebIDL `[EnforceRange] unsigned long`, following
// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint: NaN, infinities and values outside
// [0, 2^32 - 1] are a TypeError, fractional values are truncated. Returns `std::nullopt` with a
// pending exception on failure. `method` and `argument` name the operation and parameter being
// converted, for the error message.
std::optional<uint32_t> enforce_range_unsigned_long(JSContext *cx, JS::HandleValue value,
const char *method, const char *argument);

class CryptoAlgorithm {
public:
CryptoAlgorithm() = default;
Expand Down Expand Up @@ -80,6 +89,17 @@ class CryptoAlgorithmSignVerify : public CryptoAlgorithm {
static std::unique_ptr<CryptoAlgorithmSignVerify> normalize(JSContext *cx, JS::HandleValue value);
};

class CryptoAlgorithmDeriveBits : public CryptoAlgorithm {
public:
// `length` is the length, in bits, of the keying material to derive. It is `std::nullopt`
// when the `length` argument of `SubtleCrypto.deriveBits` (declared as
// `optional [EnforceRange] unsigned long? length = null`) was omitted, `undefined` or `null`,
// which the derive bits operations treat as a `null` length per the WebCrypto specification.
virtual JSObject *deriveBits(JSContext *cx, JS::HandleObject baseKey,
std::optional<uint64_t> length) = 0;
static std::unique_ptr<CryptoAlgorithmDeriveBits> normalize(JSContext *cx, JS::HandleValue value);
};

class CryptoAlgorithmHMAC_Sign_Verify final : public CryptoAlgorithmSignVerify {
public:
[[nodiscard]] const char *name() const noexcept override { return "HMAC"; };
Expand Down Expand Up @@ -273,6 +293,44 @@ class CryptoAlgorithmSHA512 final : public CryptoAlgorithmDigest {
JSObject *digest(JSContext *cx, std::span<uint8_t> data) override;
};

class CryptoAlgorithmPBKDF2_Import final : public CryptoAlgorithmImportKey {
public:
[[nodiscard]] const char *name() const noexcept override { return "PBKDF2"; };
CryptoAlgorithmPBKDF2_Import() = default;

CryptoAlgorithmIdentifier identifier() final { return CryptoAlgorithmIdentifier::PBKDF2; };

JSObject *importKey(JSContext *cx, CryptoKeyFormat format, JS::HandleValue key_data,
bool extractable, CryptoKeyUsages usages) override;
JSObject *importKey(JSContext *cx, CryptoKeyFormat format, KeyData key_data, bool extractable,
CryptoKeyUsages usages) override;
JSObject *toObject(JSContext *cx) const;
};

class CryptoAlgorithmPBKDF2_DeriveBits final : public CryptoAlgorithmDeriveBits {
public:
// The salt member of a Pbkdf2Params dictionary.
std::vector<uint8_t> salt;
// The iterations member of a Pbkdf2Params dictionary.
uint32_t iterations;
// The hash member of a Pbkdf2Params dictionary.
CryptoAlgorithmIdentifier hashIdentifier;

[[nodiscard]] const char *name() const noexcept override { return "PBKDF2"; };
CryptoAlgorithmPBKDF2_DeriveBits(std::vector<uint8_t> salt, uint32_t iterations,
CryptoAlgorithmIdentifier hashIdentifier)
: salt{std::move(salt)}, iterations{iterations}, hashIdentifier{hashIdentifier} {};

// https://w3c.github.io/webcrypto/#dfn-Pbkdf2Params
static std::unique_ptr<CryptoAlgorithmPBKDF2_DeriveBits>
fromParameters(JSContext *cx, JS::HandleObject parameters);

CryptoAlgorithmIdentifier identifier() final { return CryptoAlgorithmIdentifier::PBKDF2; };

JSObject *deriveBits(JSContext *cx, JS::HandleObject baseKey,
std::optional<uint64_t> length) override;
};

} // namespace builtins::web::crypto


Expand Down
31 changes: 24 additions & 7 deletions builtins/web/crypto/crypto-key.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -635,18 +635,27 @@ JSObject *CryptoKey::createHMAC(JSContext *cx, CryptoAlgorithmHMAC_Import *algor
bool extractable, CryptoKeyUsages usages) {
MOZ_ASSERT(cx);
MOZ_ASSERT(algorithm);
JS::RootedObject instance(
cx, JS_NewObjectWithGivenProto(cx, &CryptoKey::class_, CryptoKey::proto_obj));
if (!instance) {
return nullptr;
}

JS::RootedObject alg(cx, algorithm->toObject(cx));
if (!alg) {
return nullptr;
}

JS::SetReservedSlot(instance, std::to_underlying(Slots::Algorithm), JS::ObjectValue(*alg));
return createSymmetric(cx, alg, std::move(data), extractable, usages);
}

JSObject *CryptoKey::createSymmetric(JSContext *cx, JS::HandleObject algorithm_object,
std::unique_ptr<std::vector<uint8_t>> data, bool extractable,
CryptoKeyUsages usages) {
MOZ_ASSERT(cx);
MOZ_ASSERT(algorithm_object);
JS::RootedObject instance(
cx, JS_NewObjectWithGivenProto(cx, &CryptoKey::class_, CryptoKey::proto_obj));
if (!instance) {
return nullptr;
}

JS::SetReservedSlot(instance, std::to_underlying(Slots::Algorithm), JS::ObjectValue(*algorithm_object));
JS::SetReservedSlot(instance, std::to_underlying(Slots::Type), JS::Int32Value(static_cast<uint8_t>(CryptoKeyType::Secret)));
JS::SetReservedSlot(instance, std::to_underlying(Slots::Extractable), JS::BooleanValue(extractable));
JS::SetReservedSlot(instance, std::to_underlying(Slots::Usages), JS::Int32Value(usages.toInt()));
Expand Down Expand Up @@ -884,7 +893,7 @@ EVP_PKEY *CryptoKey::key(JSObject *self) {
return static_cast<EVP_PKEY *>(JS::GetReservedSlot(self, std::to_underlying(Slots::Key)).toPrivate());
}

std::span<uint8_t> CryptoKey::hmacKeyData(JSObject *self) {
std::span<uint8_t> CryptoKey::keyData(JSObject *self) {
MOZ_ASSERT(is_instance(self));
return {
static_cast<uint8_t *>(JS::GetReservedSlot(self, std::to_underlying(Slots::KeyData)).toPrivate()),
Expand Down Expand Up @@ -932,6 +941,14 @@ bool CryptoKey::canVerify(JS::HandleObject self) {
return usage.canVerify();
}

bool CryptoKey::canDeriveBits(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::uint16_t>(usages));
auto usage = CryptoKeyUsages(static_cast<uint16_t>(usages));
return usage.canDeriveBits();
}

} // namespace builtins::web::crypto


13 changes: 12 additions & 1 deletion builtins/web/crypto/crypto-key.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ class CryptoKeyUsages {
};
[[nodiscard]] bool canOnlyDeriveKey() const { return this->mask == derive_key_flag; };
[[nodiscard]] bool canOnlyDeriveBits() const { return this->mask == derive_bits_flag; };
[[nodiscard]] bool canOnlyDeriveBitsOrKey() const {
return this->mask == derive_bits_flag || this->mask == derive_key_flag ||
this->mask == (derive_bits_flag | derive_key_flag);
};
[[nodiscard]] bool canOnlyWrapKey() const { return this->mask == wrap_key_flag; };
[[nodiscard]] bool canOnlyUnwrapKey() const { return this->mask == unwrap_key_flag; };
};
Expand Down Expand Up @@ -136,6 +140,12 @@ class CryptoKey : public BuiltinNoConstructor<CryptoKey> {
static JSObject *createHMAC(JSContext *cx, CryptoAlgorithmHMAC_Import *algorithm,
std::unique_ptr<std::vector<uint8_t>> data, unsigned long length,
bool extractable, CryptoKeyUsages usages);
// Shared constructor for `secret`-type CryptoKey objects whose key material is a raw
// byte string (e.g. HMAC and PBKDF2 keys). `algorithm_object` is the already-materialized
// KeyAlgorithm object to store in the [[algorithm]] internal slot.
static JSObject *createSymmetric(JSContext *cx, JS::HandleObject algorithm_object,
std::unique_ptr<std::vector<uint8_t>> data, bool extractable,
CryptoKeyUsages usages);
static JSObject *createRSA(JSContext *cx, CryptoAlgorithmRSASSA_PKCS1_v1_5_Import *algorithm,
std::unique_ptr<CryptoKeyRSAComponents> keyData, bool extractable,
CryptoKeyUsages usages);
Expand All @@ -148,9 +158,10 @@ class CryptoKey : public BuiltinNoConstructor<CryptoKey> {
static CryptoKeyType type(JSObject *self);
static JSObject *get_algorithm(JS::HandleObject self);
static EVP_PKEY *key(JSObject *self);
static std::span<uint8_t> hmacKeyData(JSObject *self);
static std::span<uint8_t> keyData(JSObject *self);
static bool canSign(JS::HandleObject self);
static bool canVerify(JS::HandleObject self);
static bool canDeriveBits(JS::HandleObject self);
static JS::Result<bool> is_algorithm(JSContext *cx, JS::HandleObject self,
CryptoAlgorithmIdentifier algorithm);
};
Expand Down
97 changes: 97 additions & 0 deletions builtins/web/crypto/subtle-crypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,102 @@ bool SubtleCrypto::verify(JSContext *cx, unsigned argc, JS::Value *vp) {
return true;
}

// Promise<ArrayBuffer> deriveBits(AlgorithmIdentifier algorithm,
// CryptoKey baseKey,
// optional [EnforceRange] unsigned long? length = null);
// https://w3c.github.io/webcrypto/#SubtleCrypto-method-deriveBits
bool SubtleCrypto::deriveBits(JSContext *cx, unsigned argc, JS::Value *vp) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function also contains inaccurate spec references

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-aligned to spec

MOZ_ASSERT(cx);
MOZ_ASSERT(vp);
JS::CallArgs args = CallArgsFromVp(argc, vp);

if (!args.requireAtLeast(cx, "SubtleCrypto.deriveBits", 2)) {
return ReturnPromiseRejectedWithPendingError(cx, args);
}
if (!check_receiver(cx, args.thisv(), "SubtleCrypto.deriveBits")) {
return ReturnPromiseRejectedWithPendingError(cx, args);
}

// Argument validation and WebIDL-level processing happens before the spec steps.
// 1. Let algorithm, baseKey and length be the algorithm, baseKey and length parameters passed to
// the deriveBits() method, respectively.
auto algorithm = args.get(0);
auto key_arg = args.get(1);
if (!CryptoKey::is_instance(key_arg)) {
api::throw_error(cx, api::Errors::TypeError, "crypto.subtle.deriveBits", "key",
"be a CryptoKey object");
return ReturnPromiseRejectedWithPendingError(cx, args);
}
JS::RootedObject key(cx, &key_arg.toObject());

// The length parameter is an optional, nullable [EnforceRange] unsigned long: NaN, infinities
// and out-of-range values are a TypeError, fractional values are truncated. A missing,
// undefined, or null length is passed to the key derivation operation as null.
std::optional<uint64_t> length;
auto length_val = args.get(2);
if (!length_val.isNullOrUndefined()) {
auto length_result =
enforce_range_unsigned_long(cx, length_val, "crypto.subtle.deriveBits", "length");
if (!length_result.has_value()) {
return ReturnPromiseRejectedWithPendingError(cx, args);
}
length = length_result.value();
}

// 2. Let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to
// algorithm and op set to "deriveBits".
auto normalizedAlgorithm = CryptoAlgorithmDeriveBits::normalize(cx, algorithm);
// 3. If an error occurred, return a Promise rejected with normalizedAlgorithm.
if (!normalizedAlgorithm) {
return ReturnPromiseRejectedWithPendingError(cx, args);
}

// 4. Let realm be the relevant realm of this.
// (Implicit: there is a single realm.)
// 5. Let promise be a new Promise.
JS::RootedObject promise(cx, JS::NewPromiseObject(cx, nullptr));
if (!promise) {
return ReturnPromiseRejectedWithPendingError(cx, args);
}
// 6. Return promise and perform the remaining steps in parallel.
args.rval().setObject(*promise);

// 7. If the following steps or referenced procedures say to throw an error, queue a global task
// on the crypto task source, given realm's global object, to reject promise with the returned
// error; and then terminate the algorithm.

// 8. If the name member of normalizedAlgorithm is not equal to the name attribute of the
// [[algorithm]] internal slot of baseKey then throw an InvalidAccessError.
auto identifier = normalizedAlgorithm->identifier();
auto match_result = CryptoKey::is_algorithm(cx, key, identifier);
if (match_result.isErr() || match_result.unwrap() == false) {
DOMException::raise(cx, "CryptoKey doesn't match AlgorithmIdentifier", "InvalidAccessError");
return RejectPromiseWithPendingError(cx, promise);
}

// 9. If the [[usages]] internal slot of baseKey does not contain an entry that is "deriveBits",
// then throw an InvalidAccessError.
if (!CryptoKey::canDeriveBits(key)) {
DOMException::raise(cx, "CryptoKey doesn't support deriving bits", "InvalidAccessError");
return RejectPromiseWithPendingError(cx, promise);
}

// 10. Let bits be the result of performing the derive bits operation specified by
// normalizedAlgorithm using baseKey, algorithm and length.
auto *bits = normalizedAlgorithm->deriveBits(cx, key, length);
if (!bits) {
return RejectPromiseWithPendingError(cx, promise);
}

// 11. Queue a global task on the crypto task source, given realm's global object, to perform the
// remaining steps.
// 12. Let result be the result of creating an ArrayBuffer in realm, containing bits.
// 13. Resolve promise with result.
JS::RootedValue result(cx, JS::ObjectValue(*bits));
JS::ResolvePromise(cx, promise, result);
return true;
}

const JSFunctionSpec SubtleCrypto::static_methods[] = {
JS_FS_END,
};
Expand All @@ -357,6 +453,7 @@ const JSPropertySpec SubtleCrypto::static_properties[] = {
};

const JSFunctionSpec SubtleCrypto::methods[] = {
JS_FN("deriveBits", deriveBits, 2, JSPROP_ENUMERATE),
JS_FN("digest", digest, 2, JSPROP_ENUMERATE),
JS_FN("importKey", importKey, 5, JSPROP_ENUMERATE), JS_FN("sign", sign, 3, JSPROP_ENUMERATE),
JS_FN("verify", verify, 4, JSPROP_ENUMERATE), JS_FS_END};
Expand Down
1 change: 1 addition & 0 deletions builtins/web/crypto/subtle-crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class SubtleCrypto : public BuiltinNoConstructor<SubtleCrypto> {
static const JSPropertySpec static_properties[];
static const JSFunctionSpec methods[];
static const JSPropertySpec properties[];
static bool deriveBits(JSContext *cx, unsigned argc, JS::Value *vp);
static bool digest(JSContext *cx, unsigned argc, JS::Value *vp);
static bool importKey(JSContext *cx, unsigned argc, JS::Value *vp);
static bool sign(JSContext *cx, unsigned argc, JS::Value *vp);
Expand Down
Loading
Loading