quickjs 0.21.0.rc1 → 0.22.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +237 -7
- data/ext/quickjsrb/quickjsrb.c +3113 -556
- data/ext/quickjsrb/quickjsrb.h +401 -6
- data/ext/quickjsrb/quickjsrb_crypto_subtle.c +584 -76
- data/ext/quickjsrb/quickjsrb_file.c +253 -48
- data/lib/quickjs/crypto_key.rb +13 -0
- data/lib/quickjs/importable.rb +80 -0
- data/lib/quickjs/modules.rb +98 -0
- data/lib/quickjs/polyfills.rb +15 -1
- data/lib/quickjs/variables.rb +890 -0
- data/lib/quickjs/version.rb +1 -1
- data/lib/quickjs.rb +3 -0
- data/polyfills/package-lock.json +2 -2
- data/polyfills/package.json +1 -1
- data/sig/quickjs.rbs +20 -3
- metadata +4 -2
- data/ext/quickjsrb/quickjs/repl.c +0 -2057
|
@@ -6,10 +6,29 @@
|
|
|
6
6
|
static const char *js_get_algorithm_name(JSContext *ctx, JSValueConst j_algo)
|
|
7
7
|
{
|
|
8
8
|
if (JS_IsString(j_algo))
|
|
9
|
-
|
|
9
|
+
{
|
|
10
|
+
// Drained like the reads below: a conversion that cannot allocate answers
|
|
11
|
+
// NULL with its throw still set, and the caller reports a missing
|
|
12
|
+
// algorithm name over the top of it.
|
|
13
|
+
const char *name = JS_ToCString(ctx, j_algo);
|
|
14
|
+
if (name == NULL)
|
|
15
|
+
quickjsrb_drain_pending(ctx);
|
|
16
|
+
return name;
|
|
17
|
+
}
|
|
18
|
+
// A getter answers this read, so it can throw, and a throw from a bridge
|
|
19
|
+
// carries a host exception that only find_ruby_error takes back out of
|
|
20
|
+
// alive_objects. Same for the conversion below, which runs toString.
|
|
10
21
|
JSValue j_name = JS_GetPropertyStr(ctx, j_algo, "name");
|
|
22
|
+
if (JS_IsException(j_name))
|
|
23
|
+
{
|
|
24
|
+
quickjsrb_drain_pending(ctx);
|
|
25
|
+
return NULL;
|
|
26
|
+
}
|
|
27
|
+
|
|
11
28
|
const char *name = JS_ToCString(ctx, j_name);
|
|
12
29
|
JS_FreeValue(ctx, j_name);
|
|
30
|
+
if (name == NULL)
|
|
31
|
+
quickjsrb_drain_pending(ctx);
|
|
13
32
|
return name;
|
|
14
33
|
}
|
|
15
34
|
|
|
@@ -42,49 +61,295 @@ static VALUE js_buffer_to_ruby_str(JSContext *ctx, JSValueConst j_val)
|
|
|
42
61
|
}
|
|
43
62
|
|
|
44
63
|
// Build a Ruby Array of strings from a JS array value.
|
|
64
|
+
#define QUICKJSRB_MAX_KEY_USAGES 1024
|
|
65
|
+
|
|
66
|
+
// Qnil when the list is longer than any usages list can be, which the
|
|
67
|
+
// callers turn into a TypeError before they create a promise to reject.
|
|
45
68
|
static VALUE js_usages_to_ruby_array(JSContext *ctx, JSValueConst j_usages)
|
|
46
69
|
{
|
|
70
|
+
// Every read here is a property of an object the guest handed in, answerable
|
|
71
|
+
// by a getter that can reach a bridge, so each drains what it throws for the
|
|
72
|
+
// reason js_algo_read gives. What the operation then does with a usages list
|
|
73
|
+
// that could not be read is the refusal path nothing has yet: #130.
|
|
47
74
|
VALUE r_usages = rb_ary_new();
|
|
48
75
|
JSValue j_len = JS_GetPropertyStr(ctx, j_usages, "length");
|
|
76
|
+
if (JS_IsException(j_len))
|
|
77
|
+
{
|
|
78
|
+
quickjsrb_drain_pending(ctx);
|
|
79
|
+
return r_usages;
|
|
80
|
+
}
|
|
81
|
+
|
|
49
82
|
int32_t count = 0;
|
|
50
|
-
JS_ToInt32(ctx, &count, j_len)
|
|
83
|
+
if (JS_ToInt32(ctx, &count, j_len) < 0)
|
|
84
|
+
{
|
|
85
|
+
quickjsrb_drain_pending(ctx);
|
|
86
|
+
count = 0;
|
|
87
|
+
}
|
|
51
88
|
JS_FreeValue(ctx, j_len);
|
|
89
|
+
|
|
90
|
+
// The guest writes this length, and the loop below does a property read and
|
|
91
|
+
// a Ruby allocation per step without polling a QuickJS interrupt, so
|
|
92
|
+
// timeout_msec cannot end it: measured, a length of 10,000,000 runs 1.9s
|
|
93
|
+
// past a 100ms budget and leaves the VM out of memory, which a guest gets to
|
|
94
|
+
// choose by passing a number. There are eight key usages in the spec, so
|
|
95
|
+
// anything near this bound is not a usages list; refused rather than
|
|
96
|
+
// truncated, because a truncated one would be accepted as if the guest had
|
|
97
|
+
// asked for what is left.
|
|
98
|
+
// A negative length is an empty sequence, not too many entries: WebIDL's
|
|
99
|
+
// ToLength clamps it to 0, and main answered [] for it. JS_ToInt32 wraps, so
|
|
100
|
+
// 2**31 arrives here negative and means the same thing.
|
|
101
|
+
if (count < 0)
|
|
102
|
+
count = 0;
|
|
103
|
+
if (count > QUICKJSRB_MAX_KEY_USAGES)
|
|
104
|
+
return Qnil;
|
|
52
105
|
for (int32_t i = 0; i < count; i++)
|
|
53
106
|
{
|
|
54
107
|
JSValue j_u = JS_GetPropertyUint32(ctx, j_usages, (uint32_t)i);
|
|
108
|
+
if (JS_IsException(j_u))
|
|
109
|
+
{
|
|
110
|
+
quickjsrb_drain_pending(ctx);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
55
114
|
const char *u_str = JS_ToCString(ctx, j_u);
|
|
56
115
|
if (u_str)
|
|
57
116
|
rb_ary_push(r_usages, rb_str_new_cstr(u_str));
|
|
117
|
+
else
|
|
118
|
+
quickjsrb_drain_pending(ctx);
|
|
58
119
|
JS_FreeCString(ctx, u_str);
|
|
59
120
|
JS_FreeValue(ctx, j_u);
|
|
60
121
|
}
|
|
61
122
|
return r_usages;
|
|
62
123
|
}
|
|
63
124
|
|
|
125
|
+
// Settles with the value, or rejects with whatever stopped it being built.
|
|
126
|
+
// Never resolves with the sentinel: typeof reads "unknown" for it and the guest
|
|
127
|
+
// has no way to name, catch or discard what it was handed. Always settles, so
|
|
128
|
+
// no caller is left returning a promise nothing will ever resolve.
|
|
129
|
+
static void js_settle_or_reject(JSContext *ctx, JSValueConst *resolving_funcs, JSValue j_key)
|
|
130
|
+
{
|
|
131
|
+
JSValue j_settled;
|
|
132
|
+
if (JS_IsException(j_key))
|
|
133
|
+
{
|
|
134
|
+
// JS_GetException answers JS_UNINITIALIZED when nothing is set, and that
|
|
135
|
+
// is an internal sentinel, not a value to hand a guest. The slot can be
|
|
136
|
+
// empty here: a describe failure sets its throw, and a later step that
|
|
137
|
+
// takes a throw of its own unconditionally clears it on the way past.
|
|
138
|
+
if (!JS_HasException(ctx))
|
|
139
|
+
JS_ThrowInternalError(ctx, "quickjs: the operation failed without saying why");
|
|
140
|
+
JSValue j_thrown = JS_GetException(ctx);
|
|
141
|
+
j_settled = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1, (JSValueConst *)&j_thrown);
|
|
142
|
+
JS_FreeValue(ctx, j_thrown);
|
|
143
|
+
}
|
|
144
|
+
else
|
|
145
|
+
{
|
|
146
|
+
j_settled = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED, 1, (JSValueConst *)&j_key);
|
|
147
|
+
JS_FreeValue(ctx, j_key);
|
|
148
|
+
}
|
|
149
|
+
JS_FreeValue(ctx, j_settled);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// A rejected promise carrying a TypeError. These entry points are specified to
|
|
153
|
+
// always return a promise, so a refusal decided before the operation starts
|
|
154
|
+
// still has to arrive as a rejection rather than as a throw at the call: a
|
|
155
|
+
// guest writing importKey(...).catch(h) would otherwise never reach h.
|
|
156
|
+
static JSValue js_rejected_type_error(JSContext *ctx, const char *message)
|
|
157
|
+
{
|
|
158
|
+
JSValue resolving_funcs[2];
|
|
159
|
+
JSValue promise = JS_NewPromiseCapability(ctx, resolving_funcs);
|
|
160
|
+
if (JS_IsException(promise))
|
|
161
|
+
return JS_EXCEPTION;
|
|
162
|
+
|
|
163
|
+
JS_ThrowTypeError(ctx, "%s", message);
|
|
164
|
+
JSValue j_thrown = JS_GetException(ctx);
|
|
165
|
+
JSValue j_settled = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1, (JSValueConst *)&j_thrown);
|
|
166
|
+
JS_FreeValue(ctx, j_thrown);
|
|
167
|
+
JS_FreeValue(ctx, j_settled);
|
|
168
|
+
JS_FreeValue(ctx, resolving_funcs[0]);
|
|
169
|
+
JS_FreeValue(ctx, resolving_funcs[1]);
|
|
170
|
+
return promise;
|
|
171
|
+
}
|
|
172
|
+
|
|
64
173
|
// Reject a promise with a plain JS Error built from Ruby exception info.
|
|
65
174
|
static void js_reject_with_ruby_error(JSContext *ctx, JSValueConst *resolving_funcs)
|
|
66
175
|
{
|
|
67
176
|
VALUE r_error = rb_errinfo();
|
|
68
177
|
rb_set_errinfo(Qnil);
|
|
178
|
+
// Read and converted before anything is allocated, which is the ordering
|
|
179
|
+
// j_error_from_ruby_error follows: #message is host code and can raise
|
|
180
|
+
// anything, StringValueCStr raises on a non-String and on an embedded NUL,
|
|
181
|
+
// and a raise once j_err exists would longjmp out of a JSCFunction past it
|
|
182
|
+
// and past the promise capability the caller is holding.
|
|
69
183
|
VALUE r_message = rb_funcall(r_error, rb_intern("message"), 0);
|
|
184
|
+
StringValueCStr(r_message);
|
|
185
|
+
|
|
70
186
|
JSValue j_err = JS_NewError(ctx);
|
|
71
|
-
|
|
187
|
+
if (JS_IsException(j_err))
|
|
188
|
+
{
|
|
189
|
+
// Settling with the sentinel would hand the guest a value it cannot name,
|
|
190
|
+
// which is what js_quickjsrb_call_global refuses to do for the same reason.
|
|
191
|
+
// The runtime's own throw is the honest answer, and it is already pending.
|
|
192
|
+
// Nothing left to describe the failure with, so the promise is rejected
|
|
193
|
+
// with the runtime's own throw rather than left for a guest that is still
|
|
194
|
+
// awaiting it, and rather than carried out of here to be attributed to
|
|
195
|
+
// whatever asks for an exception next.
|
|
196
|
+
JS_FreeValue(ctx, j_err);
|
|
197
|
+
js_settle_or_reject(ctx, resolving_funcs, JS_EXCEPTION);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
// Defined, not assigned, for the reason j_error_from_ruby_error gives: an
|
|
201
|
+
// accessor on Error.prototype absorbs the write, and the guest reads whatever
|
|
202
|
+
// that getter says instead of why its call was rejected. Built first and
|
|
203
|
+
// checked, so the sentinel is never what the error says its message is.
|
|
204
|
+
JSValue j_message = JS_NewString(ctx, StringValueCStr(r_message));
|
|
205
|
+
if (JS_IsException(j_message))
|
|
206
|
+
{
|
|
207
|
+
JS_FreeValue(ctx, j_message);
|
|
208
|
+
JS_FreeValue(ctx, j_err);
|
|
209
|
+
js_settle_or_reject(ctx, resolving_funcs, JS_EXCEPTION);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (JS_DefinePropertyValueStr(ctx, j_err, "message", j_message,
|
|
213
|
+
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE) < 0)
|
|
214
|
+
{
|
|
215
|
+
// An Error with no own message is one Error.prototype answers for, which
|
|
216
|
+
// is what defining it was for, and the define's throw would be left to
|
|
217
|
+
// land on an unrelated evaluation.
|
|
218
|
+
JS_FreeValue(ctx, j_err);
|
|
219
|
+
js_settle_or_reject(ctx, resolving_funcs, JS_EXCEPTION);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
72
222
|
JSValue ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1, (JSValueConst *)&j_err);
|
|
73
223
|
JS_FreeValue(ctx, j_err);
|
|
74
224
|
JS_FreeValue(ctx, ret);
|
|
75
225
|
}
|
|
76
226
|
|
|
227
|
+
// Quickjs::CryptoKey, or Qnil while the Ruby layer that defines it has not been
|
|
228
|
+
// loaded — requiring the extension alone is enough to reach the callers below.
|
|
229
|
+
// Resolved without rb_const_get's raise, because these run inside QuickJS
|
|
230
|
+
// native callbacks with no rb_protect between them and the interpreter: a
|
|
231
|
+
// NameError there longjmps through QuickJS's own frames, past the JS values
|
|
232
|
+
// they still hold.
|
|
233
|
+
//
|
|
234
|
+
// Looked up per call rather than cached in a static. A class held only by a
|
|
235
|
+
// constant table is movable, so a cached VALUE survives GC.compact as an
|
|
236
|
+
// address the collector has since handed to something else — which would name
|
|
237
|
+
// the wrong class, or a non-class that makes rb_obj_is_kind_of raise the very
|
|
238
|
+
// unprotected raise this function exists to avoid. Two constant lookups are
|
|
239
|
+
// nothing beside the OpenSSL work every caller goes on to do.
|
|
240
|
+
static VALUE r_crypto_key_class(void)
|
|
241
|
+
{
|
|
242
|
+
// Gated too, though Init_quickjsrb defines it before any of this can run.
|
|
243
|
+
// Not for the promise, which every caller creates after resolving the key,
|
|
244
|
+
// but because a raise out of a JSCFunction unwinds past QuickJS's own call
|
|
245
|
+
// frame, which is what this function exists to answer instead of.
|
|
246
|
+
if (!rb_const_defined_at(rb_cObject, rb_intern("Quickjs")))
|
|
247
|
+
return Qnil;
|
|
248
|
+
// Defined answers true for an autoload here too, and rb_const_get would run
|
|
249
|
+
// its body from this callback, which is the raise being avoided.
|
|
250
|
+
if (!NIL_P(rb_autoload_p(rb_cObject, rb_intern("Quickjs"))))
|
|
251
|
+
return Qnil;
|
|
252
|
+
|
|
253
|
+
VALUE r_quickjs = rb_const_get(rb_cObject, rb_intern("Quickjs"));
|
|
254
|
+
// Same reason the inner one is type-checked: rb_const_defined_at reads a
|
|
255
|
+
// constant table off its receiver, so a non-module bound to the name is
|
|
256
|
+
// worse than the NameError being avoided.
|
|
257
|
+
if (!RB_TYPE_P(r_quickjs, T_MODULE))
|
|
258
|
+
return Qnil;
|
|
259
|
+
if (!rb_const_defined_at(r_quickjs, rb_intern("CryptoKey")))
|
|
260
|
+
return Qnil;
|
|
261
|
+
// Defined answers true for a registered autoload too, and rb_const_get would
|
|
262
|
+
// then run the autoload body here, which is a raise this function cannot
|
|
263
|
+
// afford for the reason above. Nobody autoloads this constant, but the claim
|
|
264
|
+
// has to hold rather than happen to.
|
|
265
|
+
if (!NIL_P(rb_autoload_p(r_quickjs, rb_intern("CryptoKey"))))
|
|
266
|
+
return Qnil;
|
|
267
|
+
|
|
268
|
+
VALUE r_class = rb_const_get(r_quickjs, rb_intern("CryptoKey"));
|
|
269
|
+
// Defined is not the same as a class. rb_obj_is_kind_of takes a class or a
|
|
270
|
+
// module and raises TypeError on anything else, so a String bound to the
|
|
271
|
+
// name would make the unprotected raise above; a Module would not raise but
|
|
272
|
+
// is not what a key is an instance of either. One predicate covers both.
|
|
273
|
+
return RB_TYPE_P(r_class, T_CLASS) ? r_class : Qnil;
|
|
274
|
+
}
|
|
275
|
+
|
|
77
276
|
// Find Ruby CryptoKey from a JS CryptoKey object via rb_object_id handle.
|
|
78
277
|
static VALUE r_find_alive_crypto_key(JSContext *ctx, JSValueConst j_key)
|
|
79
278
|
{
|
|
80
|
-
|
|
279
|
+
// Both of the reads below can run the guest's own code on a value it chose:
|
|
280
|
+
// a getter for the property, a valueOf for the conversion, either of which
|
|
281
|
+
// can reach a Ruby bridge. Nothing here can report what that raises, since
|
|
282
|
+
// this runs after its caller's rb_protect has returned, but the exception it
|
|
283
|
+
// parked in alive_objects has to come back out: find_ruby_error is the only
|
|
284
|
+
// thing that takes one out, so leaving it would pin one per call, at a rate
|
|
285
|
+
// the guest picks.
|
|
286
|
+
// Own data only, like the other two readers. Through the prototype chain an
|
|
287
|
+
// accessor on Object.prototype answers this, and then `{}` signs with a key
|
|
288
|
+
// it never carried a handle for, including one generated extractable: false.
|
|
289
|
+
JSValue j_handle = JS_UNDEFINED;
|
|
290
|
+
if (!j_read_own_handle(ctx, j_key, &j_handle))
|
|
291
|
+
{
|
|
292
|
+
// A Proxy trap can answer the descriptor read and throw, and what it throws
|
|
293
|
+
// can be a bridge that parked a host exception on its way out.
|
|
294
|
+
if (JS_IsException(j_handle))
|
|
295
|
+
quickjsrb_drain_pending(ctx);
|
|
296
|
+
return Qnil;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Only a number is a handle. Asking JS_ToInt64 for one is what runs valueOf,
|
|
300
|
+
// and the answer for anything else was never going to be a handle anyway.
|
|
301
|
+
int32_t tag = JS_VALUE_GET_NORM_TAG(j_handle);
|
|
302
|
+
if (tag != JS_TAG_INT && tag != JS_TAG_FLOAT64)
|
|
303
|
+
{
|
|
304
|
+
JS_FreeValue(ctx, j_handle);
|
|
305
|
+
return Qnil;
|
|
306
|
+
}
|
|
307
|
+
|
|
81
308
|
int64_t handle = 0;
|
|
82
309
|
JS_ToInt64(ctx, &handle, j_handle);
|
|
83
310
|
JS_FreeValue(ctx, j_handle);
|
|
84
311
|
if (handle <= 0)
|
|
85
312
|
return Qnil;
|
|
86
313
|
VMData *data = JS_GetContextOpaque(ctx);
|
|
87
|
-
|
|
314
|
+
VALUE r_key = rb_hash_aref(data->alive_objects, LL2NUM(handle));
|
|
315
|
+
// Before the class lookup, which is otherwise paid on the path a guest can
|
|
316
|
+
// repeat for free: sign('HMAC', {}, buf) reaches no OpenSSL work to dwarf it.
|
|
317
|
+
if (NIL_P(r_key))
|
|
318
|
+
return Qnil;
|
|
319
|
+
// The handle is read off whatever the guest passed as the key, and
|
|
320
|
+
// alive_objects also holds bridged exceptions and the Ruby object behind a
|
|
321
|
+
// File proxy. Without this an object carrying another entry's id reaches the
|
|
322
|
+
// operations below as if it were a key, and the caller is told about a
|
|
323
|
+
// method missing on a File rather than about an invalid CryptoKey.
|
|
324
|
+
VALUE r_key_class = r_crypto_key_class();
|
|
325
|
+
if (NIL_P(r_key_class) || !rb_obj_is_kind_of(r_key, r_key_class))
|
|
326
|
+
return Qnil;
|
|
327
|
+
return r_key;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Every read below is a property of an object the guest handed in, so a getter
|
|
331
|
+
// answers it and may throw. The block that follows each read already skips an
|
|
332
|
+
// exception, but skipping is not enough: a throw from a bridge carries a host
|
|
333
|
+
// exception that find_ruby_error is the only thing to take back out of
|
|
334
|
+
// alive_objects, so a skipped one pins it for the life of the VM, once per
|
|
335
|
+
// read, at a rate the guest picks.
|
|
336
|
+
//
|
|
337
|
+
// The read only. What each block then does with the value, JS_ToInt32 on a
|
|
338
|
+
// length, JS_ToCString on a curve name, a buffer conversion on an iv, runs the
|
|
339
|
+
// guest's valueOf or toString and can throw for the same reason, and those
|
|
340
|
+
// returns are unchecked here as they were before. #130.
|
|
341
|
+
static JSValue js_algo_read(JSContext *ctx, JSValueConst j_algo, const char *name)
|
|
342
|
+
{
|
|
343
|
+
JSValue j_value = JS_GetPropertyStr(ctx, j_algo, name);
|
|
344
|
+
if (JS_IsException(j_value))
|
|
345
|
+
// Taken, not put back. Putting it back and carrying on would leave it
|
|
346
|
+
// pending for the next thing that asks, and carrying on is what this does:
|
|
347
|
+
// the block below skips the property and the operation runs without it. So
|
|
348
|
+
// the guest's own error is lost here, replaced by whatever the call fails
|
|
349
|
+
// with next. Both halves want the same fix, a way for this function to
|
|
350
|
+
// refuse, which its ten callers do not have yet. #130.
|
|
351
|
+
quickjsrb_drain_pending(ctx);
|
|
352
|
+
return j_value;
|
|
88
353
|
}
|
|
89
354
|
|
|
90
355
|
// Build a comprehensive Ruby Hash (symbol keys) from a JS algorithm object.
|
|
@@ -103,7 +368,7 @@ static VALUE js_algo_to_ruby_hash(JSContext *ctx, JSValueConst j_algo)
|
|
|
103
368
|
if (JS_IsString(j_algo))
|
|
104
369
|
return r_hash;
|
|
105
370
|
|
|
106
|
-
JSValue j_length =
|
|
371
|
+
JSValue j_length = js_algo_read(ctx, j_algo, "length");
|
|
107
372
|
if (!JS_IsUndefined(j_length) && !JS_IsException(j_length))
|
|
108
373
|
{
|
|
109
374
|
int32_t length = 0;
|
|
@@ -112,7 +377,7 @@ static VALUE js_algo_to_ruby_hash(JSContext *ctx, JSValueConst j_algo)
|
|
|
112
377
|
}
|
|
113
378
|
JS_FreeValue(ctx, j_length);
|
|
114
379
|
|
|
115
|
-
JSValue j_named_curve =
|
|
380
|
+
JSValue j_named_curve = js_algo_read(ctx, j_algo, "namedCurve");
|
|
116
381
|
if (!JS_IsUndefined(j_named_curve) && !JS_IsException(j_named_curve))
|
|
117
382
|
{
|
|
118
383
|
const char *nc = JS_ToCString(ctx, j_named_curve);
|
|
@@ -124,7 +389,7 @@ static VALUE js_algo_to_ruby_hash(JSContext *ctx, JSValueConst j_algo)
|
|
|
124
389
|
}
|
|
125
390
|
JS_FreeValue(ctx, j_named_curve);
|
|
126
391
|
|
|
127
|
-
JSValue j_hash =
|
|
392
|
+
JSValue j_hash = js_algo_read(ctx, j_algo, "hash");
|
|
128
393
|
if (!JS_IsUndefined(j_hash) && !JS_IsException(j_hash))
|
|
129
394
|
{
|
|
130
395
|
const char *hash_name = js_get_algorithm_name(ctx, j_hash);
|
|
@@ -136,7 +401,7 @@ static VALUE js_algo_to_ruby_hash(JSContext *ctx, JSValueConst j_algo)
|
|
|
136
401
|
}
|
|
137
402
|
JS_FreeValue(ctx, j_hash);
|
|
138
403
|
|
|
139
|
-
JSValue j_modulus_length =
|
|
404
|
+
JSValue j_modulus_length = js_algo_read(ctx, j_algo, "modulusLength");
|
|
140
405
|
if (!JS_IsUndefined(j_modulus_length) && !JS_IsException(j_modulus_length))
|
|
141
406
|
{
|
|
142
407
|
int32_t mod_len = 0;
|
|
@@ -145,7 +410,7 @@ static VALUE js_algo_to_ruby_hash(JSContext *ctx, JSValueConst j_algo)
|
|
|
145
410
|
}
|
|
146
411
|
JS_FreeValue(ctx, j_modulus_length);
|
|
147
412
|
|
|
148
|
-
JSValue j_pub_exp =
|
|
413
|
+
JSValue j_pub_exp = js_algo_read(ctx, j_algo, "publicExponent");
|
|
149
414
|
if (!JS_IsUndefined(j_pub_exp) && !JS_IsException(j_pub_exp))
|
|
150
415
|
{
|
|
151
416
|
VALUE r_pe = js_buffer_to_ruby_str(ctx, j_pub_exp);
|
|
@@ -154,7 +419,7 @@ static VALUE js_algo_to_ruby_hash(JSContext *ctx, JSValueConst j_algo)
|
|
|
154
419
|
}
|
|
155
420
|
JS_FreeValue(ctx, j_pub_exp);
|
|
156
421
|
|
|
157
|
-
JSValue j_iv =
|
|
422
|
+
JSValue j_iv = js_algo_read(ctx, j_algo, "iv");
|
|
158
423
|
if (!JS_IsUndefined(j_iv) && !JS_IsException(j_iv))
|
|
159
424
|
{
|
|
160
425
|
VALUE r_iv = js_buffer_to_ruby_str(ctx, j_iv);
|
|
@@ -163,7 +428,7 @@ static VALUE js_algo_to_ruby_hash(JSContext *ctx, JSValueConst j_algo)
|
|
|
163
428
|
}
|
|
164
429
|
JS_FreeValue(ctx, j_iv);
|
|
165
430
|
|
|
166
|
-
JSValue j_tag_length =
|
|
431
|
+
JSValue j_tag_length = js_algo_read(ctx, j_algo, "tagLength");
|
|
167
432
|
if (!JS_IsUndefined(j_tag_length) && !JS_IsException(j_tag_length))
|
|
168
433
|
{
|
|
169
434
|
int32_t tag_length = 0;
|
|
@@ -172,7 +437,7 @@ static VALUE js_algo_to_ruby_hash(JSContext *ctx, JSValueConst j_algo)
|
|
|
172
437
|
}
|
|
173
438
|
JS_FreeValue(ctx, j_tag_length);
|
|
174
439
|
|
|
175
|
-
JSValue j_additional_data =
|
|
440
|
+
JSValue j_additional_data = js_algo_read(ctx, j_algo, "additionalData");
|
|
176
441
|
if (!JS_IsUndefined(j_additional_data) && !JS_IsException(j_additional_data))
|
|
177
442
|
{
|
|
178
443
|
VALUE r_ad = js_buffer_to_ruby_str(ctx, j_additional_data);
|
|
@@ -181,7 +446,7 @@ static VALUE js_algo_to_ruby_hash(JSContext *ctx, JSValueConst j_algo)
|
|
|
181
446
|
}
|
|
182
447
|
JS_FreeValue(ctx, j_additional_data);
|
|
183
448
|
|
|
184
|
-
JSValue j_counter =
|
|
449
|
+
JSValue j_counter = js_algo_read(ctx, j_algo, "counter");
|
|
185
450
|
if (!JS_IsUndefined(j_counter) && !JS_IsException(j_counter))
|
|
186
451
|
{
|
|
187
452
|
VALUE r_counter = js_buffer_to_ruby_str(ctx, j_counter);
|
|
@@ -190,7 +455,7 @@ static VALUE js_algo_to_ruby_hash(JSContext *ctx, JSValueConst j_algo)
|
|
|
190
455
|
}
|
|
191
456
|
JS_FreeValue(ctx, j_counter);
|
|
192
457
|
|
|
193
|
-
JSValue j_salt =
|
|
458
|
+
JSValue j_salt = js_algo_read(ctx, j_algo, "salt");
|
|
194
459
|
if (!JS_IsUndefined(j_salt) && !JS_IsException(j_salt))
|
|
195
460
|
{
|
|
196
461
|
VALUE r_salt = js_buffer_to_ruby_str(ctx, j_salt);
|
|
@@ -199,7 +464,7 @@ static VALUE js_algo_to_ruby_hash(JSContext *ctx, JSValueConst j_algo)
|
|
|
199
464
|
}
|
|
200
465
|
JS_FreeValue(ctx, j_salt);
|
|
201
466
|
|
|
202
|
-
JSValue j_iterations =
|
|
467
|
+
JSValue j_iterations = js_algo_read(ctx, j_algo, "iterations");
|
|
203
468
|
if (!JS_IsUndefined(j_iterations) && !JS_IsException(j_iterations))
|
|
204
469
|
{
|
|
205
470
|
int32_t iterations = 0;
|
|
@@ -208,7 +473,7 @@ static VALUE js_algo_to_ruby_hash(JSContext *ctx, JSValueConst j_algo)
|
|
|
208
473
|
}
|
|
209
474
|
JS_FreeValue(ctx, j_iterations);
|
|
210
475
|
|
|
211
|
-
JSValue j_info =
|
|
476
|
+
JSValue j_info = js_algo_read(ctx, j_algo, "info");
|
|
212
477
|
if (!JS_IsUndefined(j_info) && !JS_IsException(j_info))
|
|
213
478
|
{
|
|
214
479
|
VALUE r_info = js_buffer_to_ruby_str(ctx, j_info);
|
|
@@ -217,7 +482,7 @@ static VALUE js_algo_to_ruby_hash(JSContext *ctx, JSValueConst j_algo)
|
|
|
217
482
|
}
|
|
218
483
|
JS_FreeValue(ctx, j_info);
|
|
219
484
|
|
|
220
|
-
JSValue j_salt_length =
|
|
485
|
+
JSValue j_salt_length = js_algo_read(ctx, j_algo, "saltLength");
|
|
221
486
|
if (!JS_IsUndefined(j_salt_length) && !JS_IsException(j_salt_length))
|
|
222
487
|
{
|
|
223
488
|
int32_t salt_length = 0;
|
|
@@ -226,7 +491,7 @@ static VALUE js_algo_to_ruby_hash(JSContext *ctx, JSValueConst j_algo)
|
|
|
226
491
|
}
|
|
227
492
|
JS_FreeValue(ctx, j_salt_length);
|
|
228
493
|
|
|
229
|
-
JSValue j_label =
|
|
494
|
+
JSValue j_label = js_algo_read(ctx, j_algo, "label");
|
|
230
495
|
if (!JS_IsUndefined(j_label) && !JS_IsException(j_label))
|
|
231
496
|
{
|
|
232
497
|
VALUE r_label = js_buffer_to_ruby_str(ctx, j_label);
|
|
@@ -235,7 +500,7 @@ static VALUE js_algo_to_ruby_hash(JSContext *ctx, JSValueConst j_algo)
|
|
|
235
500
|
}
|
|
236
501
|
JS_FreeValue(ctx, j_label);
|
|
237
502
|
|
|
238
|
-
JSValue j_public =
|
|
503
|
+
JSValue j_public = js_algo_read(ctx, j_algo, "public");
|
|
239
504
|
if (!JS_IsUndefined(j_public) && !JS_IsException(j_public) && JS_IsObject(j_public))
|
|
240
505
|
{
|
|
241
506
|
VALUE r_public = r_find_alive_crypto_key(ctx, j_public);
|
|
@@ -247,77 +512,297 @@ static VALUE js_algo_to_ruby_hash(JSContext *ctx, JSValueConst j_algo)
|
|
|
247
512
|
return r_hash;
|
|
248
513
|
}
|
|
249
514
|
|
|
250
|
-
|
|
251
|
-
//
|
|
252
|
-
|
|
515
|
+
|
|
516
|
+
// Builds the string and defines it, reporting whether it got that far, so a
|
|
517
|
+
// caller can throw away a half-described object rather than define the
|
|
518
|
+
// allocation-failure sentinel as an ordinary property value. Defined, not
|
|
519
|
+
// assigned, for the reason js_crypto_key_to_js gives.
|
|
520
|
+
static bool j_define_str(JSContext *ctx, JSValue j_obj, const char *prop, VALUE r_str)
|
|
521
|
+
{
|
|
522
|
+
JSValue j_val = JS_NewString(ctx, StringValueCStr(r_str));
|
|
523
|
+
if (JS_IsException(j_val))
|
|
524
|
+
{
|
|
525
|
+
JS_FreeValue(ctx, j_val);
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
return JS_DefinePropertyValueStr(ctx, j_obj, prop, j_val, JS_PROP_C_W_E) >= 0;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Build a JS CryptoKey plain object from a Ruby Quickjs::CryptoKey. Stores the
|
|
532
|
+
// Ruby object in alive_objects; sets rb_object_id as non-enumerable.
|
|
533
|
+
//
|
|
534
|
+
// registered_here, when given, says whether this call is the one that put the
|
|
535
|
+
// key in alive_objects, so a caller that has to throw the key away can take
|
|
536
|
+
// exactly the row it caused and not one an earlier crossing still relies on.
|
|
537
|
+
static JSValue js_crypto_key_to_js(JSContext *ctx, VALUE r_key, bool *registered_here_out)
|
|
253
538
|
{
|
|
254
539
|
VMData *data = JS_GetContextOpaque(ctx);
|
|
255
|
-
VALUE r_object_id = rb_funcall(r_key, rb_intern("object_id"), 0);
|
|
256
|
-
rb_hash_aset(data->alive_objects, r_object_id, r_key);
|
|
257
|
-
int64_t handle = NUM2LONG(r_object_id);
|
|
258
540
|
|
|
541
|
+
// Nothing is registered until the key is fully described, which is the
|
|
542
|
+
// ordering j_error_from_ruby_error follows and the only version of it that
|
|
543
|
+
// holds: the reads below raise, and so does describing them, since
|
|
544
|
+
// StringValueCStr rejects a non-String and a NUL, and NUM2INT rejects a
|
|
545
|
+
// non-Integer. A raise out of a JSCFunction after registering would leave a
|
|
546
|
+
// row anchoring this key for the life of the VM with nothing able to reach
|
|
547
|
+
// it. With the draw last, there is nothing to leave.
|
|
259
548
|
VALUE r_type = rb_funcall(r_key, rb_intern("type"), 0);
|
|
260
549
|
VALUE r_extractable = rb_funcall(r_key, rb_intern("extractable"), 0);
|
|
261
550
|
VALUE r_algorithm = rb_funcall(r_key, rb_intern("algorithm"), 0);
|
|
262
551
|
VALUE r_usages = rb_funcall(r_key, rb_intern("usages"), 0);
|
|
263
552
|
|
|
553
|
+
VALUE r_algo_name = rb_hash_aref(r_algorithm, rb_str_new_cstr("name"));
|
|
554
|
+
VALUE r_algo_length = rb_hash_aref(r_algorithm, rb_str_new_cstr("length"));
|
|
555
|
+
VALUE r_algo_named_curve = rb_hash_aref(r_algorithm, rb_str_new_cstr("namedCurve"));
|
|
556
|
+
VALUE r_algo_hash = rb_hash_aref(r_algorithm, rb_str_new_cstr("hash"));
|
|
557
|
+
VALUE r_algo_modulus_length = rb_hash_aref(r_algorithm, rb_str_new_cstr("modulusLength"));
|
|
558
|
+
VALUE r_algo_pub_exp = rb_hash_aref(r_algorithm, rb_str_new_cstr("publicExponent"));
|
|
559
|
+
long usages_len = RARRAY_LEN(r_usages);
|
|
560
|
+
|
|
561
|
+
// Touched for their conversions, not their values: StringValueCStr raises on
|
|
562
|
+
// a non-String and on an embedded NUL, NUM2INT on a non-Integer. Doing that
|
|
563
|
+
// here means the describing below cannot raise, which is what lets the draw
|
|
564
|
+
// sit between the two. Both orderings cost something otherwise: draw first
|
|
565
|
+
// and a raise from describing orphans a table row that anchors this key, with
|
|
566
|
+
// its private material, for the life of the VM; draw last and a raise from
|
|
567
|
+
// the draw leaves j_key, and the promise capability the caller is holding,
|
|
568
|
+
// unreleased. With nothing between them able to raise, neither is reachable.
|
|
569
|
+
StringValueCStr(r_type);
|
|
570
|
+
StringValueCStr(r_algo_name);
|
|
571
|
+
if (!NIL_P(r_algo_length))
|
|
572
|
+
NUM2INT(r_algo_length);
|
|
573
|
+
if (!NIL_P(r_algo_named_curve))
|
|
574
|
+
StringValueCStr(r_algo_named_curve);
|
|
575
|
+
if (!NIL_P(r_algo_hash))
|
|
576
|
+
StringValueCStr(r_algo_hash);
|
|
577
|
+
if (!NIL_P(r_algo_modulus_length))
|
|
578
|
+
NUM2INT(r_algo_modulus_length);
|
|
579
|
+
if (!NIL_P(r_algo_pub_exp))
|
|
580
|
+
StringValue(r_algo_pub_exp);
|
|
581
|
+
// Into a list of our own, because StringValueCStr converts the local it is
|
|
582
|
+
// given and rb_ary_entry hands back a temporary: touching that would leave
|
|
583
|
+
// the caller's array holding the unconverted member, and the describing loop
|
|
584
|
+
// would convert it a second time, after the draw and with j_key allocated,
|
|
585
|
+
// which is the raise the ordering above exists to rule out.
|
|
586
|
+
VALUE r_usage_strings = rb_ary_new_capa(usages_len);
|
|
587
|
+
for (long i = 0; i < usages_len; i++)
|
|
588
|
+
{
|
|
589
|
+
VALUE r_usage = rb_ary_entry(r_usages, i);
|
|
590
|
+
StringValueCStr(r_usage);
|
|
591
|
+
rb_ary_push(r_usage_strings, r_usage);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Taken here, in the same raise-safe stretch, so the rollback paths below can
|
|
595
|
+
// undo a registration without dispatching from inside a JSCFunction.
|
|
596
|
+
VALUE r_key_object_id = rb_obj_id(r_key);
|
|
597
|
+
|
|
598
|
+
// Drawn here, with everything that can raise behind it and nothing allocated
|
|
599
|
+
// in front of it.
|
|
600
|
+
bool registered_here = false;
|
|
601
|
+
VALUE r_object_id = alive_objects_register(data, r_key, ®istered_here);
|
|
602
|
+
if (registered_here_out != NULL)
|
|
603
|
+
*registered_here_out = registered_here;
|
|
604
|
+
if (NIL_P(r_object_id))
|
|
605
|
+
return JS_ThrowInternalError(ctx, "quickjs: could not publish a handle for a CryptoKey");
|
|
606
|
+
|
|
607
|
+
// Checked, like every value that becomes a property of the key: passing the
|
|
608
|
+
// sentinel to JS_DefinePropertyValue* stores it as an ordinary value, which
|
|
609
|
+
// is the shape the rest of this branch refuses.
|
|
264
610
|
JSValue j_key = JS_NewObject(ctx);
|
|
611
|
+
if (JS_IsException(j_key))
|
|
612
|
+
{
|
|
613
|
+
if (registered_here)
|
|
614
|
+
{
|
|
615
|
+
alive_objects_unregister(data, r_object_id, r_key_object_id);
|
|
616
|
+
// The claim goes with the row: a caller told the row is theirs would ask
|
|
617
|
+
// alive_objects_forget for one that is already gone.
|
|
618
|
+
registered_here = false;
|
|
619
|
+
if (registered_here_out != NULL)
|
|
620
|
+
*registered_here_out = false;
|
|
621
|
+
}
|
|
622
|
+
return j_key;
|
|
623
|
+
}
|
|
265
624
|
|
|
266
|
-
|
|
267
|
-
|
|
625
|
+
bool described = j_define_str(ctx, j_key, "type", r_type);
|
|
626
|
+
described = JS_DefinePropertyValueStr(ctx, j_key, "extractable", JS_NewBool(ctx, RTEST(r_extractable)),
|
|
627
|
+
JS_PROP_WRITABLE | JS_PROP_ENUMERABLE | JS_PROP_CONFIGURABLE) >= 0 && described;
|
|
268
628
|
|
|
629
|
+
// Every one of these is defined rather than assigned: an accessor a guest
|
|
630
|
+
// puts on Object.prototype otherwise absorbs the write, and the key ends up
|
|
631
|
+
// describing itself with whatever that getter says, including reporting
|
|
632
|
+
// extractable: true for a key generated without it.
|
|
269
633
|
JSValue j_algo = JS_NewObject(ctx);
|
|
634
|
+
if (JS_IsException(j_algo))
|
|
635
|
+
{
|
|
636
|
+
if (registered_here)
|
|
637
|
+
{
|
|
638
|
+
alive_objects_unregister(data, r_object_id, r_key_object_id);
|
|
639
|
+
// The claim goes with the row: a caller told the row is theirs would ask
|
|
640
|
+
// alive_objects_forget for one that is already gone.
|
|
641
|
+
registered_here = false;
|
|
642
|
+
if (registered_here_out != NULL)
|
|
643
|
+
*registered_here_out = false;
|
|
644
|
+
}
|
|
645
|
+
JS_FreeValue(ctx, j_key);
|
|
646
|
+
return j_algo;
|
|
647
|
+
}
|
|
270
648
|
|
|
271
|
-
|
|
272
|
-
JS_SetPropertyStr(ctx, j_algo, "name", JS_NewString(ctx, StringValueCStr(r_algo_name)));
|
|
649
|
+
described = j_define_str(ctx, j_algo, "name", r_algo_name) && described;
|
|
273
650
|
|
|
274
|
-
VALUE r_algo_length = rb_hash_aref(r_algorithm, rb_str_new_cstr("length"));
|
|
275
651
|
if (!NIL_P(r_algo_length))
|
|
276
|
-
|
|
652
|
+
described = JS_DefinePropertyValueStr(ctx, j_algo, "length", JS_NewInt32(ctx, NUM2INT(r_algo_length)),
|
|
653
|
+
JS_PROP_WRITABLE | JS_PROP_ENUMERABLE | JS_PROP_CONFIGURABLE) >= 0 && described;
|
|
277
654
|
|
|
278
|
-
VALUE r_algo_named_curve = rb_hash_aref(r_algorithm, rb_str_new_cstr("namedCurve"));
|
|
279
655
|
if (!NIL_P(r_algo_named_curve))
|
|
280
|
-
|
|
656
|
+
described = j_define_str(ctx, j_algo, "namedCurve", r_algo_named_curve) && described;
|
|
281
657
|
|
|
282
|
-
VALUE r_algo_hash = rb_hash_aref(r_algorithm, rb_str_new_cstr("hash"));
|
|
283
658
|
if (!NIL_P(r_algo_hash))
|
|
284
659
|
{
|
|
285
660
|
JSValue j_hash_obj = JS_NewObject(ctx);
|
|
286
|
-
|
|
287
|
-
|
|
661
|
+
if (JS_IsException(j_hash_obj))
|
|
662
|
+
{
|
|
663
|
+
if (registered_here)
|
|
664
|
+
{
|
|
665
|
+
alive_objects_unregister(data, r_object_id, r_key_object_id);
|
|
666
|
+
registered_here = false;
|
|
667
|
+
if (registered_here_out != NULL)
|
|
668
|
+
*registered_here_out = false;
|
|
669
|
+
}
|
|
670
|
+
JS_FreeValue(ctx, j_algo);
|
|
671
|
+
JS_FreeValue(ctx, j_key);
|
|
672
|
+
return j_hash_obj;
|
|
673
|
+
}
|
|
674
|
+
described = j_define_str(ctx, j_hash_obj, "name", r_algo_hash) && described;
|
|
675
|
+
described = JS_DefinePropertyValueStr(ctx, j_algo, "hash", j_hash_obj,
|
|
676
|
+
JS_PROP_WRITABLE | JS_PROP_ENUMERABLE | JS_PROP_CONFIGURABLE) >= 0 && described;
|
|
288
677
|
}
|
|
289
678
|
|
|
290
|
-
VALUE r_algo_modulus_length = rb_hash_aref(r_algorithm, rb_str_new_cstr("modulusLength"));
|
|
291
679
|
if (!NIL_P(r_algo_modulus_length))
|
|
292
|
-
|
|
680
|
+
described = JS_DefinePropertyValueStr(ctx, j_algo, "modulusLength", JS_NewInt32(ctx, NUM2INT(r_algo_modulus_length)),
|
|
681
|
+
JS_PROP_WRITABLE | JS_PROP_ENUMERABLE | JS_PROP_CONFIGURABLE) >= 0 && described;
|
|
293
682
|
|
|
294
|
-
VALUE r_algo_pub_exp = rb_hash_aref(r_algorithm, rb_str_new_cstr("publicExponent"));
|
|
295
683
|
if (!NIL_P(r_algo_pub_exp))
|
|
296
684
|
{
|
|
685
|
+
// Built through JS_NewTypedArray rather than by calling globalThis's
|
|
686
|
+
// Uint8Array. That constructor is the guest's to delete or replace, and a
|
|
687
|
+
// replacement can throw anything it likes, including a Ruby exception from
|
|
688
|
+
// a bridge: nothing here can report it, because this runs after its
|
|
689
|
+
// caller's rb_protect has returned and every caller stores the result
|
|
690
|
+
// unchecked. Not reading the global removes the question rather than
|
|
691
|
+
// answering it. What remains is the runtime's own allocation failing, and
|
|
692
|
+
// both branches below take that rather than leave it: no caller of this
|
|
693
|
+
// function returns JS_EXCEPTION, so a throw left set would be attributed to
|
|
694
|
+
// whatever evaluation next asked for one. The cost is that a key can come
|
|
695
|
+
// back describing itself without publicExponent, with the out-of-memory
|
|
696
|
+
// reported nowhere. #129.
|
|
297
697
|
JSValue j_pe_buf = JS_NewArrayBufferCopy(ctx,
|
|
298
698
|
(const uint8_t *)RSTRING_PTR(r_algo_pub_exp),
|
|
299
699
|
RSTRING_LEN(r_algo_pub_exp));
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
700
|
+
if (JS_IsException(j_pe_buf))
|
|
701
|
+
{
|
|
702
|
+
// Taken here for the reason the branch below gives: the key is still
|
|
703
|
+
// handed over without a publicExponent, so leaving this set would
|
|
704
|
+
// attribute the allocation failure to whatever asked next.
|
|
705
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
706
|
+
}
|
|
707
|
+
else
|
|
708
|
+
{
|
|
709
|
+
JSValue j_args[3] = {j_pe_buf, JS_NewInt32(ctx, 0), JS_NewInt64(ctx, RSTRING_LEN(r_algo_pub_exp))};
|
|
710
|
+
JSValue j_pe = JS_NewTypedArray(ctx, 3, (JSValueConst *)j_args, JS_TYPED_ARRAY_UINT8);
|
|
711
|
+
JS_FreeValue(ctx, j_pe_buf);
|
|
712
|
+
if (JS_IsException(j_pe))
|
|
713
|
+
// Taken and lost, because the key is still described well enough to
|
|
714
|
+
// hand over and a throw left here would surface at whatever calls
|
|
715
|
+
// JS_GetException next. It can be a throw an earlier describe set, so
|
|
716
|
+
// js_settle_or_reject no longer assumes the slot is full. The only way
|
|
717
|
+
// to get here is the runtime's own allocation failing, which the next
|
|
718
|
+
// one will fail at too.
|
|
719
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
720
|
+
else
|
|
721
|
+
described = JS_DefinePropertyValueStr(ctx, j_algo, "publicExponent", j_pe,
|
|
722
|
+
JS_PROP_WRITABLE | JS_PROP_ENUMERABLE | JS_PROP_CONFIGURABLE) >= 0 && described;
|
|
723
|
+
}
|
|
305
724
|
}
|
|
306
725
|
|
|
307
|
-
|
|
726
|
+
described = JS_DefinePropertyValueStr(ctx, j_key, "algorithm", j_algo,
|
|
727
|
+
JS_PROP_WRITABLE | JS_PROP_ENUMERABLE | JS_PROP_CONFIGURABLE) >= 0 && described;
|
|
308
728
|
|
|
309
|
-
long usages_len = RARRAY_LEN(r_usages);
|
|
310
729
|
JSValue j_usages = JS_NewArray(ctx);
|
|
730
|
+
if (JS_IsException(j_usages))
|
|
731
|
+
{
|
|
732
|
+
if (registered_here)
|
|
733
|
+
{
|
|
734
|
+
alive_objects_unregister(data, r_object_id, r_key_object_id);
|
|
735
|
+
// The claim goes with the row: a caller told the row is theirs would ask
|
|
736
|
+
// alive_objects_forget for one that is already gone.
|
|
737
|
+
registered_here = false;
|
|
738
|
+
if (registered_here_out != NULL)
|
|
739
|
+
*registered_here_out = false;
|
|
740
|
+
}
|
|
741
|
+
JS_FreeValue(ctx, j_key);
|
|
742
|
+
return j_usages;
|
|
743
|
+
}
|
|
311
744
|
for (long i = 0; i < usages_len; i++)
|
|
312
745
|
{
|
|
313
|
-
VALUE r_usage = rb_ary_entry(
|
|
314
|
-
|
|
746
|
+
VALUE r_usage = rb_ary_entry(r_usage_strings, i);
|
|
747
|
+
// Defined, like every other property of the key: an index property on
|
|
748
|
+
// Array.prototype takes the fast array path away, and the element write
|
|
749
|
+
// then walks the chain into the guest's setter like any named one would.
|
|
750
|
+
// StringValueCStr cannot raise here: it is a String already, converted
|
|
751
|
+
// above, before anything was drawn or allocated.
|
|
752
|
+
JSValue j_usage = JS_NewString(ctx, StringValueCStr(r_usage));
|
|
753
|
+
if (JS_IsException(j_usage))
|
|
754
|
+
{
|
|
755
|
+
JS_FreeValue(ctx, j_usage);
|
|
756
|
+
described = false;
|
|
757
|
+
}
|
|
758
|
+
else
|
|
759
|
+
{
|
|
760
|
+
described = JS_DefinePropertyValueUint32(ctx, j_usages, (uint32_t)i, j_usage, JS_PROP_C_W_E) >= 0 && described;
|
|
761
|
+
}
|
|
315
762
|
}
|
|
316
|
-
|
|
763
|
+
described = JS_DefinePropertyValueStr(ctx, j_key, "usages", j_usages,
|
|
764
|
+
JS_PROP_WRITABLE | JS_PROP_ENUMERABLE | JS_PROP_CONFIGURABLE) >= 0 && described;
|
|
317
765
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
766
|
+
if (!described)
|
|
767
|
+
{
|
|
768
|
+
// A key that cannot say what it is, is not one to hand over: the runtime's
|
|
769
|
+
// own throw is already pending from whichever allocation failed. The row
|
|
770
|
+
// goes with it, since the draw is behind us now: left there it would anchor
|
|
771
|
+
// this key, key_data included, for the life of the VM with nothing in JS
|
|
772
|
+
// able to reach it. Only what this call made, because the same key crossing
|
|
773
|
+
// twice reuses its handle and the earlier crossing still relies on that row.
|
|
774
|
+
if (registered_here)
|
|
775
|
+
{
|
|
776
|
+
alive_objects_unregister(data, r_object_id, r_key_object_id);
|
|
777
|
+
// The claim goes with the row: a caller told the row is theirs would ask
|
|
778
|
+
// alive_objects_forget for one that is already gone.
|
|
779
|
+
registered_here = false;
|
|
780
|
+
if (registered_here_out != NULL)
|
|
781
|
+
*registered_here_out = false;
|
|
782
|
+
}
|
|
783
|
+
JS_FreeValue(ctx, j_key);
|
|
784
|
+
return JS_EXCEPTION;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// A key that looks like a key and carries no handle is worse than no key:
|
|
788
|
+
// every later sign or exportKey on it fails as an invalid key while the row
|
|
789
|
+
// keeps anchoring the Ruby object.
|
|
790
|
+
if (JS_DefinePropertyValueStr(ctx, j_key, "rb_object_id",
|
|
791
|
+
JS_NewInt64(ctx, NUM2LL(r_object_id)),
|
|
792
|
+
JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE) < 0)
|
|
793
|
+
{
|
|
794
|
+
// Only what this call put there: the same key object crossing twice reuses
|
|
795
|
+
// its handle, and taking that row would unanchor the key the earlier
|
|
796
|
+
// crossing handed the guest.
|
|
797
|
+
if (registered_here)
|
|
798
|
+
{
|
|
799
|
+
alive_objects_unregister(data, r_object_id, r_key_object_id);
|
|
800
|
+
if (registered_here_out != NULL)
|
|
801
|
+
*registered_here_out = false;
|
|
802
|
+
}
|
|
803
|
+
JS_FreeValue(ctx, j_key);
|
|
804
|
+
return JS_EXCEPTION;
|
|
805
|
+
}
|
|
321
806
|
|
|
322
807
|
return j_key;
|
|
323
808
|
}
|
|
@@ -389,6 +874,8 @@ static JSValue js_subtle_generate_key(JSContext *ctx, JSValueConst this_val, int
|
|
|
389
874
|
VALUE r_algo_hash = js_algo_to_ruby_hash(ctx, argv[0]);
|
|
390
875
|
VALUE r_extractable = JS_ToBool(ctx, argv[1]) ? Qtrue : Qfalse;
|
|
391
876
|
VALUE r_usages = js_usages_to_ruby_array(ctx, argv[2]);
|
|
877
|
+
if (NIL_P(r_usages))
|
|
878
|
+
return js_rejected_type_error(ctx, "SubtleCrypto: keyUsages has more entries than a key can have");
|
|
392
879
|
|
|
393
880
|
JSValue promise, resolving_funcs[2];
|
|
394
881
|
promise = JS_NewPromiseCapability(ctx, resolving_funcs);
|
|
@@ -407,21 +894,45 @@ static JSValue js_subtle_generate_key(JSContext *ctx, JSValueConst this_val, int
|
|
|
407
894
|
{
|
|
408
895
|
VALUE r_priv = rb_hash_aref(r_result, ID2SYM(rb_intern("private_key")));
|
|
409
896
|
VALUE r_pub = rb_hash_aref(r_result, ID2SYM(rb_intern("public_key")));
|
|
897
|
+
bool priv_registered_here = false;
|
|
898
|
+
bool pub_registered_here = false;
|
|
899
|
+
JSValue j_priv = js_crypto_key_to_js(ctx, r_priv, &priv_registered_here);
|
|
900
|
+
JSValue j_pub = js_crypto_key_to_js(ctx, r_pub, &pub_registered_here);
|
|
410
901
|
JSValue j_pair = JS_NewObject(ctx);
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
902
|
+
bool built = !JS_IsException(j_priv) && !JS_IsException(j_pub) && !JS_IsException(j_pair);
|
|
903
|
+
if (built)
|
|
904
|
+
{
|
|
905
|
+
built = JS_DefinePropertyValueStr(ctx, j_pair, "privateKey", j_priv, JS_PROP_C_W_E) >= 0;
|
|
906
|
+
built = JS_DefinePropertyValueStr(ctx, j_pair, "publicKey", j_pub, JS_PROP_C_W_E) >= 0 && built;
|
|
907
|
+
}
|
|
908
|
+
else
|
|
909
|
+
{
|
|
910
|
+
// Half a pair is not a pair, and the sentinel is not a value to hand on.
|
|
911
|
+
JS_FreeValue(ctx, j_priv);
|
|
912
|
+
JS_FreeValue(ctx, j_pub);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
if (built)
|
|
916
|
+
{
|
|
917
|
+
js_settle_or_reject(ctx, resolving_funcs, j_pair);
|
|
918
|
+
}
|
|
919
|
+
else
|
|
920
|
+
{
|
|
921
|
+
// Whichever key did get built is being dropped here, so its row goes with
|
|
922
|
+
// it. Left behind, it would anchor a Ruby CryptoKey, private material
|
|
923
|
+
// included, for the life of the VM with nothing in JS able to reach it.
|
|
924
|
+
VMData *data = JS_GetContextOpaque(ctx);
|
|
925
|
+
if (priv_registered_here)
|
|
926
|
+
alive_objects_forget(data, r_priv);
|
|
927
|
+
if (pub_registered_here)
|
|
928
|
+
alive_objects_forget(data, r_pub);
|
|
929
|
+
JS_FreeValue(ctx, j_pair);
|
|
930
|
+
js_settle_or_reject(ctx, resolving_funcs, JS_EXCEPTION);
|
|
931
|
+
}
|
|
418
932
|
}
|
|
419
933
|
else
|
|
420
934
|
{
|
|
421
|
-
|
|
422
|
-
JSValue ret = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED, 1, (JSValueConst *)&j_result);
|
|
423
|
-
JS_FreeValue(ctx, j_result);
|
|
424
|
-
JS_FreeValue(ctx, ret);
|
|
935
|
+
js_settle_or_reject(ctx, resolving_funcs, js_crypto_key_to_js(ctx, r_result, NULL));
|
|
425
936
|
}
|
|
426
937
|
|
|
427
938
|
JS_FreeValue(ctx, resolving_funcs[0]);
|
|
@@ -457,6 +968,8 @@ static JSValue js_subtle_import_key(JSContext *ctx, JSValueConst this_val, int a
|
|
|
457
968
|
VALUE r_algo_hash = js_algo_to_ruby_hash(ctx, argv[2]);
|
|
458
969
|
VALUE r_extractable = JS_ToBool(ctx, argv[3]) ? Qtrue : Qfalse;
|
|
459
970
|
VALUE r_usages = js_usages_to_ruby_array(ctx, argv[4]);
|
|
971
|
+
if (NIL_P(r_usages))
|
|
972
|
+
return js_rejected_type_error(ctx, "SubtleCrypto: keyUsages has more entries than a key can have");
|
|
460
973
|
|
|
461
974
|
JSValue promise, resolving_funcs[2];
|
|
462
975
|
promise = JS_NewPromiseCapability(ctx, resolving_funcs);
|
|
@@ -473,10 +986,7 @@ static JSValue js_subtle_import_key(JSContext *ctx, JSValueConst this_val, int a
|
|
|
473
986
|
}
|
|
474
987
|
else
|
|
475
988
|
{
|
|
476
|
-
|
|
477
|
-
JSValue ret = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED, 1, (JSValueConst *)&j_result);
|
|
478
|
-
JS_FreeValue(ctx, j_result);
|
|
479
|
-
JS_FreeValue(ctx, ret);
|
|
989
|
+
js_settle_or_reject(ctx, resolving_funcs, js_crypto_key_to_js(ctx, r_result, NULL));
|
|
480
990
|
}
|
|
481
991
|
|
|
482
992
|
JS_FreeValue(ctx, resolving_funcs[0]);
|
|
@@ -809,6 +1319,8 @@ static JSValue js_subtle_derive_key(JSContext *ctx, JSValueConst this_val, int a
|
|
|
809
1319
|
VALUE r_derived_algo_hash = js_algo_to_ruby_hash(ctx, argv[2]);
|
|
810
1320
|
VALUE r_extractable = JS_ToBool(ctx, argv[3]) ? Qtrue : Qfalse;
|
|
811
1321
|
VALUE r_usages = js_usages_to_ruby_array(ctx, argv[4]);
|
|
1322
|
+
if (NIL_P(r_usages))
|
|
1323
|
+
return js_rejected_type_error(ctx, "SubtleCrypto: keyUsages has more entries than a key can have");
|
|
812
1324
|
|
|
813
1325
|
JSValue promise, resolving_funcs[2];
|
|
814
1326
|
promise = JS_NewPromiseCapability(ctx, resolving_funcs);
|
|
@@ -825,10 +1337,7 @@ static JSValue js_subtle_derive_key(JSContext *ctx, JSValueConst this_val, int a
|
|
|
825
1337
|
}
|
|
826
1338
|
else
|
|
827
1339
|
{
|
|
828
|
-
|
|
829
|
-
JSValue ret = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED, 1, (JSValueConst *)&j_result);
|
|
830
|
-
JS_FreeValue(ctx, j_result);
|
|
831
|
-
JS_FreeValue(ctx, ret);
|
|
1340
|
+
js_settle_or_reject(ctx, resolving_funcs, js_crypto_key_to_js(ctx, r_result, NULL));
|
|
832
1341
|
}
|
|
833
1342
|
|
|
834
1343
|
JS_FreeValue(ctx, resolving_funcs[0]);
|
|
@@ -934,6 +1443,8 @@ static JSValue js_subtle_unwrap_key(JSContext *ctx, JSValueConst this_val, int a
|
|
|
934
1443
|
VALUE r_unwrapped_algo_hash = js_algo_to_ruby_hash(ctx, argv[4]);
|
|
935
1444
|
VALUE r_extractable = JS_ToBool(ctx, argv[5]) ? Qtrue : Qfalse;
|
|
936
1445
|
VALUE r_usages = js_usages_to_ruby_array(ctx, argv[6]);
|
|
1446
|
+
if (NIL_P(r_usages))
|
|
1447
|
+
return js_rejected_type_error(ctx, "SubtleCrypto: keyUsages has more entries than a key can have");
|
|
937
1448
|
|
|
938
1449
|
JSValue promise, resolving_funcs[2];
|
|
939
1450
|
promise = JS_NewPromiseCapability(ctx, resolving_funcs);
|
|
@@ -959,10 +1470,7 @@ static JSValue js_subtle_unwrap_key(JSContext *ctx, JSValueConst this_val, int a
|
|
|
959
1470
|
}
|
|
960
1471
|
else
|
|
961
1472
|
{
|
|
962
|
-
|
|
963
|
-
JSValue ret = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED, 1, (JSValueConst *)&j_result);
|
|
964
|
-
JS_FreeValue(ctx, j_result);
|
|
965
|
-
JS_FreeValue(ctx, ret);
|
|
1473
|
+
js_settle_or_reject(ctx, resolving_funcs, js_crypto_key_to_js(ctx, r_result, NULL));
|
|
966
1474
|
}
|
|
967
1475
|
|
|
968
1476
|
JS_FreeValue(ctx, resolving_funcs[0]);
|