Skip to content

Commit adb31f7

Browse files
Jake ChampionJakeChampion
authored andcommitted
feat: Implement JS CryptoKey Interface
Spec: https://w3c.github.io/webcrypto/#cryptokey-interface This implements the basic interface of a CryptoKey: - Exposing a public constructor which throws an JS Error when called - Defining the interface/instance members (https://w3c.github.io/webcrypto/#cryptokey-interface-members) - Defining all the internal slots that are required for the interface
1 parent b2e3de5 commit adb31f7

File tree

3 files changed

+291
-0
lines changed

3 files changed

+291
-0
lines changed

c-dependencies/js-compute-runtime/builtin.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ const JSErrorFormatString *GetErrorMessageBuiltin(void *userRef, unsigned errorN
9191
if (!args.requireAtLeast(cx, name, required_argc)) \
9292
return false;
9393

94+
// This macro:
95+
// - Declares a `JS::CallArgs args` which contains the arguments provided to the method
96+
// - Checks the receiver (`this`) is an instance of the class containing the called method
97+
// - Declares a `JS::RootedObject self` which contains the receiver (`this`)
98+
// - Checks that the number of arguments provided to the member is at least the number provided to
99+
// the macro.
94100
#define METHOD_HEADER(required_argc) METHOD_HEADER_WITH_NAME(required_argc, __func__)
95101

96102
#define CTOR_HEADER(name, required_argc) \
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
#include "crypto-key.h"
2+
#include "js-compute-builtins.h"
3+
4+
namespace builtins {
5+
6+
bool CryptoKey::algorithm_get(JSContext *cx, unsigned argc, JS::Value *vp) {
7+
METHOD_HEADER(0);
8+
9+
// TODO: Should we move this into the METHOD_HEADER macro?
10+
// CryptoKey.prototype passes the receiver check in the above macro but is not actually an
11+
// instance of CryptoKey. We check if `self` is `CryptoKey.prototype` and if it is, we throw a JS
12+
// Error.
13+
if (self == proto_obj.get()) {
14+
JS_ReportErrorNumberASCII(cx, GetErrorMessageBuiltin, nullptr, JSMSG_INCOMPATIBLE_INSTANCE,
15+
__func__, CryptoKey::class_.name);
16+
return false;
17+
}
18+
19+
auto algorithm = &JS::GetReservedSlot(self, Slots::Algorithm).toObject();
20+
JS::RootedObject result(cx, algorithm);
21+
if (!result) {
22+
return false;
23+
}
24+
args.rval().setObject(*result);
25+
26+
return true;
27+
}
28+
29+
bool CryptoKey::extractable_get(JSContext *cx, unsigned argc, JS::Value *vp) {
30+
METHOD_HEADER(0);
31+
32+
// TODO: Should we move this into the METHOD_HEADER macro?
33+
// CryptoKey.prototype passes the receiver check in the above macro but is not actually an
34+
// instance of CryptoKey. We check if `self` is `CryptoKey.prototype` and if it is, we throw a JS
35+
// Error.
36+
if (self == proto_obj.get()) {
37+
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_INTERFACE,
38+
"extractable get", "CryptoKey");
39+
return false;
40+
}
41+
42+
auto extractable = JS::GetReservedSlot(self, Slots::Extractable).toBoolean();
43+
args.rval().setBoolean(extractable);
44+
45+
return true;
46+
}
47+
48+
bool CryptoKey::type_get(JSContext *cx, unsigned argc, JS::Value *vp) {
49+
METHOD_HEADER(0)
50+
51+
// TODO: Should we move this into the METHOD_HEADER macro?
52+
// CryptoKey.prototype passes the receiver check in the above macro but is not actually an
53+
// instance of CryptoKey. We check if `self` is `CryptoKey.prototype` and if it is, we throw a JS
54+
// Error.
55+
if (self == proto_obj.get()) {
56+
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_INTERFACE, "type get",
57+
"CryptoKey");
58+
return false;
59+
}
60+
auto type = static_cast<CryptoKeyType>(JS::GetReservedSlot(self, Slots::Type).toInt32());
61+
62+
// We store the type internally as a CryptoKeyType variant and need to
63+
// convert it into it's JSString representation.
64+
switch (type) {
65+
case CryptoKeyType::Private: {
66+
auto str = JS_AtomizeString(cx, "private");
67+
if (!str) {
68+
return false;
69+
}
70+
args.rval().setString(str);
71+
return true;
72+
}
73+
case CryptoKeyType::Public: {
74+
auto str = JS_AtomizeString(cx, "public");
75+
if (!str) {
76+
return false;
77+
}
78+
args.rval().setString(str);
79+
return true;
80+
}
81+
case CryptoKeyType::Secret: {
82+
auto str = JS_AtomizeString(cx, "secret");
83+
if (!str) {
84+
return false;
85+
}
86+
args.rval().setString(str);
87+
return true;
88+
}
89+
default: {
90+
MOZ_ASSERT_UNREACHABLE("Unknown `CryptoKeyType` value");
91+
return false;
92+
}
93+
};
94+
}
95+
96+
bool CryptoKey::usages_get(JSContext *cx, unsigned argc, JS::Value *vp) {
97+
METHOD_HEADER(0);
98+
99+
// TODO: Should we move this into the METHOD_HEADER macro?
100+
// CryptoKey.prototype passes the receiver check in the above macro but is not actually an
101+
// instance of CryptoKey. We check if `self` is `CryptoKey.prototype` and if it is, we throw a JS
102+
// Error.
103+
if (self == proto_obj.get()) {
104+
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INVALID_INTERFACE, "usages get",
105+
"CryptoKey");
106+
return false;
107+
}
108+
109+
// If the JS Array has already been created previously, return it.
110+
auto cached_usage = JS::GetReservedSlot(self, Slots::UsagesArray);
111+
if (cached_usage.isObject()) {
112+
args.rval().setObject(cached_usage.toObject());
113+
return true;
114+
}
115+
// Else, grab the CryptoKeyUsageBitmap value from Slots::Usages and convert
116+
// it into a JS Array and store the result in Slots::UsagesArray.
117+
auto usage = JS::GetReservedSlot(self, Slots::Usages).toInt32();
118+
// The result is ordered alphabetically.
119+
JS::RootedValueVector result(cx);
120+
JS::RootedString str(cx);
121+
auto append = [&](const char *name) -> bool {
122+
if (!(str = JS_AtomizeString(cx, name))) {
123+
return false;
124+
}
125+
if (!result.append(JS::StringValue(str))) {
126+
js::ReportOutOfMemory(cx);
127+
return false;
128+
}
129+
return true;
130+
};
131+
if (usage & CryptoKeyUsageDecrypt) {
132+
if (!append("decrypt")) {
133+
return false;
134+
}
135+
}
136+
if (usage & CryptoKeyUsageDeriveBits) {
137+
if (!append("deriveBits")) {
138+
return false;
139+
}
140+
}
141+
if (usage & CryptoKeyUsageDeriveKey) {
142+
if (!append("deriveKey")) {
143+
return false;
144+
}
145+
}
146+
if (usage & CryptoKeyUsageEncrypt) {
147+
if (!append("encrypt")) {
148+
return false;
149+
}
150+
}
151+
if (usage & CryptoKeyUsageSign) {
152+
if (!append("sign")) {
153+
return false;
154+
}
155+
}
156+
if (usage & CryptoKeyUsageUnwrapKey) {
157+
if (!append("unwrapKey")) {
158+
return false;
159+
}
160+
}
161+
if (usage & CryptoKeyUsageVerify) {
162+
if (!append("verify")) {
163+
return false;
164+
}
165+
}
166+
if (usage & CryptoKeyUsageWrapKey) {
167+
if (!append("wrapKey")) {
168+
return false;
169+
}
170+
}
171+
172+
JS::Rooted<JSObject *> array(cx, JS::NewArrayObject(cx, result));
173+
if (!array) {
174+
return false;
175+
}
176+
cached_usage.setObject(*array);
177+
JS::SetReservedSlot(self, Slots::UsagesArray, cached_usage);
178+
179+
args.rval().setObject(*array);
180+
181+
return true;
182+
}
183+
184+
const JSFunctionSpec CryptoKey::methods[] = {JS_FS_END};
185+
186+
const JSPropertySpec CryptoKey::properties[] = {
187+
JS_PSG("type", CryptoKey::type_get, JSPROP_ENUMERATE),
188+
JS_PSG("extractable", CryptoKey::extractable_get, JSPROP_ENUMERATE),
189+
JS_PSG("algorithm", CryptoKey::algorithm_get, JSPROP_ENUMERATE),
190+
JS_PSG("usages", CryptoKey::usages_get, JSPROP_ENUMERATE),
191+
JS_STRING_SYM_PS(toStringTag, "CryptoKey", JSPROP_READONLY),
192+
JS_PS_END};
193+
194+
// There is no directly exposed constructor in the CryptoKey interface
195+
// https://w3c.github.io/webcrypto/#cryptokey-interface We throw a JS Error if the application
196+
// attempts to call the CryptoKey constructor directly
197+
bool CryptoKey::constructor(JSContext *cx, unsigned argc, JS::Value *vp) {
198+
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ILLEGAL_CTOR);
199+
return false;
200+
}
201+
202+
bool CryptoKey::init_class(JSContext *cx, JS::HandleObject global) {
203+
return BuiltinImpl<CryptoKey>::init_class_impl(cx, global);
204+
}
205+
206+
} // namespace builtins
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#ifndef JS_COMPUTE_RUNTIME_CRYPTO_KEY_H
2+
#define JS_COMPUTE_RUNTIME_CRYPTO_KEY_H
3+
#include <span>
4+
5+
#include "builtin.h"
6+
#include "openssl/evp.h"
7+
8+
namespace builtins {
9+
10+
enum class CryptoKeyType : uint8_t { Public, Private, Secret };
11+
12+
enum {
13+
CryptoKeyUsageEncrypt = 1 << 0,
14+
CryptoKeyUsageDecrypt = 1 << 1,
15+
CryptoKeyUsageSign = 1 << 2,
16+
CryptoKeyUsageVerify = 1 << 3,
17+
CryptoKeyUsageDeriveKey = 1 << 4,
18+
CryptoKeyUsageDeriveBits = 1 << 5,
19+
CryptoKeyUsageWrapKey = 1 << 6,
20+
CryptoKeyUsageUnwrapKey = 1 << 7
21+
};
22+
23+
typedef int CryptoKeyUsageBitmap;
24+
25+
class CryptoKey : public BuiltinImpl<CryptoKey> {
26+
public:
27+
static const int ctor_length = 0;
28+
static constexpr const char *class_name = "CryptoKey";
29+
30+
// https://w3c.github.io/webcrypto/#dom-cryptokey-algorithm
31+
// Returns the cached ECMAScript object associated with the [[algorithm]] internal slot.
32+
static bool algorithm_get(JSContext *cx, unsigned argc, JS::Value *vp);
33+
34+
// https://w3c.github.io/webcrypto/#dom-cryptokey-extractable
35+
// Reflects the [[extractable]] internal slot, which indicates whether or not the raw keying
36+
// material may be exported by the application.
37+
static bool extractable_get(JSContext *cx, unsigned argc, JS::Value *vp);
38+
39+
// https://w3c.github.io/webcrypto/#dom-cryptokey-type
40+
// Reflects the [[type]] internal slot, which contains the type of the underlying key.
41+
static bool type_get(JSContext *cx, unsigned argc, JS::Value *vp);
42+
43+
// https://w3c.github.io/webcrypto/#dom-cryptokey-usages
44+
// Returns the cached ECMAScript object associated with the [[usages]] internal slot, which
45+
// indicates which cryptographic operations are permissible to be used with this key.
46+
static bool usages_get(JSContext *cx, unsigned argc, JS::Value *vp);
47+
48+
enum Slots {
49+
// https://w3c.github.io/webcrypto/#ref-for-dfn-CryptoKey-slot-algorithm-1
50+
// The contents of the [[algorithm]] internal slot shall be, or be derived from, a KeyAlgorithm.
51+
// We store a JS::ObjectValue within this slot which contains a JS Object representation of the
52+
// algorithm.
53+
Algorithm,
54+
// The type of the underlying key.
55+
// We store a JS::Int32Value representation of the CryptoKeyType variant in this slot
56+
Type,
57+
// Indicates whether or not the raw keying material may be exported by the application
58+
// We store a JS::BooleanValue in this slot
59+
Extractable,
60+
// Indicates which cryptographic operations are permissible to be used with this key
61+
// We store a JS::Int32Value representation of a CryptoKeyUsageBitmap in this slot
62+
Usages,
63+
// Returns the cached ECMAScript object associated with the [[usages]] internal slot,
64+
// which indicates which cryptographic operations are permissible to be used with this key.
65+
UsagesArray,
66+
// We store a JS::PrivateValue in this slot, it will contain either the raw key data.
67+
// It will either be an `EVP_PKEY *` or an `uint8_t *`.
68+
// `uint8_t *` is used only for HMAC keys, `EVP_PKEY *` is used for all the other key types.
69+
Key,
70+
Count
71+
};
72+
static const JSFunctionSpec methods[];
73+
static const JSPropertySpec properties[];
74+
static bool constructor(JSContext *cx, unsigned argc, JS::Value *vp);
75+
static bool init_class(JSContext *cx, JS::HandleObject global);
76+
};
77+
78+
} // namespace builtins
79+
#endif

0 commit comments

Comments
 (0)