mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-08 08:44:37 +08:00
Merge main
This commit is contained in:
@@ -623,6 +623,10 @@ struct ScrFiber {
|
||||
* awaiter destroys the record when it takes the result. */
|
||||
bool direct;
|
||||
struct ScrFiber *direct_awaiter;
|
||||
/* While an async body runs its synchronous prefix (until its first await
|
||||
* or its end), the AsyncLocalStorage slot of the execution that spawned
|
||||
* it; NULL afterwards (scr_als_prefix_end). */
|
||||
ScrAlsCtx **prefix_slot;
|
||||
#ifdef SCR_ASAN_FIBERS
|
||||
void *fake_stack;
|
||||
/* Live stackless async fibers (scr_async_spawn_stackless) form a list
|
||||
@@ -670,6 +674,61 @@ void scr_als_ctx_release(ScrAlsCtx *c) {
|
||||
free(c);
|
||||
}
|
||||
|
||||
/* enterWith on the main stack lasts until that code returns to the loop,
|
||||
* so the store does not reach callbacks scheduled without it: the loop
|
||||
* clears the main slot when the main module's body returns, and an
|
||||
* enterWith inside a callback the loop runs puts a tick that clears it at
|
||||
* the front of the tick queue, so it runs as soon as that callback
|
||||
* returns. A store that an async callee's prefix or a generator step
|
||||
* enters becomes the caller's at scr_als_prefix_end and is cleared the
|
||||
* same way. */
|
||||
static SCR_TL bool scr_loop_started = false;
|
||||
|
||||
static void scr_als_main_clear(void) {
|
||||
scr_als_ctx_release(scr_als_main_slot);
|
||||
scr_als_main_slot = NULL;
|
||||
}
|
||||
|
||||
static void scr_next_tick_raw_first(void (*fn)(void));
|
||||
|
||||
/* A new store on the main slot: one a loop callback enters ends with it. */
|
||||
static void scr_als_main_entered(void) {
|
||||
if (scr_loop_started) scr_next_tick_raw_first(scr_als_main_clear);
|
||||
}
|
||||
|
||||
void scr_als_entered_with(void) {
|
||||
if (SCR_ALS_SLOT() == &scr_als_main_slot) scr_als_main_entered();
|
||||
}
|
||||
|
||||
/* The end of an async body's synchronous prefix: its first await, or its
|
||||
* end. Node runs that prefix inside the caller's execution, so a store it
|
||||
* entered (enterWith) is still the caller's store after the call; the body
|
||||
* keeps its own copy of the context for what runs after its await. */
|
||||
static void scr_als_prefix_end(ScrFiber *f) {
|
||||
ScrAlsCtx **slot = f->prefix_slot;
|
||||
if (slot == NULL) return;
|
||||
f->prefix_slot = NULL;
|
||||
if (*slot != f->als) {
|
||||
scr_als_ctx_release(*slot);
|
||||
*slot = scr_als_ctx_retain(f->als);
|
||||
if (f->als != NULL && slot == &scr_als_main_slot) scr_als_main_entered();
|
||||
}
|
||||
}
|
||||
|
||||
/* A synchronous generator has no context of its own: each step runs in its
|
||||
* resumer's execution, as Node runs it. A fiber-backed one switches to its
|
||||
* own slot, so its step starts from the resumer's store and a store it
|
||||
* enters is the resumer's after the step (scr_als_prefix_end on the way
|
||||
* back); a stackless one runs in the resumer's slot (scr_gen_coro_hop). */
|
||||
static void scr_als_step_begin(ScrFiber *f) {
|
||||
ScrAlsCtx **slot = SCR_ALS_SLOT();
|
||||
if (f->als != *slot) {
|
||||
scr_als_ctx_release(f->als);
|
||||
f->als = scr_als_ctx_retain(*slot);
|
||||
}
|
||||
f->prefix_slot = slot;
|
||||
}
|
||||
|
||||
/* The lifetime region a new async fiber carries (ScrFiber.region). */
|
||||
static inline void *scr_fiber_region_capture(void) {
|
||||
const ScrRgnFiberHooks *regions = atomic_load_explicit(&scr_region_fiber_hooks, memory_order_relaxed);
|
||||
@@ -726,26 +785,6 @@ ScrDyn *scr_als_peek(double id) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* enterWith on the main stack lasts until that code returns to the loop,
|
||||
* so the store does not reach callbacks scheduled without it: the loop
|
||||
* clears the main slot when the main module's body returns, and an
|
||||
* enterWith inside a callback the loop runs puts a tick that clears it at
|
||||
* the front of the tick queue, so it runs as soon as that callback
|
||||
* returns. */
|
||||
static SCR_TL bool scr_loop_started = false;
|
||||
|
||||
static void scr_als_main_clear(void) {
|
||||
scr_als_ctx_release(scr_als_main_slot);
|
||||
scr_als_main_slot = NULL;
|
||||
}
|
||||
|
||||
static void scr_next_tick_raw_first(void (*fn)(void));
|
||||
|
||||
void scr_als_entered_with(void) {
|
||||
if (scr_loop_started && SCR_ALS_SLOT() == &scr_als_main_slot)
|
||||
scr_next_tick_raw_first(scr_als_main_clear);
|
||||
}
|
||||
|
||||
static SCR_TL ScrCtx scr_loop_ctx; /* the main stack (scheduler home) */
|
||||
static SCR_TL ScrFiber *scr_current = NULL;
|
||||
/* A finished fiber whose last switch went to the loop (scr_resume_fiber). */
|
||||
@@ -1889,6 +1928,7 @@ static void scr_trampoline(void) {
|
||||
ScrFiber *self = scr_current;
|
||||
self->entry(self, self->argpack);
|
||||
scr_fiber_finish(self);
|
||||
scr_als_prefix_end(self);
|
||||
/* The loop reaps the fiber that comes back to it finished, which need
|
||||
* not be the one it resumed: a fiber that continued in place for the
|
||||
* awaiters it parked (scr_await_yield) returns to the loop too. */
|
||||
@@ -2010,6 +2050,7 @@ static ScrPromise *scr_async_spawn_impl(void (*entry)(ScrFiber *, void *), void
|
||||
/* AsyncLocalStorage: the child runs in the SPAWNER's context (Node's
|
||||
* init-time capture); snapshots are immutable, so a retain suffices. */
|
||||
f->als = scr_als_ctx_retain(*SCR_ALS_SLOT());
|
||||
f->prefix_slot = SCR_ALS_SLOT();
|
||||
f->region = scr_fiber_region_capture();
|
||||
scr_fibers_live++;
|
||||
|
||||
@@ -2023,13 +2064,15 @@ static ScrPromise *scr_async_spawn_impl(void (*entry)(ScrFiber *, void *), void
|
||||
#elif defined(__wasi__)
|
||||
(void)awaiter;
|
||||
ScrFiber *spawner = scr_current;
|
||||
ScrAlsCtx **spawner_slot = scr_als_active;
|
||||
scr_current = f;
|
||||
scr_exc_swap_cell(&f->exc);
|
||||
scr_als_active = &f->als;
|
||||
entry(f, argpack); /* eager prefix; returns at suspend/final suspend */
|
||||
scr_als_prefix_end(f);
|
||||
scr_current = spawner;
|
||||
scr_exc_swap_cell(spawner ? &spawner->exc : NULL);
|
||||
scr_als_active = spawner ? &spawner->als : &scr_als_main_slot;
|
||||
scr_als_active = spawner_slot;
|
||||
ScrPromise *result = scr_promise_retain(f->promise);
|
||||
if (f->done) {
|
||||
scr_promise_release(f->promise);
|
||||
@@ -2057,6 +2100,7 @@ static ScrPromise *scr_async_spawn_impl(void (*entry)(ScrFiber *, void *), void
|
||||
#endif
|
||||
f->return_to = home;
|
||||
ScrFiber *spawner = scr_current;
|
||||
ScrAlsCtx **spawner_slot = scr_als_active;
|
||||
scr_switch(home, &f->ctx, f);
|
||||
/* back: the fiber suspended or finished. The switch back targeted NULL
|
||||
* (the child doesn't know who spawned it), which pointed both the current
|
||||
@@ -2066,7 +2110,7 @@ static ScrPromise *scr_async_spawn_impl(void (*entry)(ScrFiber *, void *), void
|
||||
* `undefined` rejection while the real payload leaks). */
|
||||
scr_current = spawner;
|
||||
scr_exc_swap_cell(spawner ? &spawner->exc : NULL);
|
||||
scr_als_active = spawner ? &spawner->als : &scr_als_main_slot;
|
||||
scr_als_active = spawner_slot;
|
||||
if (awaited != NULL) {
|
||||
/* Parked by the child: the loop owns (and may have reaped) it. */
|
||||
if (awaiter->await_parked) return awaited;
|
||||
@@ -2172,6 +2216,7 @@ static ScrFiber *scr_spawn_stackless_fiber(void (*entry)(ScrFiber *, void *), vo
|
||||
scr_fiber_track(f);
|
||||
f->stackless = true;
|
||||
f->als = scr_als_ctx_retain(*SCR_ALS_SLOT());
|
||||
f->prefix_slot = SCR_ALS_SLOT();
|
||||
/* The lifetime region the eager prefix runs in, which every later step
|
||||
* resumes in (scr_resume_fiber), as a fiber's body does. */
|
||||
f->region = scr_fiber_region_capture();
|
||||
@@ -2183,6 +2228,7 @@ static ScrFiber *scr_spawn_stackless_fiber(void (*entry)(ScrFiber *, void *), vo
|
||||
scr_stackless_fibers = f;
|
||||
#endif
|
||||
ScrFiber *spawner = scr_current;
|
||||
ScrAlsCtx **spawner_slot = scr_als_active;
|
||||
#ifndef _WIN32
|
||||
uintptr_t guard = scr_stack_guard;
|
||||
if (guard != 0 && (uintptr_t)__builtin_frame_address(0) < guard + SCR_STACKLESS_PREFIX_ROOM) {
|
||||
@@ -2195,9 +2241,10 @@ static ScrFiber *scr_spawn_stackless_fiber(void (*entry)(ScrFiber *, void *), vo
|
||||
scr_als_active = &f->als;
|
||||
entry(f, argpack); /* the eager prefix: returns at a suspension or the end */
|
||||
}
|
||||
scr_als_prefix_end(f);
|
||||
scr_current = spawner;
|
||||
scr_exc_swap_cell(spawner ? &spawner->exc : NULL);
|
||||
scr_als_active = spawner ? &spawner->als : &scr_als_main_slot;
|
||||
scr_als_active = spawner_slot;
|
||||
return f;
|
||||
}
|
||||
|
||||
@@ -2303,8 +2350,10 @@ void *scr_async_spawn_await_ref(void (*entry)(ScrFiber *, void *), void *argpack
|
||||
* settled promise is created and settled in the same order, rejections
|
||||
* enter the unhandled ledger through the same settle path, and the frame
|
||||
* isolates what a fresh fiber would — the Error.stack frame chain
|
||||
* (cell->stack) and the AsyncLocalStorage context (enterWith inside the
|
||||
* body stays inside it). The caller's exception cell is clean at any call
|
||||
* (cell->stack). The AsyncLocalStorage context is the caller's throughout:
|
||||
* the whole body is the synchronous prefix Node runs in the caller's
|
||||
* execution, so a store it enters stays entered after the call
|
||||
* (scr_als_prefix_end). The caller's exception cell is clean at any call
|
||||
* (try/finally stashes in-flight exceptions), so a pending exception
|
||||
* after the body is the body's own throw. */
|
||||
static void scr_inline_park_trap(void) {
|
||||
@@ -2321,21 +2370,12 @@ void scr_async_inline_enter(ScrAsyncInline *frame) {
|
||||
ScrExcCell *cell = scr_exc_current_cell();
|
||||
frame->stack = cell->stack;
|
||||
cell->stack = NULL;
|
||||
frame->als = scr_als_ctx_retain(*SCR_ALS_SLOT());
|
||||
if (scr_current != NULL) scr_current->inline_depth++;
|
||||
}
|
||||
|
||||
void scr_async_inline_exit(ScrAsyncInline *frame) {
|
||||
if (scr_current != NULL) scr_current->inline_depth--;
|
||||
scr_exc_current_cell()->stack = (ScrStackFrame *)frame->stack;
|
||||
ScrAlsCtx *saved = (ScrAlsCtx *)frame->als;
|
||||
ScrAlsCtx **slot = SCR_ALS_SLOT();
|
||||
if (*slot != saved) {
|
||||
scr_als_ctx_release(*slot);
|
||||
*slot = saved; /* the frame's reference moves back in */
|
||||
} else {
|
||||
scr_als_ctx_release(saved);
|
||||
}
|
||||
}
|
||||
|
||||
ScrPromise *scr_async_inline_leave(ScrAsyncInline *frame) {
|
||||
@@ -2379,11 +2419,14 @@ ScrPromise *scr_async_spawn_after(ScrPromise *dependency,
|
||||
* switch to its own resumer: park it here, for each such awaiter up the
|
||||
* chain, and switch there directly. Nothing runs in between either way. */
|
||||
static ScrCtx *scr_fiber_park_awaiters(ScrFiber *self) {
|
||||
scr_als_prefix_end(self);
|
||||
ScrCtx *to = self->return_to;
|
||||
#ifdef SCR_SPAWN_AWAIT
|
||||
for (ScrFiber *cur = self; cur->awaiter != NULL;) {
|
||||
ScrFiber *aw = cur->awaiter;
|
||||
cur->awaiter = NULL;
|
||||
/* The awaiter's await is its own suspension: its prefix ends here. */
|
||||
scr_als_prefix_end(aw);
|
||||
scr_promise_add_waiter(cur->promise, aw);
|
||||
aw->await_parked = true;
|
||||
to = aw->return_to;
|
||||
@@ -5675,9 +5718,13 @@ __attribute__((always_inline)) static inline void scr_gen_coro_hop(ScrGen *g, bo
|
||||
if (retain) scr_gen_retain(g);
|
||||
g->state = SCR_GEN_RUNNING;
|
||||
f->resumer = scr_current;
|
||||
ScrAlsCtx **resumer_slot = scr_als_active;
|
||||
scr_current = f;
|
||||
scr_exc_swap_cell(&f->exc);
|
||||
scr_als_active = &f->als;
|
||||
/* A synchronous generator has no AsyncLocalStorage context of its own:
|
||||
* its step runs in the resumer's slot, as Node runs it in the execution
|
||||
* that called next(). An async generator's step runs in its own. */
|
||||
if (g->is_async) scr_als_active = &f->als;
|
||||
if (f->coro == NULL) f->entry(f, f->argpack);
|
||||
else scr_coro_resume(f->coro);
|
||||
/* A running generator cannot be resumed again, so neither its fiber nor
|
||||
@@ -5686,7 +5733,7 @@ __attribute__((always_inline)) static inline void scr_gen_coro_hop(ScrGen *g, bo
|
||||
ScrFiber *me = f->resumer;
|
||||
scr_current = me;
|
||||
scr_exc_swap_cell(me != NULL ? &me->exc : NULL);
|
||||
scr_als_active = me != NULL ? &me->als : &scr_als_main_slot;
|
||||
scr_als_active = resumer_slot;
|
||||
scr_gen_switched_back(g, f, retain, true);
|
||||
}
|
||||
|
||||
@@ -5708,6 +5755,7 @@ static void scr_gen_switch_in(ScrGen *g) {
|
||||
#endif
|
||||
scr_gen_retain(g);
|
||||
g->state = SCR_GEN_RUNNING;
|
||||
if (!g->is_async) scr_als_step_begin(f);
|
||||
#ifdef _WIN32
|
||||
ScrCtx here = scr_win_self();
|
||||
#elif defined(__wasi__)
|
||||
@@ -5717,6 +5765,7 @@ static void scr_gen_switch_in(ScrGen *g) {
|
||||
#endif
|
||||
f->return_to = &here;
|
||||
ScrFiber *me = scr_current;
|
||||
ScrAlsCtx **resumer_slot = scr_als_active;
|
||||
#ifdef __wasi__
|
||||
scr_current = f;
|
||||
scr_exc_swap_cell(&f->exc);
|
||||
@@ -5725,14 +5774,17 @@ static void scr_gen_switch_in(ScrGen *g) {
|
||||
else scr_wasi_coro_resume(f->coro);
|
||||
scr_current = me;
|
||||
scr_exc_swap_cell(me != NULL ? &me->exc : NULL);
|
||||
scr_als_active = me != NULL ? &me->als : &scr_als_main_slot;
|
||||
scr_als_active = resumer_slot;
|
||||
#else
|
||||
scr_switch(&here, &f->ctx, f);
|
||||
/* Back on the consumer: restore identity + the consumer's cell (the
|
||||
* yield/finish switch targeted NULL — main's cell). */
|
||||
/* Back on the consumer: restore identity, the consumer's cell and its
|
||||
* AsyncLocalStorage slot (the yield/finish switch targeted NULL — main's
|
||||
* cell and slot). */
|
||||
scr_current = me;
|
||||
scr_exc_swap_cell(me != NULL ? &me->exc : NULL);
|
||||
scr_als_active = resumer_slot;
|
||||
#endif
|
||||
scr_als_prefix_end(f);
|
||||
scr_gen_switched_back(g, f, true, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -62,12 +62,14 @@ ScrPromise *scr_async_spawn_after(ScrPromise *dependency,
|
||||
/* Fiberless async call (native targets): the compiler proves an async body
|
||||
* can never park its execution context and runs it directly on the
|
||||
* caller's stack between enter and leave. The frame saves what a fresh
|
||||
* fiber would isolate — the Error.stack frame chain and the
|
||||
* AsyncLocalStorage context — and leave restores both, returning a fresh
|
||||
* pending promise (+1) the caller settles from the body's outcome. */
|
||||
* fiber would isolate, the Error.stack frame chain, and leave restores it,
|
||||
* returning a fresh pending promise (+1) the caller settles from the body's
|
||||
* outcome. The body runs in the caller's AsyncLocalStorage context, as
|
||||
* Node runs an async body's synchronous prefix. The second word is unused;
|
||||
* emitted frames keep its size. */
|
||||
typedef struct ScrAsyncInline {
|
||||
void *stack;
|
||||
void *als;
|
||||
void *reserved;
|
||||
} ScrAsyncInline;
|
||||
void scr_async_inline_enter(ScrAsyncInline *frame);
|
||||
ScrPromise *scr_async_inline_leave(ScrAsyncInline *frame);
|
||||
|
||||
@@ -148,7 +148,7 @@ static void scr_enable_utf8_console_output(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
void scr_runtime_abi_v25(void) {}
|
||||
void scr_runtime_abi_v26(void) {}
|
||||
|
||||
void scr_init(void) {
|
||||
#ifdef SCR_WORKERS
|
||||
|
||||
@@ -81,7 +81,7 @@ void scr_init(void);
|
||||
/* Program objects emitted by the bundled LLVM helper reference this symbol.
|
||||
* Its versioned spelling makes a mismatched manual runtime link fail before
|
||||
* the program can start. */
|
||||
void scr_runtime_abi_v25(void);
|
||||
void scr_runtime_abi_v26(void);
|
||||
void scr_runtime_workers_v8(void);
|
||||
|
||||
/* ── the trap funnel (scr_console.c; scr_library.c under -DSCR_LIB) ──────
|
||||
|
||||
@@ -85,6 +85,10 @@ ScrDyn *scr_crypto_create_secret_key(ScrDyn *key, ScrDyn *encoding);
|
||||
ScrDyn *scr_crypto_generate_key_sync(ScrDyn *type, ScrDyn *options);
|
||||
ScrBytes *scr_crypto_key_object_input(ScrDyn *key);
|
||||
bool scr_crypto_is_key_object(const ScrDyn *value);
|
||||
/* Throws Node's ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE for a private or public
|
||||
* KeyObject used as a secret key. scr_pki.c, which makes those keys,
|
||||
* installs it with them, so its messages stay out of other programs. */
|
||||
extern void (*scr_crypto_reject_asymmetric_secret)(const ScrDyn *key);
|
||||
const unsigned char *scr_key_object_bytes(const ScrDyn *value, size_t *len);
|
||||
/* crypto.createCipheriv (decrypt false) and crypto.createDecipheriv
|
||||
* (decrypt true) over checked-dynamic arguments (scr_cipher.c, linked
|
||||
@@ -268,5 +272,17 @@ ScrStr *scr_crypto_x509_valid_from(ScrBytes *data);
|
||||
ScrStr *scr_crypto_x509_valid_from_str(ScrStr *pem);
|
||||
ScrStr *scr_crypto_x509_valid_to(ScrBytes *data);
|
||||
ScrStr *scr_crypto_x509_valid_to_str(ScrStr *pem);
|
||||
/* Asymmetric keys and signatures (scr_pki.c, the pki unit). Arguments
|
||||
* borrowed; results +1, or NULL with the error pending. */
|
||||
ScrDyn *scr_crypto_create_private_key(ScrDyn *key);
|
||||
ScrDyn *scr_crypto_create_public_key(ScrDyn *key);
|
||||
ScrDyn *scr_crypto_generate_key_pair_sync(ScrDyn *type, ScrDyn *options);
|
||||
void scr_crypto_generate_key_pair_async(ScrDyn *type, ScrDyn *options, ScrDyn *callback);
|
||||
ScrDyn *scr_crypto_sign_oneshot(ScrDyn *algorithm, ScrDyn *data, ScrDyn *key);
|
||||
ScrDyn *scr_crypto_verify_oneshot(ScrDyn *algorithm, ScrDyn *data, ScrDyn *key, ScrDyn *signature);
|
||||
ScrDyn *scr_crypto_create_sign(ScrDyn *algorithm);
|
||||
ScrDyn *scr_crypto_create_verify(ScrDyn *algorithm);
|
||||
bool scr_crypto_is_asymmetric_key(const ScrDyn *value);
|
||||
const char *scr_crypto_asymmetric_key_type(const ScrDyn *value);
|
||||
|
||||
#endif /* SCR_CRYPTO_H */
|
||||
|
||||
@@ -158,6 +158,11 @@ typedef enum {
|
||||
X(DIR, 44) /* fs.Dir (scr_lib.c) */ \
|
||||
X(DISPOSABLE_STACK, 49) /* DisposableStack (scr_symbol.c) */ \
|
||||
X(ECDH, 64) /* node:crypto ECDH (scr_ecdh.c) */ \
|
||||
X(PRIVATE_KEY, 65) /* node:crypto PrivateKeyObject (scr_pki.c) */ \
|
||||
X(PUBLIC_KEY, 66) /* node:crypto PublicKeyObject (scr_pki.c) */ \
|
||||
X(SIGN, 67) /* node:crypto Sign (scr_pki.c) */ \
|
||||
X(VERIFY, 68) /* node:crypto Verify (scr_pki.c) */ \
|
||||
X(ASYMMETRIC_CRYPTO_KEY, 69) /* WebCrypto ECDSA and RSA CryptoKey (scr_webcrypto_pki.c) */ \
|
||||
X(EVENT, 14) /* ScrEvent — new Event() and AbortSignal's abort events */ \
|
||||
X(EVENT_TARGET, 39) /* ScrEventTarget — new EventTarget() */ \
|
||||
X(FETCH_HEADERS, 13) /* native static-fetch response Headers */ \
|
||||
@@ -759,6 +764,9 @@ ScrDyn *scr_dyn_array_constructor(void);
|
||||
/* The interned builtin constructor `name` (Object, Date, RegExp, Map, Set,
|
||||
* Promise, WeakMap, WeakSet) as a dynamic function value: +1. */
|
||||
ScrDyn *scr_dyn_global_constructor(const ScrStr *name);
|
||||
/* The interned function value of an error constructor ("TypeError"), or
|
||||
* NULL for other names. */
|
||||
ScrDyn *scr_dyn_error_constructor(const char *name);
|
||||
/* A builtin global function (setTimeout, setImmediate, process.nextTick, ...)
|
||||
* as an interned dynamic function value: +1. */
|
||||
ScrDyn *scr_dyn_builtin_function(const ScrStr *name);
|
||||
|
||||
@@ -34,8 +34,7 @@
|
||||
* receiver without a reference; the hook closures outlive the record.
|
||||
*
|
||||
* Not supported here (each throws the "not supported yet" Error where it
|
||||
* runs): objectMode and the other options scr_stream.c's dyn option walk
|
||||
* refuses, `_writev` and `_construct`, symbol event names, listeners() /
|
||||
* runs): the options scr_stream.c's dyn option walk refuses, `_writev` and `_construct`, symbol event names, listeners() /
|
||||
* rawListeners(), `wrap()`, and pipes into values that are neither runtime
|
||||
* streams nor constructor-function streams. */
|
||||
#include "scr_runtime.h"
|
||||
@@ -275,11 +274,10 @@ static ScrDyn *bcs_done_call(ScrClosure *closure, ScrDyn *const *args, size_t ar
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
/* the data is pushed as push(data) pushes it */
|
||||
ScrDyn *data = bcs_arg(args, argc, 1);
|
||||
ScrBytes *bytes = data->kind == SCR_DYN_BYTES ? scr_bytes_raw_view(data->v.bytes) : NULL;
|
||||
ScrStr *text = data->kind == SCR_DYN_STR ? scr_str_retain(data->v.str) : NULL;
|
||||
if (which == BCS_DONE_TRANSFORM) scr_stream_transform_done(s, err, bytes, text);
|
||||
else scr_stream_flush_done(s, err, bytes, text);
|
||||
if (which == BCS_DONE_TRANSFORM) scr_stream_transform_done_value(s, err, data);
|
||||
else scr_stream_flush_done_value(s, err, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -362,13 +360,14 @@ static void bcs_read_inv(ScrClosure *cb, ScrStream *s, double size) {
|
||||
scr_dyn_release(object);
|
||||
}
|
||||
|
||||
/* _write(chunk, 'buffer', cb) and _transform(chunk, 'buffer', cb): a
|
||||
* throw propagates to the write() caller, as in Node. */
|
||||
/* _write(chunk, 'buffer', cb) and _transform(chunk, 'buffer', cb), or the
|
||||
* value and the write's encoding argument on an object-mode writable
|
||||
* side: a throw propagates to the write() caller, as in Node. */
|
||||
static void bcs_chunk_call(ScrClosure *cb, ScrStream *s, ScrBytes *chunk, const char *method, int done) {
|
||||
ScrDyn *object = scr_box_get_ref(cb->caps[0]);
|
||||
ScrDyn *args[3];
|
||||
args[0] = scr_dyn_new_buffer(chunk);
|
||||
args[1] = bcs_str("buffer");
|
||||
args[0] = scr_stream_chunk_value(s, chunk);
|
||||
args[1] = scr_stream_chunk_encoding_value(s);
|
||||
args[2] = bcs_done(s, done);
|
||||
ScrDyn *result = bcs_invoke(object, method, args, 3);
|
||||
for (int i = 0; i < 3; i++) scr_dyn_release(args[i]);
|
||||
@@ -488,8 +487,8 @@ static void bcs_listener0(ScrClosure *adapter) {
|
||||
bcs_listener_call(adapter, NULL, 0);
|
||||
}
|
||||
|
||||
static void bcs_listener_data(ScrClosure *adapter, ScrBytes *bytes, ScrStr *text) {
|
||||
ScrDyn *arg = text ? scr_dyn_new_str(text) : scr_dyn_new_buffer(bytes);
|
||||
static void bcs_listener_data(ScrClosure *adapter, ScrBytes *bytes, ScrStr *text, ScrDyn *value) {
|
||||
ScrDyn *arg = value ? scr_dyn_retain(value) : text ? scr_dyn_new_str(text) : scr_dyn_new_buffer(bytes);
|
||||
bcs_listener_call(adapter, &arg, 1);
|
||||
scr_dyn_release(arg);
|
||||
}
|
||||
@@ -583,7 +582,7 @@ static bool bcs_add_listener(ScrStream *s, ScrDyn *self, ScrDyn *type, ScrDyn *l
|
||||
break;
|
||||
case 1:
|
||||
chained = scr_emitter_on_dyn(em, name, listener, bcs_adapter(listener, self, (void *)bcs_listener_data),
|
||||
scr_ee_inv_fixed2, once, prepend);
|
||||
scr_ee_inv_fixed3, once, prepend);
|
||||
break;
|
||||
case 2:
|
||||
chained = scr_emitter_on_dyn(em, name, listener, bcs_adapter(listener, self, (void *)bcs_listener_error),
|
||||
@@ -625,14 +624,15 @@ static ScrDyn *bcs_emit(ScrStream *s, ScrDyn *const *args, size_t argc) {
|
||||
had = scr_emitter_emit(em, name);
|
||||
break;
|
||||
case 1:
|
||||
/* 'data' carries the bytes, string and value slots (scr_stream_emit_data) */
|
||||
if (first->kind == SCR_DYN_BYTES) {
|
||||
ScrBytes *bytes = scr_bytes_raw_view(first->v.bytes);
|
||||
had = scr_emitter_emit(em, name, bytes, (ScrStr *)NULL);
|
||||
had = scr_emitter_emit(em, name, bytes, (ScrStr *)NULL, first);
|
||||
scr_bytes_release(bytes);
|
||||
} else if (first->kind == SCR_DYN_STR) {
|
||||
had = scr_emitter_emit(em, name, (ScrBytes *)NULL, first->v.str);
|
||||
had = scr_emitter_emit(em, name, (ScrBytes *)NULL, first->v.str, first);
|
||||
} else {
|
||||
bcs_unsupported("emit('data') with a chunk other than a Buffer or string on a constructor-function stream is not supported yet");
|
||||
had = scr_emitter_emit(em, name, (ScrBytes *)NULL, (ScrStr *)NULL, first);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
@@ -1037,10 +1037,16 @@ static ScrDyn *bcs_write(ScrStream *s, ScrDyn *self, ScrDyn *const *args, size_t
|
||||
ScrDyn *cb = bcs_arg(args, argc, 2);
|
||||
if (scr_dyn_is_callable(encoding)) {
|
||||
cb = encoding;
|
||||
encoding = scr_dyn_undefined();
|
||||
encoding = scr_stream_writes_values(s) ? scr_dyn_new_null() : scr_dyn_undefined();
|
||||
}
|
||||
bool below;
|
||||
if (scr_stream_writes_values(s)) {
|
||||
/* an object-mode side hands the encoding argument to _write */
|
||||
below = scr_stream_write_enc(s, chunk, encoding, bcs_callback(cb, self, s));
|
||||
} else {
|
||||
if (chunk->kind == SCR_DYN_STR && !bcs_utf8_only(encoding)) return NULL;
|
||||
below = scr_stream_write_dyn(s, chunk, bcs_callback(cb, self, s));
|
||||
}
|
||||
if (chunk->kind == SCR_DYN_STR && !bcs_utf8_only(encoding)) return NULL;
|
||||
bool below = scr_stream_write_dyn(s, chunk, bcs_callback(cb, self, s));
|
||||
if (scr_exc_pending()) return NULL;
|
||||
return scr_dyn_new_bool(below);
|
||||
}
|
||||
@@ -1055,11 +1061,17 @@ static ScrDyn *bcs_end(ScrStream *s, ScrDyn *self, ScrDyn *const *args, size_t a
|
||||
encoding = scr_dyn_undefined();
|
||||
} else if (scr_dyn_is_callable(encoding)) {
|
||||
cb = encoding;
|
||||
encoding = scr_dyn_undefined();
|
||||
encoding = scr_dyn_new_null();
|
||||
}
|
||||
if (chunk->kind == SCR_DYN_STR && !bcs_utf8_only(encoding)) return NULL;
|
||||
ScrStream *chained = NULL;
|
||||
if (bcs_nullish(chunk)) {
|
||||
if (!bcs_nullish(chunk) && scr_stream_writes_values(s)) {
|
||||
/* end(chunk) writes the value with the encoding argument */
|
||||
scr_stream_write_enc(s, chunk, encoding, NULL);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
chained = scr_stream_end(s, NULL, NULL, bcs_callback(cb, self, s));
|
||||
} else if (chunk->kind == SCR_DYN_STR && !bcs_utf8_only(encoding)) {
|
||||
return NULL;
|
||||
} else if (bcs_nullish(chunk)) {
|
||||
chained = scr_stream_end(s, NULL, NULL, bcs_callback(cb, self, s));
|
||||
} else if (chunk->kind == SCR_DYN_STR) {
|
||||
chained = scr_stream_end(s, NULL, chunk->v.str, bcs_callback(cb, self, s));
|
||||
@@ -1376,8 +1388,6 @@ static ScrDyn *bcs_getter_call(ScrClosure *closure, ScrDyn *const *args, size_t
|
||||
return bcs_flowing(s);
|
||||
case BCS_P_ERRORED:
|
||||
return bcs_errored(s);
|
||||
case BCS_P_WRITABLE_OBJECT_MODE:
|
||||
return scr_dyn_new_bool(false);
|
||||
case BCS_P_READABLE_LENGTH:
|
||||
case BCS_P_READABLE_HWM:
|
||||
case BCS_P_WRITABLE_LENGTH:
|
||||
|
||||
@@ -1923,6 +1923,8 @@ static ScrDyn *dyn_function_constructor(void) {
|
||||
|
||||
ScrDyn *scr_dyn_global_constructor(const ScrStr *name) {
|
||||
if (strcmp(name->data, dyn_s20) == 0) return dyn_function_constructor();
|
||||
ScrDyn *error = scr_dyn_error_constructor(name->data);
|
||||
if (error) return error;
|
||||
ScrDyn *ctor = dyn_global_ctor(name->data);
|
||||
if (!ctor) scr_trap(dyn_s3);
|
||||
return ctor;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/* Ed25519 for scr_pki.c: libsodium's ref10 implementation (vendored, see
|
||||
* ../vendor/README.md), textually included so it builds as this one unit.
|
||||
* It links only into programs that name Ed25519 (the ed25519 runtime
|
||||
* feature) and installs itself into scr_pki.c at startup; without it,
|
||||
* Ed25519 signing, verification and public-key derivation throw a coded
|
||||
* SC2020 error.
|
||||
*
|
||||
* Signing runs in constant time. Verification rejects a non-canonical S
|
||||
* and the small-order R and public-key encodings, then checks the
|
||||
* cofactorless equation, which is what Node 24 answers. */
|
||||
#include "scr_runtime.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "scr_pki.h"
|
||||
|
||||
#define CONFIGURED 1
|
||||
#include "../vendor/libsodium/ed25519_ref10.c"
|
||||
#include "../vendor/libsodium/hash_sha512_cp.c"
|
||||
#include "../vendor/libsodium/verify.c"
|
||||
#include "../vendor/libsodium/sign.c"
|
||||
#include "../vendor/libsodium/open.c"
|
||||
#include "../vendor/libsodium/keypair.c"
|
||||
#undef CONFIGURED
|
||||
|
||||
/* The libsodium utilities those units call. */
|
||||
void sodium_memzero(void *const pnt, const size_t len) {
|
||||
volatile unsigned char *p = (volatile unsigned char *)pnt;
|
||||
for (size_t i = 0; i < len; i++) p[i] = 0U;
|
||||
}
|
||||
|
||||
int sodium_memcmp(const void *const b1_, const void *const b2_, size_t len) {
|
||||
const volatile unsigned char *b1 = (const volatile unsigned char *)b1_;
|
||||
const volatile unsigned char *b2 = (const volatile unsigned char *)b2_;
|
||||
volatile unsigned char d = 0U;
|
||||
for (size_t i = 0; i < len; i++) d |= b1[i] ^ b2[i];
|
||||
return (1 & ((d - 1) >> 8)) - 1;
|
||||
}
|
||||
|
||||
int sodium_is_zero(const unsigned char *n, const size_t nlen) {
|
||||
volatile unsigned char d = 0U;
|
||||
for (size_t i = 0; i < nlen; i++) d |= n[i];
|
||||
return 1 & ((d - 1) >> 8);
|
||||
}
|
||||
|
||||
void randombytes_buf(void *const buf, const size_t size) { arc4random_buf(buf, size); }
|
||||
|
||||
static void se_public_key(uint8_t pub[32], const uint8_t seed[32]) {
|
||||
uint8_t sk[64];
|
||||
crypto_sign_ed25519_seed_keypair(pub, sk, seed);
|
||||
sodium_memzero(sk, sizeof sk);
|
||||
}
|
||||
|
||||
static void se_sign(uint8_t sig[64], const uint8_t seed[32], const uint8_t pub[32], const uint8_t *m,
|
||||
size_t n) {
|
||||
uint8_t sk[64];
|
||||
memcpy(sk, seed, 32);
|
||||
memcpy(sk + 32, pub, 32);
|
||||
crypto_sign_ed25519_detached(sig, NULL, m, n, sk);
|
||||
sodium_memzero(sk, sizeof sk);
|
||||
}
|
||||
|
||||
static bool se_verify(const uint8_t pub[32], const uint8_t *m, size_t n, const uint8_t sig[64]) {
|
||||
return crypto_sign_ed25519_verify_detached(sig, m, n, pub) == 0;
|
||||
}
|
||||
|
||||
static const ScrPkiEd25519 se_table = {
|
||||
.public_key = se_public_key,
|
||||
.sign = se_sign,
|
||||
.verify = se_verify,
|
||||
};
|
||||
|
||||
__attribute__((constructor)) static void se_install(void) { scr_pki_ed25519 = &se_table; }
|
||||
@@ -2918,22 +2918,6 @@ static bool scr_http_conn_parse_head(ScrHttpConn *conn, size_t head_len, bool *o
|
||||
conn->trailer_bytes = 0;
|
||||
conn->trailer_fields = 0;
|
||||
|
||||
if (!http10 && conn->srv->require_host_header) {
|
||||
bool has_host = false;
|
||||
for (size_t i = 0; i < req->nheaders; i++) {
|
||||
const ScrStr *name = req->hnames[i];
|
||||
if (name->len == 4 && memcmp(name->data, "host", 4) == 0) {
|
||||
has_host = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!has_host) {
|
||||
scr_http_req_release(req);
|
||||
scr_http_conn_missing_host(conn);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* framing: chunked wins over Content-Length (RFC 9112) */
|
||||
bool chunked = false;
|
||||
long long content_length = 0;
|
||||
@@ -2958,12 +2942,33 @@ static bool scr_http_conn_parse_head(ScrHttpConn *conn, size_t head_len, bool *o
|
||||
}
|
||||
}
|
||||
|
||||
/* HTTP/1.1 requires Host (requireHostHeader). CONNECT requests, and
|
||||
* upgrade requests an 'upgrade' listener takes, leave the parser before
|
||||
* Node's check. */
|
||||
bool is_connect = req->method->len == 7 && memcmp(req->method->data, "CONNECT", 7) == 0;
|
||||
bool takes_upgrade = has_upgrade_hdr && conn_upgrade_token && conn->srv->upgrade_ls.n > 0;
|
||||
if (!http10 && conn->srv->require_host_header && !is_connect && !takes_upgrade) {
|
||||
bool has_host = false;
|
||||
for (size_t i = 0; i < req->nheaders; i++) {
|
||||
const ScrStr *name = req->hnames[i];
|
||||
if (name->len == 4 && memcmp(name->data, "host", 4) == 0) {
|
||||
has_host = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!has_host) {
|
||||
scr_http_req_release(req);
|
||||
scr_http_conn_missing_host(conn);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* A CONNECT request: 'connect' fires INSTEAD of 'request' — the
|
||||
* upgrade machinery's exact shape ((req, socket, head); the parser
|
||||
* steps aside; no listener destroys the socket, Node's default). The
|
||||
* h2 compat server's HTTP/1.1 arm lands here too; its HTTP/2 arm is
|
||||
* dispatched separately with the compatibility response handle. */
|
||||
if (req->method->len == 7 && memcmp(req->method->data, "CONNECT", 7) == 0) {
|
||||
if (is_connect) {
|
||||
memmove(conn->buf, conn->buf + head_len, conn->len - head_len);
|
||||
conn->len -= head_len;
|
||||
if (!scr_net_ls_has_live(&conn->srv->connect_ls)) {
|
||||
|
||||
@@ -1814,6 +1814,55 @@ static void scr_builtin_object_prototype_cleanup(void) {
|
||||
scr_dyn_release(prototype);
|
||||
}
|
||||
|
||||
/* The error constructors as values (an error's `constructor`, and the
|
||||
* globals in JavaScript): one interned function per constructor and thread,
|
||||
* indexed like scr_error_vts, with the constructor's name and length.
|
||||
* Calling one through the value is not supported yet, as for the other
|
||||
* interned constructors (scr_dyn_global_constructor). */
|
||||
static const char *const scr_error_ctor_names[] = {
|
||||
"Error", "TypeError", "RangeError", "SyntaxError", NULL,
|
||||
"ReferenceError", "EvalError", "URIError", "AggregateError",
|
||||
};
|
||||
#define SCR_ERROR_CTORS (sizeof scr_error_ctor_names / sizeof scr_error_ctor_names[0])
|
||||
static SCR_TL ScrDyn *scr_error_ctor_values[SCR_ERROR_CTORS];
|
||||
static SCR_TL bool scr_error_ctors_registered;
|
||||
|
||||
static void scr_error_ctors_cleanup(void) {
|
||||
for (size_t i = 0; i < SCR_ERROR_CTORS; i++) {
|
||||
scr_dyn_release(scr_error_ctor_values[i]);
|
||||
scr_error_ctor_values[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static ScrDyn *scr_error_ctor_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)args;
|
||||
(void)argc;
|
||||
char message[128];
|
||||
int length = snprintf(message, sizeof message,
|
||||
"calling the %s constructor through a stored value is not supported yet",
|
||||
(const char *)closure->fn);
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, (size_t)length, "SC2020");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_error_constructor(const char *name) {
|
||||
for (size_t i = 0; i < SCR_ERROR_CTORS; i++) {
|
||||
if (!scr_error_ctor_names[i] || strcmp(scr_error_ctor_names[i], name) != 0) continue;
|
||||
if (!scr_error_ctors_registered) {
|
||||
scr_error_ctors_registered = true;
|
||||
scr_atexit(scr_error_ctors_cleanup);
|
||||
}
|
||||
if (!scr_error_ctor_values[i]) {
|
||||
uint32_t length = strcmp(name, "AggregateError") == 0 ? 2 : 1;
|
||||
scr_error_ctor_values[i] =
|
||||
scr_dyn_new_func(scr_closure_new((void *)scr_error_ctor_names[i], 0), scr_error_ctor_call, length,
|
||||
"native:constructor", scr_error_ctor_names[i]);
|
||||
}
|
||||
return scr_dyn_retain(scr_error_ctor_values[i]);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_object_prototype(void) {
|
||||
if (!scr_builtin_object_prototype) {
|
||||
scr_builtin_object_prototype = scr_dyn_alloc(SCR_DYN_OBJ);
|
||||
@@ -7951,6 +8000,17 @@ ScrStr *scr_dyn_to_string(const ScrDyn *d, const ScrStr *enc) {
|
||||
return out;
|
||||
}
|
||||
case SCR_DYN_FUNC: {
|
||||
/* The interned builtin constructors print with their names, as in Node
|
||||
* (function Map() { [native code] }); other functions without a source
|
||||
* text print the anonymous native form. */
|
||||
if (d->v.fn.sig && strcmp(d->v.fn.sig, "native:constructor") == 0 && d->v.fn.name) {
|
||||
ScrJsonBuf b;
|
||||
scr_jb_init(&b);
|
||||
scr_jb_puts(&b, "function ");
|
||||
scr_jb_puts(&b, d->v.fn.name);
|
||||
scr_jb_puts(&b, "() { [native code] }");
|
||||
return scr_jb_finish(&b);
|
||||
}
|
||||
static const char f[] = "function () { [native code] }";
|
||||
return scr_str_new(f, sizeof f - 1);
|
||||
}
|
||||
@@ -11943,21 +12003,18 @@ static ScrDyn *scr_dyn_obj_read_receiver(const ScrDyn *d, const char *key, size_
|
||||
ScrDyn *own = scr_dyn_obj_get(d, key, key_len);
|
||||
if (own) return scr_dyn_retain(own);
|
||||
if (key_len == 11 && memcmp(key, "constructor", 11) == 0) {
|
||||
static const char *const tokens[] = {
|
||||
"[builtin Error]", "[builtin TypeError]",
|
||||
"[builtin RangeError]", "[builtin SyntaxError]",
|
||||
NULL, "[builtin ReferenceError]", "[builtin EvalError]", "[builtin URIError]",
|
||||
"[builtin AggregateError]",
|
||||
/* An error's constructor: the interned function value its global
|
||||
* reads as (scr_dyn_error_constructor). */
|
||||
static const char *const names[] = {
|
||||
"Error", "TypeError", "RangeError", "SyntaxError", NULL,
|
||||
"ReferenceError", "EvalError", "URIError", "AggregateError",
|
||||
};
|
||||
size_t hit = scr_errdyn_find(d, false);
|
||||
const ScrVt *vt = hit == SIZE_MAX ? NULL : scr_errdyn_cache[hit].err->vt;
|
||||
for (size_t kind = 0; vt && kind < sizeof tokens / sizeof tokens[0]; kind++) {
|
||||
if (!tokens[kind]) continue;
|
||||
for (size_t kind = 0; vt && kind < sizeof names / sizeof names[0]; kind++) {
|
||||
if (!names[kind]) continue;
|
||||
if (vt != &scr_error_vts[kind]) continue;
|
||||
ScrStr *token = scr_str_new(tokens[kind], strlen(tokens[kind]));
|
||||
ScrDyn *result = scr_dyn_new_str(token);
|
||||
scr_str_release(token);
|
||||
return result;
|
||||
return scr_dyn_error_constructor(names[kind]);
|
||||
}
|
||||
}
|
||||
return scr_dyn_retain(scr_dyn_undefined());
|
||||
|
||||
@@ -9225,6 +9225,10 @@ ScrCryptoHash *scr_crypto_hmac_new_dyn(ScrStr *alg, ScrDyn *key, ScrDyn *options
|
||||
if (key_object) {
|
||||
bytes = scr_bytes_new(SCR_BYTES_U8, (double)key_object_len);
|
||||
if (key_object_len) memcpy(bytes->data, key_object, key_object_len);
|
||||
} else if (scr_crypto_is_key_object(key)) {
|
||||
/* A private or public KeyObject: scr_pki.c reports Node's error. */
|
||||
scr_crypto_reject_asymmetric_secret(key);
|
||||
return NULL;
|
||||
} else if (key->kind == SCR_DYN_STR) {
|
||||
ScrStr *utf8 = encoding ? NULL : scr_str_new("utf8", 4);
|
||||
bytes = scr_bytes_from_str_checked(key->v.str, encoding ? encoding : utf8);
|
||||
@@ -9856,9 +9860,15 @@ const unsigned char *scr_key_object_bytes(const ScrDyn *value, size_t *len) {
|
||||
return key->data;
|
||||
}
|
||||
|
||||
/* `value instanceof KeyObject`. */
|
||||
void (*scr_crypto_reject_asymmetric_secret)(const ScrDyn *key) = NULL;
|
||||
|
||||
/* `value instanceof KeyObject`: a secret key, or a private or public key
|
||||
* (scr_pki.c, matched by its handle tags so this unit does not link it). */
|
||||
bool scr_crypto_is_key_object(const ScrDyn *value) {
|
||||
size_t len;
|
||||
if (value->kind == SCR_DYN_HANDLE &&
|
||||
(value->v.handle.tag == SCR_DYNH_PRIVATE_KEY || value->v.handle.tag == SCR_DYNH_PUBLIC_KEY))
|
||||
return true;
|
||||
return scr_key_object_bytes(value, &len) != NULL;
|
||||
}
|
||||
|
||||
|
||||
+256
-58
@@ -517,6 +517,9 @@ struct ScrNetSocket {
|
||||
int remote_port_cache;
|
||||
bool remote_v6_cache;
|
||||
ScrStr *pending_err; /* deferred connect failure */
|
||||
/* pending_err's code when its message does not name one (static
|
||||
* storage; NULL = the code the message maps to) */
|
||||
const char *pending_code;
|
||||
/* the caller-lookup dial (net.connect with a lookup option): true while
|
||||
* the lookup decides; dial_ips holds its answered addresses (+1 each),
|
||||
* dialed in order — a connect failure closes the fd and tries
|
||||
@@ -531,6 +534,9 @@ struct ScrNetSocket {
|
||||
size_t dial_n, dial_i;
|
||||
/* a host name the dial resolves first (+1; NULL for IP literals) */
|
||||
ScrStr *dial_host;
|
||||
/* the host a client dial was given, Node's socket._host (+1; NULL on
|
||||
* server sockets): tls.connect({ socket }) verifies against it */
|
||||
ScrStr *host_name;
|
||||
/* the in-flight dial chain's LAST failure — surfaced (moved into
|
||||
* pending_err) only when the list exhausts; a later success drops it */
|
||||
ScrStr *dial_err;
|
||||
@@ -607,6 +613,12 @@ struct ScrNetSocket {
|
||||
* until the proxy answers 200: writes buffer and the transport
|
||||
* handshake waits until the tunnel is up */
|
||||
struct ScrNetTunnel *tunnel;
|
||||
/* new tls.TLSSocket(socket) and tls.connect({ socket }): on the wrapped
|
||||
* socket, the socket that took over its connection (+1; end() and
|
||||
* destroy() act on it); on that socket, the one it took over (+1; its
|
||||
* 'close' fires just before this socket's, as in Node) */
|
||||
struct ScrNetSocket *adopted_by;
|
||||
struct ScrNetSocket *adopted_from;
|
||||
};
|
||||
|
||||
/* The CONNECT head still being sent from out_off, and the proxy's
|
||||
@@ -616,6 +628,9 @@ typedef struct ScrNetTunnel {
|
||||
size_t out_len, out_off;
|
||||
char *in;
|
||||
size_t in_len;
|
||||
/* bytes a wrapped socket left unsent, which go out before the
|
||||
* transport's own; no response head is read */
|
||||
bool send_only;
|
||||
} ScrNetTunnel;
|
||||
|
||||
/* The 'lookup' event's half of the runtime, installed by the first
|
||||
@@ -712,6 +727,7 @@ void scr_net_sock_release(ScrNetSocket *s) {
|
||||
free(s->dial_ips);
|
||||
scr_str_release(s->dial_err);
|
||||
scr_str_release(s->dial_host);
|
||||
scr_str_release(s->host_name);
|
||||
scr_str_release(s->unix_path);
|
||||
scr_net_ls_drop(&s->lookup_ls);
|
||||
if (s->lookup_evs != NULL) scr_net_lookup_ops->free(s);
|
||||
@@ -721,6 +737,8 @@ void scr_net_sock_release(ScrNetSocket *s) {
|
||||
if (s->pipe_dst) scr_net_sock_release(s->pipe_dst);
|
||||
if (s->conn_pending) scr_net_server_release(s->conn_pending);
|
||||
if (s->server) scr_net_server_release(s->server);
|
||||
if (s->adopted_by) scr_net_sock_release(s->adopted_by);
|
||||
if (s->adopted_from) scr_net_sock_release(s->adopted_from);
|
||||
if (s->fd >= 0) scr_net_close_fd_raw(s->fd);
|
||||
#ifdef SCR_RC_AUDIT
|
||||
scr_net_live--;
|
||||
@@ -962,7 +980,7 @@ static ScrNetSocket *scr_net_sock_new(void) {
|
||||
static void scr_net_sock_update_read(ScrNetSocket *s) {
|
||||
bool want = s->fd >= 0 && !s->connecting && !s->rd_eof && !s->user_paused &&
|
||||
(s->data_ls.n > 0 || s->pipe_dst != NULL || s->native_data != NULL ||
|
||||
s->tunnel != NULL /* the proxy's CONNECT answer */ ||
|
||||
(s->tunnel != NULL && !s->tunnel->send_only) /* the proxy's CONNECT answer */ ||
|
||||
s->flowing /* resume(): flow (and discard sans listeners) */ ||
|
||||
s->readable_ls.n > 0 /* paused-mode consumer: bytes buffer */ ||
|
||||
s->tops != NULL /* a transport is ALWAYS a consumer: the TLS
|
||||
@@ -977,7 +995,8 @@ static void scr_net_sock_update_read(ScrNetSocket *s) {
|
||||
* finished socket the same way */ ||
|
||||
(!s->eof_pending && s->rlen - s->rhead < SCR_NET_READ_AHEAD)
|
||||
/* no consumer yet: read ahead into the buffer, as Node's
|
||||
* stream does up to its high-water mark */);
|
||||
* stream does up to its high-water mark */) &&
|
||||
!(s->tunnel != NULL && s->tunnel->send_only);
|
||||
if (want && !s->read_armed) {
|
||||
scr_net_watch_read(s->fd, s, true);
|
||||
s->read_armed = true;
|
||||
@@ -1056,6 +1075,23 @@ static void scr_net_sock_buffer(ScrNetSocket *s, const char *data, size_t len) {
|
||||
* dead or ended write half (Node fires 'error' there; this slice drops —
|
||||
* SEMANTICS.md documents the bound). */
|
||||
static void scr_net_sock_write_raw(ScrNetSocket *s, const char *data, size_t len) {
|
||||
if (s->adopted_by != NULL) {
|
||||
/* Node writes these bytes to the wire as they are, between the TLS
|
||||
* records of the socket that took over (the peer then fails the
|
||||
* session). Bytes the kernel cannot take now end that socket. */
|
||||
ScrNetSocket *t = s->adopted_by;
|
||||
while (len > 0 && t->fd >= 0) {
|
||||
ssize_t sent = scr_net_send(t->fd, data, len);
|
||||
if (sent < 0 && errno == EINTR) continue;
|
||||
if (sent <= 0) {
|
||||
scr_net_sock_destroy(t);
|
||||
break;
|
||||
}
|
||||
data += sent;
|
||||
len -= (size_t)sent;
|
||||
}
|
||||
return;
|
||||
}
|
||||
/* A socket whose dial hasn't STARTED yet (the agent's deferred dial,
|
||||
* the caller-lookup wait) has no fd but is logically connecting —
|
||||
* bytes buffer and flush at establishment like any mid-connect write. */
|
||||
@@ -1490,6 +1526,12 @@ static void scr_net_sock_tunnel_pump(ScrNetSocket *s) {
|
||||
}
|
||||
scr_net_sock_update_write(s);
|
||||
if (s->fd < 0 || t->out_off < t->out_len) return;
|
||||
if (t->send_only) {
|
||||
scr_net_tunnel_free(t);
|
||||
s->tunnel = NULL;
|
||||
scr_net_sock_connected(s);
|
||||
return;
|
||||
}
|
||||
for (;;) {
|
||||
char buf[4096];
|
||||
ssize_t n = read(s->fd, buf, sizeof buf);
|
||||
@@ -1592,6 +1634,10 @@ static void scr_net_sock_transport_pump(ScrNetSocket *s) {
|
||||
if (scr_exc_pending()) return;
|
||||
scr_net_fire0_this(&s->ready_ls, s, SCR_DYNH_NET_SOCKET);
|
||||
if (scr_exc_pending()) return;
|
||||
if (s->tops->after_established) {
|
||||
s->tops->after_established(s->tctx);
|
||||
if (scr_exc_pending()) return;
|
||||
}
|
||||
scr_net_sock_flush(s);
|
||||
scr_net_sock_update_read(s);
|
||||
scr_net_sock_update_write(s);
|
||||
@@ -2250,6 +2296,8 @@ ScrStr *scr_net_blocking_lookup(ScrStr *host /*borrowed*/) {
|
||||
* autoselect), and any other name is resolved when the dial starts. */
|
||||
static void scr_net_sock_set_peer(ScrNetSocket *s, ScrStr *host /*borrowed, nullable*/) {
|
||||
const char *h = host && host->len > 0 ? host->data : "localhost";
|
||||
scr_str_release(s->host_name);
|
||||
s->host_name = scr_str_new(h, strlen(h));
|
||||
if (strcmp(h, "localhost") == 0) {
|
||||
h = "127.0.0.1";
|
||||
if (scr_net_lookup_ops != NULL) scr_net_lookup_ops->record(s, "localhost", 9, h, 4, NULL);
|
||||
@@ -3024,6 +3072,10 @@ void scr_net_sock_write_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/) {
|
||||
}
|
||||
|
||||
void scr_net_sock_end(ScrNetSocket *s) {
|
||||
if (s->adopted_by != NULL) {
|
||||
scr_net_sock_end(s->adopted_by);
|
||||
return;
|
||||
}
|
||||
if (s->fd < 0 || s->wr_ending) return;
|
||||
s->wr_ending = true;
|
||||
scr_net_sock_maybe_finish_write(s);
|
||||
@@ -3136,7 +3188,10 @@ void scr_net_sock_on_write_flush(ScrNetSocket *s, ScrClosure *cb /*moves*/) {
|
||||
double scr_net_sock_bytes_written(ScrNetSocket *s) { return (double)s->bytes_written; }
|
||||
|
||||
/* socket.readable: true until the read half is done (peer FIN / destroy). */
|
||||
bool scr_net_sock_readable(ScrNetSocket *s) { return s->fd >= 0 && !s->rd_eof; }
|
||||
bool scr_net_sock_readable(ScrNetSocket *s) {
|
||||
if (s->adopted_by != NULL) return scr_net_sock_readable(s->adopted_by);
|
||||
return s->fd >= 0 && !s->rd_eof;
|
||||
}
|
||||
|
||||
/* socket.setTimeout(ms): ms > 0 arms the idle timer NOW (Node starts the
|
||||
* clock at the call — connecting time counts), ms <= 0 disables. */
|
||||
@@ -3206,6 +3261,7 @@ ScrStr *scr_net_sock_remote_family(ScrNetSocket *s) {
|
||||
/* socket.localAddress / localPort / localFamily: read from the open
|
||||
* socket (undefined while connecting and after close, as in Node). */
|
||||
static bool scr_net_sock_local(ScrNetSocket *s, char *addr, size_t cap, int *port, bool *v6) {
|
||||
if (s->adopted_by != NULL) return scr_net_sock_local(s->adopted_by, addr, cap, port, v6);
|
||||
if (s->fd < 0 || s->connecting || s->dial_deferred || s->lookup_wait) return false;
|
||||
struct sockaddr_storage ss;
|
||||
socklen_t len = sizeof ss;
|
||||
@@ -4652,6 +4708,10 @@ bool scr_net_is_ipv4(const ScrDyn *input) { return scr_net_is_ip(input) == 4; }
|
||||
bool scr_net_is_ipv6(const ScrDyn *input) { return scr_net_is_ip(input) == 6; }
|
||||
|
||||
void scr_net_sock_destroy(ScrNetSocket *s) {
|
||||
if (s->adopted_by != NULL) {
|
||||
scr_net_sock_destroy(s->adopted_by);
|
||||
return;
|
||||
}
|
||||
if (s->fd < 0 && !s->in_registry) return;
|
||||
s->rd_eof = true;
|
||||
s->wr_done = true;
|
||||
@@ -4833,7 +4893,8 @@ void scr_net_sock_on_error(ScrNetSocket *s, ScrClosure *cb /*moves*/, ScrChildEr
|
||||
}
|
||||
|
||||
void scr_net_sock_on_connect(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once) {
|
||||
if (s->close_emitted || (!s->connecting && s->fd < 0)) {
|
||||
/* a socket that took over another's connection never emits 'connect' */
|
||||
if (s->close_emitted || (!s->connecting && s->fd < 0) || s->adopted_from != NULL) {
|
||||
scr_closure_release(cb);
|
||||
return;
|
||||
}
|
||||
@@ -4939,12 +5000,15 @@ void scr_net_sock_clear_native_reader(ScrNetSocket *s) {
|
||||
|
||||
/* socket.destroyed — true once the fd is gone (destroy() or full close),
|
||||
* Node's flag for "this stream is done". */
|
||||
bool scr_net_sock_destroyed(ScrNetSocket *s) { return s->fd < 0; }
|
||||
bool scr_net_sock_destroyed(ScrNetSocket *s) {
|
||||
return s->adopted_by != NULL ? scr_net_sock_destroyed(s->adopted_by) : s->fd < 0;
|
||||
}
|
||||
|
||||
/* socket.writable — Node's stream flag: the write half is open (no end()
|
||||
* yet, no FIN sent, fd alive). Connecting sockets answer true — writes
|
||||
* queue, exactly Node. */
|
||||
bool scr_net_sock_writable(ScrNetSocket *s) {
|
||||
if (s->adopted_by != NULL) return scr_net_sock_writable(s->adopted_by);
|
||||
return s->fd >= 0 && !s->wr_ending && !s->wr_done;
|
||||
}
|
||||
|
||||
@@ -4996,8 +5060,78 @@ void scr_net_sock_set_transport(ScrNetSocket *s, const ScrNetTransportOps *ops,
|
||||
scr_net_sock_update_write(s);
|
||||
}
|
||||
|
||||
/* A fresh socket takes over raw's open connection: the fd, the bytes it
|
||||
* buffered either way and the peer identity. raw keeps its listeners and
|
||||
* its server's accounting but does no I/O of its own; end() and destroy()
|
||||
* on it act on the new socket, and its 'close' fires just before the new
|
||||
* socket's. Bytes raw had not sent yet go out first. +1; the caller sets
|
||||
* the transport, then starts it with scr_net_sock_adopt_start. NULL when
|
||||
* raw has no open, idle connection to give (connecting, closed, already
|
||||
* wrapped, or carrying a transport). */
|
||||
ScrNetSocket *scr_net_sock_adopt(ScrNetSocket *raw) {
|
||||
if (raw->fd < 0 || scr_net_sock_connecting(raw) || raw->adopted_by != NULL || raw->tops != NULL)
|
||||
return NULL;
|
||||
ScrNetSocket *t = scr_net_sock_new();
|
||||
scr_str_release(scr_net_sock_remote_address(raw)); /* cache the peer while the fd is raw's */
|
||||
if (raw->remote_cache) {
|
||||
t->remote_cache = scr_str_retain(raw->remote_cache);
|
||||
t->remote_port_cache = raw->remote_port_cache;
|
||||
t->remote_v6_cache = raw->remote_v6_cache;
|
||||
}
|
||||
memcpy(t->peer_ip, raw->peer_ip, sizeof t->peer_ip);
|
||||
t->peer_port = raw->peer_port;
|
||||
t->unrefd = raw->unrefd;
|
||||
t->rd_eof = raw->rd_eof;
|
||||
scr_net_sock_timer_cancel(raw);
|
||||
if (raw->read_armed) scr_net_watch_read(raw->fd, raw, false);
|
||||
if (raw->write_armed) scr_net_watch_write(raw->fd, raw, false);
|
||||
raw->read_armed = raw->write_armed = false;
|
||||
t->fd = raw->fd;
|
||||
raw->fd = -1;
|
||||
if (raw->rlen > raw->rhead) scr_net_sock_append_rbuf(t, raw->rbuf + raw->rhead, raw->rlen - raw->rhead);
|
||||
raw->rhead = raw->rlen = 0;
|
||||
while (raw->wlen > raw->whead) {
|
||||
ssize_t sent = scr_net_send(t->fd, raw->wbuf + raw->whead, raw->wlen - raw->whead);
|
||||
if (sent < 0 && errno == EINTR) continue;
|
||||
if (sent <= 0) break; /* EAGAIN, or an error the transport will meet */
|
||||
raw->whead += (size_t)sent;
|
||||
}
|
||||
if (raw->wlen > raw->whead) {
|
||||
ScrNetTunnel *tun = calloc(1, sizeof *tun);
|
||||
if (!tun) scr_net_oom();
|
||||
tun->out_len = raw->wlen - raw->whead;
|
||||
tun->out = malloc(tun->out_len);
|
||||
if (!tun->out) scr_net_oom();
|
||||
memcpy(tun->out, raw->wbuf + raw->whead, tun->out_len);
|
||||
tun->send_only = true;
|
||||
t->tunnel = tun;
|
||||
}
|
||||
raw->whead = raw->wlen = 0;
|
||||
raw->adopted_by = scr_net_sock_retain(t);
|
||||
t->adopted_from = scr_net_sock_retain(raw);
|
||||
scr_net_sock_register(t);
|
||||
return t;
|
||||
}
|
||||
|
||||
/* Starts an adopted socket once its transport is set: the unsent bytes
|
||||
* drain first, then the handshake runs (over any bytes raw had already
|
||||
* read). */
|
||||
void scr_net_sock_adopt_start(ScrNetSocket *t) {
|
||||
if (t->tunnel != NULL) {
|
||||
scr_net_sock_update_write(t);
|
||||
return;
|
||||
}
|
||||
/* the handshake starts from the poller (the socket is writable), never
|
||||
* the constructing stack — Node's start is asynchronous too */
|
||||
scr_net_sock_transport_want_write(t);
|
||||
}
|
||||
|
||||
int scr_net_sock_fd(ScrNetSocket *s) { return s->fd; }
|
||||
|
||||
/* The host a client socket was told to dial (Node's socket._host). +1;
|
||||
* NULL on server sockets. */
|
||||
ScrStr *scr_net_sock_dial_name(ScrNetSocket *s) { return scr_str_retain(s->host_name); }
|
||||
|
||||
/* Drain up to n bytes of the receive buffer (peeked/unshifted bytes) —
|
||||
* consumers call this BEFORE the fd so re-queued bytes keep their
|
||||
* position in the stream. */
|
||||
@@ -5050,6 +5184,32 @@ void scr_net_sock_replay(ScrNetSocket *s, const char *data, size_t len) {
|
||||
s->rcap = total;
|
||||
}
|
||||
|
||||
/* The peer closed before the handshake finished, on a socket whose
|
||||
* stream the program already holds (new tls.TLSSocket): 'end', then
|
||||
* 'close' without an error, as Node reports it. */
|
||||
void scr_net_sock_transport_eof(ScrNetSocket *s) {
|
||||
scr_net_sock_eof(s);
|
||||
if (s->fd >= 0) scr_net_sock_close_fd(s);
|
||||
}
|
||||
|
||||
/* A listener for the handshake's completion once TCP is up (a socket
|
||||
* tls.connect({ socket }) took over is up from the start): it registers
|
||||
* until the transport establishes. */
|
||||
void scr_net_sock_on_established(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once) {
|
||||
if (s->close_emitted || s->fd < 0 || s->tops == NULL || s->t_est) {
|
||||
scr_closure_release(cb);
|
||||
return;
|
||||
}
|
||||
scr_net_ls_add(&s->conn_ls, cb, NULL, once);
|
||||
}
|
||||
|
||||
/* scr_net_sock_transport_error with the error's code given (static
|
||||
* storage), for messages whose text does not name it. */
|
||||
void scr_net_sock_transport_error_code(ScrNetSocket *s, const char *msg, const char *code) {
|
||||
if (!s->pending_err) s->pending_code = code;
|
||||
scr_net_sock_transport_error(s, msg);
|
||||
}
|
||||
|
||||
void scr_net_sock_transport_error(ScrNetSocket *s, const char *msg) {
|
||||
if (msg != NULL) {
|
||||
if (!s->pending_err) s->pending_err = scr_str_new(msg, strlen(msg));
|
||||
@@ -5141,6 +5301,72 @@ static void scr_net_server_settle(ScrNetServer *srv) {
|
||||
scr_net_server_unregister(srv);
|
||||
}
|
||||
|
||||
/* Fires a closed socket's 'close' and settles it: its server's count
|
||||
* drains, the protocol layer hears it, listeners drop and it leaves the
|
||||
* registry. A socket that took over another's connection settles that one
|
||||
* first, so the wrapped socket's 'close' comes just before its own. False
|
||||
* with an exception pending. */
|
||||
static bool scr_net_sock_settle(ScrNetSocket *sock) {
|
||||
ScrNetSocket *raw = sock->adopted_from;
|
||||
if (raw != NULL) {
|
||||
sock->adopted_from = NULL;
|
||||
if (raw->adopted_by == sock) {
|
||||
raw->adopted_by = NULL;
|
||||
scr_net_sock_release(sock);
|
||||
}
|
||||
bool ok = raw->close_emitted || scr_net_sock_settle(raw);
|
||||
scr_net_sock_release(raw);
|
||||
if (!ok) return false;
|
||||
}
|
||||
sock->close_emitted = true;
|
||||
sock->emit_close = false;
|
||||
/* The owning server's count drains FIRST, and a now-drained closing
|
||||
* server settles before this socket's own 'close' fires — Node's
|
||||
* observed order (differential: the net-echo fixture). */
|
||||
if (sock->server) {
|
||||
ScrNetServer *srv = sock->server;
|
||||
sock->server = NULL;
|
||||
srv->nconns--;
|
||||
if (srv->closing && srv->nconns == 0 && !srv->close_emitted) {
|
||||
scr_net_server_settle(srv);
|
||||
}
|
||||
scr_net_server_release(srv);
|
||||
if (scr_exc_pending()) return false;
|
||||
}
|
||||
if (sock->native_closed) {
|
||||
sock->native_closed(sock->native_ctx);
|
||||
if (scr_exc_pending()) return false;
|
||||
}
|
||||
{
|
||||
ScrDyn *had_error = scr_dyn_new_bool(sock->had_error);
|
||||
scr_net_fire_arg_this(&sock->close_ls, had_error, sock, SCR_DYNH_NET_SOCKET);
|
||||
scr_dyn_release(had_error);
|
||||
}
|
||||
/* settle: listeners drop (cycle story) */
|
||||
scr_net_ls_drop(&sock->data_ls);
|
||||
scr_net_ls_drop(&sock->end_ls);
|
||||
scr_net_ls_drop(&sock->close_ls);
|
||||
scr_net_ls_drop(&sock->err_ls);
|
||||
scr_net_ls_drop(&sock->conn_ls);
|
||||
scr_net_ls_drop(&sock->timeout_ls);
|
||||
scr_net_ls_drop(&sock->readable_ls);
|
||||
scr_net_ls_drop(&sock->finish_ls);
|
||||
scr_net_ls_drop(&sock->wcb_ls);
|
||||
scr_net_ls_drop(&sock->ready_ls);
|
||||
scr_net_ls_drop(&sock->lookup_ls);
|
||||
if (sock->tops && sock->tops->settle) sock->tops->settle(sock->tctx);
|
||||
if (sock->pipe_dst) {
|
||||
scr_net_sock_release(sock->pipe_dst);
|
||||
sock->pipe_dst = NULL;
|
||||
}
|
||||
if (sock->conn_pending) {
|
||||
scr_net_server_release(sock->conn_pending);
|
||||
sock->conn_pending = NULL;
|
||||
}
|
||||
scr_net_sock_unregister(sock);
|
||||
return !scr_exc_pending();
|
||||
}
|
||||
|
||||
/* Sockets first (a dying connection may drain its server), servers after.
|
||||
* Every fire checks for a pending exception and bails — the loop surfaces
|
||||
* it as an uncaught throw. */
|
||||
@@ -5200,7 +5426,19 @@ static void scr_net_sweep(void) {
|
||||
* listeners — and the unhandled-'error' exit */
|
||||
bool consumed = false;
|
||||
if (sock->native_err) consumed = sock->native_err(sock->native_ctx, msg);
|
||||
if (!consumed && !scr_exc_pending()) scr_net_fire_err_this(&sock->err_ls, msg, sock, SCR_DYNH_NET_SOCKET);
|
||||
if (!consumed && !scr_exc_pending()) {
|
||||
ScrError *coded = NULL;
|
||||
ScrError *saved = scr_event_error_payload;
|
||||
if (sock->pending_code != NULL) {
|
||||
coded = scr_error_new(SCR_ERR_ERROR, msg);
|
||||
coded->code = scr_str_new(sock->pending_code, strlen(sock->pending_code));
|
||||
scr_event_error_payload = coded;
|
||||
}
|
||||
scr_net_fire_err_this(&sock->err_ls, msg, sock, SCR_DYNH_NET_SOCKET);
|
||||
scr_event_error_payload = saved;
|
||||
if (coded != NULL) scr_error_release(coded);
|
||||
}
|
||||
sock->pending_code = NULL;
|
||||
scr_str_release(msg);
|
||||
if (scr_exc_pending()) {
|
||||
scr_net_sock_release(sock);
|
||||
@@ -5208,58 +5446,7 @@ static void scr_net_sweep(void) {
|
||||
}
|
||||
}
|
||||
if (sock->emit_close && !sock->close_emitted && sock->fd < 0) {
|
||||
sock->close_emitted = true;
|
||||
sock->emit_close = false;
|
||||
/* The owning server's count drains FIRST, and a now-drained closing
|
||||
* server settles before this socket's own 'close' fires — Node's
|
||||
* observed order (differential: the net-echo fixture). */
|
||||
if (sock->server) {
|
||||
ScrNetServer *srv = sock->server;
|
||||
sock->server = NULL;
|
||||
srv->nconns--;
|
||||
if (srv->closing && srv->nconns == 0 && !srv->close_emitted) {
|
||||
scr_net_server_settle(srv);
|
||||
}
|
||||
scr_net_server_release(srv);
|
||||
if (scr_exc_pending()) {
|
||||
scr_net_sock_release(sock);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (sock->native_closed) {
|
||||
sock->native_closed(sock->native_ctx);
|
||||
if (scr_exc_pending()) {
|
||||
scr_net_sock_release(sock);
|
||||
return;
|
||||
}
|
||||
}
|
||||
{
|
||||
ScrDyn *had_error = scr_dyn_new_bool(sock->had_error);
|
||||
scr_net_fire_arg_this(&sock->close_ls, had_error, sock, SCR_DYNH_NET_SOCKET);
|
||||
scr_dyn_release(had_error);
|
||||
}
|
||||
/* settle: listeners drop (cycle story) */
|
||||
scr_net_ls_drop(&sock->data_ls);
|
||||
scr_net_ls_drop(&sock->end_ls);
|
||||
scr_net_ls_drop(&sock->close_ls);
|
||||
scr_net_ls_drop(&sock->err_ls);
|
||||
scr_net_ls_drop(&sock->conn_ls);
|
||||
scr_net_ls_drop(&sock->timeout_ls);
|
||||
scr_net_ls_drop(&sock->readable_ls);
|
||||
scr_net_ls_drop(&sock->finish_ls);
|
||||
scr_net_ls_drop(&sock->wcb_ls);
|
||||
scr_net_ls_drop(&sock->ready_ls);
|
||||
scr_net_ls_drop(&sock->lookup_ls);
|
||||
if (sock->pipe_dst) {
|
||||
scr_net_sock_release(sock->pipe_dst);
|
||||
sock->pipe_dst = NULL;
|
||||
}
|
||||
if (sock->conn_pending) {
|
||||
scr_net_server_release(sock->conn_pending);
|
||||
sock->conn_pending = NULL;
|
||||
}
|
||||
scr_net_sock_unregister(sock);
|
||||
if (scr_exc_pending()) {
|
||||
if (!scr_net_sock_settle(sock)) {
|
||||
scr_net_sock_release(sock);
|
||||
return;
|
||||
}
|
||||
@@ -5504,6 +5691,17 @@ static void scr_net_cleanup_atexit(void) {
|
||||
scr_net_server_release(s->server);
|
||||
s->server = NULL;
|
||||
}
|
||||
/* a wrapped socket and the socket that took it over hold each other */
|
||||
if (s->adopted_by) {
|
||||
ScrNetSocket *t = s->adopted_by;
|
||||
s->adopted_by = NULL;
|
||||
scr_net_sock_release(t);
|
||||
}
|
||||
if (s->adopted_from) {
|
||||
ScrNetSocket *raw = s->adopted_from;
|
||||
s->adopted_from = NULL;
|
||||
scr_net_sock_release(raw);
|
||||
}
|
||||
scr_net_sock_unregister(s);
|
||||
}
|
||||
while (scr_net_servers) {
|
||||
|
||||
@@ -351,8 +351,22 @@ typedef struct ScrNetTransportOps {
|
||||
bool (*pending)(void *tctx);
|
||||
void (*shutdown_write)(void *tctx);
|
||||
void (*free)(void *tctx);
|
||||
/* nullable: runs after the established socket's 'connect' listeners
|
||||
* (the TLS 'secure' event, which Node emits after secureConnect) */
|
||||
void (*after_established)(void *tctx);
|
||||
/* nullable: runs when the closed socket settles, after its own
|
||||
* listeners drop; the transport drops the listeners it holds */
|
||||
void (*settle)(void *tctx);
|
||||
} ScrNetTransportOps;
|
||||
void scr_net_sock_set_transport(ScrNetSocket *s, const ScrNetTransportOps *ops, void *tctx);
|
||||
/* new tls.TLSSocket(socket) and tls.connect({ socket }): a fresh socket
|
||||
* takes over an open connection (fd, buffered bytes, peer identity); the
|
||||
* old socket stays open without I/O, forwards end() and destroy(), and
|
||||
* fires 'close' just before the new one. +1, or NULL when the socket has
|
||||
* no open, idle connection to give. Set the transport on the result, then
|
||||
* call scr_net_sock_adopt_start. */
|
||||
ScrNetSocket *scr_net_sock_adopt(ScrNetSocket *raw);
|
||||
void scr_net_sock_adopt_start(ScrNetSocket *t);
|
||||
/* Routes a client socket through an HTTP CONNECT proxy. Call it right
|
||||
* after the dial (scr_net_connect to the proxy) and before any transport
|
||||
* is set: once TCP is up, `request` (the CONNECT head, copied) is sent
|
||||
@@ -366,6 +380,9 @@ void scr_net_sock_set_tunnel(ScrNetSocket *s, const char *request, size_t len);
|
||||
int scr_net_tunnel_status(const char *head, size_t len);
|
||||
void scr_net_tunnel_failure(int status, char *msg, size_t cap);
|
||||
int scr_net_sock_fd(ScrNetSocket *s);
|
||||
/* The host a client socket was told to dial (Node's socket._host). +1;
|
||||
* NULL on server sockets. */
|
||||
ScrStr *scr_net_sock_dial_name(ScrNetSocket *s);
|
||||
/* Drain up to n bytes of the socket's receive buffer (peeked/unshifted
|
||||
* bytes — the demux path); consumers call it before the fd. */
|
||||
size_t scr_net_sock_take_buffered(ScrNetSocket *s, char *buf, size_t n);
|
||||
@@ -385,6 +402,16 @@ void scr_net_sock_replay(ScrNetSocket *s, const char *data, size_t len);
|
||||
* 'error' + teardown to the sweep; NULL tears down without an 'error'
|
||||
* event, Node's default for an unhandled server-side handshake failure. */
|
||||
void scr_net_sock_transport_error(ScrNetSocket *s, const char *msg);
|
||||
/* The same with the error's code given (static storage), for messages
|
||||
* whose text does not name it. */
|
||||
void scr_net_sock_transport_error_code(ScrNetSocket *s, const char *msg, const char *code);
|
||||
/* The peer closed before the handshake finished on a wrapped socket:
|
||||
* 'end', then 'close' without an error. */
|
||||
void scr_net_sock_transport_eof(ScrNetSocket *s);
|
||||
/* Registers a listener that fires when the transport establishes, once
|
||||
* TCP is up (a socket that took over an open connection is up from the
|
||||
* start). */
|
||||
void scr_net_sock_on_established(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once);
|
||||
/* Defer the server's 'connection' listeners to handshake completion —
|
||||
* tls.createServer's handler is the 'secureConnection' event, fired with
|
||||
* the socket only once the TLS layer stands (Node's timing). */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,79 @@
|
||||
/* The key and signature core of scr_pki.c, for the WebCrypto layer
|
||||
* (scr_webcrypto_pki.c) and the Ed25519 unit (scr_ed25519.c). Keys are
|
||||
* reference-counted RSA keys, EC keys on P-256, P-384, P-521 or secp256k1,
|
||||
* or Ed25519 keys. Functions answering NULL or false set no error unless
|
||||
* they say so; byte results are +1. */
|
||||
#ifndef SCR_PKI_H
|
||||
#define SCR_PKI_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct SpKey ScrPkiKey;
|
||||
|
||||
/* Throws an error of `kind` (with `code`, or none for NULL) whose message
|
||||
* is the printf format's whole output, at any length. */
|
||||
void scr_pki_throw_fmt(int kind, const char *code, const char *fmt, ...) __attribute__((format(printf, 3, 4)));
|
||||
|
||||
/* The Ed25519 arithmetic (scr_ed25519.c), installed at startup in
|
||||
* programs that name Ed25519; NULL otherwise. Keys are the 32-byte seed
|
||||
* and the 32-byte encoded public point. */
|
||||
typedef struct {
|
||||
void (*public_key)(uint8_t pub[32], const uint8_t seed[32]);
|
||||
void (*sign)(uint8_t sig[64], const uint8_t seed[32], const uint8_t pub[32], const uint8_t *m, size_t n);
|
||||
bool (*verify)(const uint8_t pub[32], const uint8_t *m, size_t n, const uint8_t sig[64]);
|
||||
} ScrPkiEd25519;
|
||||
extern const ScrPkiEd25519 *scr_pki_ed25519;
|
||||
|
||||
/* Whether the Ed25519 arithmetic is linked; false with a coded SC2020
|
||||
* error pending otherwise. */
|
||||
bool scr_pki_ed25519_ready(void);
|
||||
|
||||
ScrPkiKey *scr_pki_key_retain(ScrPkiKey *key);
|
||||
void scr_pki_key_release(ScrPkiKey *key);
|
||||
bool scr_pki_key_is_private(const ScrPkiKey *key);
|
||||
bool scr_pki_key_is_rsa(const ScrPkiKey *key);
|
||||
bool scr_pki_key_is_ed25519(const ScrPkiKey *key);
|
||||
/* The EC key's JWK curve name ("P-256", ...), or NULL for RSA keys. */
|
||||
const char *scr_pki_key_curve(const ScrPkiKey *key);
|
||||
/* The RSA modulus length in bits. */
|
||||
size_t scr_pki_key_bits(const ScrPkiKey *key);
|
||||
/* The RSA public exponent's big-endian bytes, without leading zeros. */
|
||||
ScrBytes *scr_pki_key_exponent(const ScrPkiKey *key);
|
||||
|
||||
/* A key from SubjectPublicKeyInfo (public) or PKCS#8 (private) DER. */
|
||||
ScrPkiKey *scr_pki_key_parse_der(const uint8_t *der, size_t len, bool private_key);
|
||||
/* A key from a JWK object (borrowed; a plain object). */
|
||||
ScrPkiKey *scr_pki_key_from_jwk(const ScrDyn *jwk, bool private_key);
|
||||
/* A public EC key from an uncompressed or compressed point. */
|
||||
ScrPkiKey *scr_pki_key_from_point(const char *curve, const uint8_t *point, size_t len);
|
||||
ScrPkiKey *scr_pki_key_public_of(ScrPkiKey *key);
|
||||
ScrPkiKey *scr_pki_generate_rsa(unsigned bits, uint64_t exponent);
|
||||
ScrPkiKey *scr_pki_generate_ec(const char *curve);
|
||||
/* NULL with a coded SC2020 error pending when the program does not link
|
||||
* the Ed25519 unit. */
|
||||
ScrPkiKey *scr_pki_generate_ed25519(void);
|
||||
/* A public Ed25519 key from its 32-byte encoding. */
|
||||
ScrPkiKey *scr_pki_key_from_ed25519_raw(const uint8_t *raw, size_t len);
|
||||
/* The Ed25519 public key's 32-byte encoding. */
|
||||
ScrBytes *scr_pki_export_ed25519_raw(ScrPkiKey *key);
|
||||
|
||||
/* SubjectPublicKeyInfo (public keys) or PKCS#8 (private keys) DER. */
|
||||
ScrBytes *scr_pki_export_der(ScrPkiKey *key);
|
||||
/* The EC public key's uncompressed point. */
|
||||
ScrBytes *scr_pki_export_point(ScrPkiKey *key);
|
||||
/* The JWK members in Node's order (kty, n, e, ... or kty, x, y, crv, d),
|
||||
* +1. */
|
||||
ScrDyn *scr_pki_export_jwk(ScrPkiKey *key);
|
||||
|
||||
/* Signs `data` with the digest named by its WebCrypto name ("SHA-256"):
|
||||
* RSA PKCS#1 v1.5 (pss false), RSA-PSS with `salt` bytes, or ECDSA as
|
||||
* IEEE P1363 r || s. Ed25519 keys sign the message itself (hash
|
||||
* ignored). */
|
||||
ScrBytes *scr_pki_sign(ScrPkiKey *key, const char *hash, bool pss, int salt, const uint8_t *data,
|
||||
size_t len);
|
||||
bool scr_pki_verify(ScrPkiKey *key, const char *hash, bool pss, int salt, const uint8_t *data,
|
||||
size_t len, const uint8_t *signature, size_t signature_len);
|
||||
|
||||
#endif
|
||||
+725
-235
File diff suppressed because it is too large
Load Diff
@@ -272,6 +272,7 @@ ScrStream *scr_stream_set_push_encoding(ScrStream *s, ScrStr *enc);
|
||||
bool scr_stream_push_null(ScrStream *s);
|
||||
void scr_stream_unshift(ScrStream *s, ScrBytes *chunk);
|
||||
void scr_stream_unshift_str(ScrStream *s, ScrStr *str);
|
||||
void scr_stream_unshift_dyn(ScrStream *s, const ScrDyn *d);
|
||||
ScrBytes *scr_stream_read(ScrStream *s, double size);
|
||||
ScrStream *scr_stream_on_dyn(ScrStream *s, ScrStr *name, ScrDyn *cb, bool once, bool prepend);
|
||||
ScrDyn *scr_stream_read_dyn(ScrStream *s, ScrDyn *size); /* +1 or NULL; size < 0 = all/first; throws once encoded (the static type is Buffer|null) */
|
||||
@@ -291,6 +292,8 @@ typedef struct ScrPromise ScrPromise; /* full declaration below (C11 repeat) */
|
||||
* each chunk before asking again). */
|
||||
bool scr_stream_push_dyn(ScrStream *s, const ScrDyn *d); /* borrows d; tag dispatch */
|
||||
bool scr_stream_write_dyn(ScrStream *s, const ScrDyn *d, ScrClosure *cb); /* cb moves */
|
||||
/* write(chunk, encoding, cb): enc borrowed, NULL or undefined when absent */
|
||||
bool scr_stream_write_enc(ScrStream *s, const ScrDyn *chunk, ScrDyn *enc, ScrClosure *cb);
|
||||
ScrPromise *scr_stream_next_chunk(ScrStream *s);
|
||||
ScrPromise *scr_stream_next_chunk_dyn(ScrStream *s);
|
||||
/* node:stream/promises — the promise forms over the finished/pipeline
|
||||
@@ -475,6 +478,28 @@ void scr_stream_final_done(ScrStream *s, ScrError *err);
|
||||
void scr_stream_destroy_done(ScrStream *s, ScrError *err);
|
||||
void scr_stream_transform_done(ScrStream *s, ScrError *err, ScrBytes *data, ScrStr *data_str);
|
||||
void scr_stream_flush_done(ScrStream *s, ScrError *err, ScrBytes *data, ScrStr *data_str);
|
||||
/* The same completions with a checked-dynamic data value (borrowed; NULL,
|
||||
* null and undefined push nothing): an object-mode Transform's data, or an
|
||||
* untyped callback's. err moves. */
|
||||
/* objectMode: scr_stream_set_object_mode applies the option's sides right
|
||||
* after construction; scr_stream_writes_values answers whether the write
|
||||
* and transform steps receive dyn values instead of Buffers. */
|
||||
void scr_stream_set_object_mode(ScrStream *s, bool readable, bool writable);
|
||||
ScrStream *scr_stream_object_mode(ScrStream *s, bool readable, bool writable);
|
||||
bool scr_stream_writes_values(ScrStream *s);
|
||||
/* A compiled write or transform callback's chunk by its declared type: a
|
||||
* Buffer (+1, or NULL with a TypeError for another object-mode value), a
|
||||
* dyn value (+1), and the encoding argument (+1). */
|
||||
ScrBytes *scr_stream_chunk_bytes(ScrStream *s, ScrBytes *chunk);
|
||||
struct ScrDyn *scr_stream_chunk_value(ScrStream *s, ScrBytes *chunk);
|
||||
ScrStr *scr_stream_chunk_encoding(ScrStream *s);
|
||||
ScrDyn *scr_stream_chunk_encoding_value(ScrStream *s);
|
||||
/* emit('data', value) with a checked-dynamic chunk (borrowed). */
|
||||
bool scr_stream_emit_value(ScrStream *s, ScrStr *name, struct ScrDyn *v);
|
||||
/* Whether a dyn for-await step (readable.nextChunkDyn) answered the end. */
|
||||
bool scr_stream_chunk_done(const struct ScrDyn *v);
|
||||
void scr_stream_transform_done_value(ScrStream *s, ScrError *err, struct ScrDyn *v);
|
||||
void scr_stream_flush_done_value(ScrStream *s, ScrError *err, struct ScrDyn *v);
|
||||
|
||||
/* Runtime-backed streams (the fs streams in scr_fs_stream.c).
|
||||
* - construct: Node's _construct. The hook runs on a tick after
|
||||
|
||||
@@ -305,7 +305,7 @@ static ScrPromise *src_next(OpSrc *src) {
|
||||
}
|
||||
|
||||
static bool src_done(const OpSrc *src, const ScrDyn *v) {
|
||||
return src->s ? v->kind == SCR_DYN_UNDEF : v == op_k_done;
|
||||
return src->s ? scr_stream_chunk_done(v) : v == op_k_done;
|
||||
}
|
||||
|
||||
/* The Readable iterator's return(): the iterator generator resumes at its
|
||||
@@ -1492,16 +1492,6 @@ static void op_from_step_k(void *self, ScrDyn *v) {
|
||||
}
|
||||
int pushed = scr_stream_push_object(out, v);
|
||||
scr_dyn_release(v);
|
||||
if (pushed == -2) {
|
||||
op_from_destroy_with(out, op_unsupported(
|
||||
"a stream operator output that mixes strings and Buffers"));
|
||||
return;
|
||||
}
|
||||
if (pushed < 0) {
|
||||
op_from_destroy_with(out, op_unsupported(
|
||||
"a stream operator value that is not a string or Buffer (object-mode streams of other values)"));
|
||||
return;
|
||||
}
|
||||
if (pushed) {
|
||||
from_next(f, out);
|
||||
return;
|
||||
|
||||
+315
-12
@@ -368,6 +368,10 @@ typedef struct ScrTlsSrv {
|
||||
/* maxVersion TLSv1.2: the ClientHello is pre-parsed so a hello that
|
||||
* offers only later versions gets OpenSSL's protocol_version alert. */
|
||||
bool max_tls12;
|
||||
/* new tls.TLSSocket(socket, { secureContext }): the context whose
|
||||
* certificate and key this config serves (+1; NULL when cert/key are the
|
||||
* config's own) */
|
||||
ScrSecureCtx *ctx;
|
||||
} ScrTlsSrv;
|
||||
|
||||
/* The ALPN answers (static storage — mbedTLS keeps the pointers). Plain
|
||||
@@ -405,6 +409,28 @@ static ScrTlsSrv *scr_tls_srv_new(const char *cert, size_t cert_len, const char
|
||||
return s;
|
||||
}
|
||||
|
||||
/* A server config that serves a SecureContext's certificate and key. */
|
||||
static ScrTlsSrv *scr_tls_srv_from_ctx(ScrSecureCtx *ctx /*borrowed*/) {
|
||||
scr_tls_rng_init();
|
||||
ScrTlsSrv *s = calloc(1, sizeof *s);
|
||||
if (!s) scr_tls_oom();
|
||||
s->rc = 1;
|
||||
mbedtls_x509_crt_init(&s->cert);
|
||||
mbedtls_pk_init(&s->pk);
|
||||
mbedtls_ssl_config_init(&s->conf);
|
||||
if (mbedtls_ssl_config_defaults(&s->conf, MBEDTLS_SSL_IS_SERVER, MBEDTLS_SSL_TRANSPORT_STREAM,
|
||||
MBEDTLS_SSL_PRESET_DEFAULT) != 0) {
|
||||
scr_tls_oom();
|
||||
}
|
||||
mbedtls_ssl_conf_rng(&s->conf, mbedtls_ctr_drbg_random, &scr_tls_drbg);
|
||||
mbedtls_ssl_conf_authmode(&s->conf, MBEDTLS_SSL_VERIFY_NONE);
|
||||
s->ctx = scr_secure_ctx_retain(ctx);
|
||||
if (mbedtls_ssl_conf_own_cert(&s->conf, &ctx->cert, &ctx->pk) != 0) {
|
||||
scr_tls_bad_material("certificate/key pair", 0);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static ScrTlsSrv *scr_tls_srv_retain(ScrTlsSrv *s) {
|
||||
s->rc++;
|
||||
return s;
|
||||
@@ -419,6 +445,7 @@ static void scr_tls_srv_release(ScrTlsSrv *s) {
|
||||
mbedtls_x509_crt_free(&s->cert);
|
||||
mbedtls_pk_free(&s->pk);
|
||||
scr_tls_alpn_free(s->alpn);
|
||||
scr_secure_ctx_release(s->ctx);
|
||||
free(s);
|
||||
}
|
||||
|
||||
@@ -629,6 +656,15 @@ typedef struct ScrTlsSock {
|
||||
unsigned char *hello; /* the raw ClientHello flight, replayed at DONE */
|
||||
size_t hello_len, hello_cap;
|
||||
ScrSecureCtx *sni_ctx; /* owned; the answer (NULL = default cert) */
|
||||
/* new tls.TLSSocket(socket, { isServer: true }): the program holds this
|
||||
* socket before the handshake, so a failed handshake is its 'error'
|
||||
* (tls.createServer's sockets fail without one) */
|
||||
bool wrapped;
|
||||
unsigned char head[5]; /* the first bytes the peer sent (the error's wording) */
|
||||
size_t head_len;
|
||||
/* the 'secure' event's listeners */
|
||||
ScrNetLs secure_ls;
|
||||
bool settled; /* the socket closed: listeners added later never fire */
|
||||
} ScrTlsSock;
|
||||
|
||||
/* checkServerIdentity for an IP literal: the address must be one of the
|
||||
@@ -703,6 +739,16 @@ static int scr_tls_bio_send(void *ctx, const unsigned char *buf, size_t len) {
|
||||
return MBEDTLS_ERR_NET_SEND_FAILED;
|
||||
}
|
||||
|
||||
/* Keeps the first bytes a wrapped socket's peer sends: a handshake that
|
||||
* fails on them reports what they looked like, as OpenSSL does. */
|
||||
static void scr_tls_note_head(ScrTlsSock *t, const unsigned char *buf, size_t n) {
|
||||
if (!t->wrapped || t->head_len == sizeof t->head) return;
|
||||
size_t take = sizeof t->head - t->head_len;
|
||||
if (take > n) take = n;
|
||||
memcpy(t->head + t->head_len, buf, take);
|
||||
t->head_len += take;
|
||||
}
|
||||
|
||||
static int scr_tls_bio_recv(void *ctx, unsigned char *buf, size_t len) {
|
||||
ScrTlsSock *t = (ScrTlsSock *)ctx;
|
||||
int fd = scr_net_sock_fd(t->sock);
|
||||
@@ -710,8 +756,12 @@ static int scr_tls_bio_recv(void *ctx, unsigned char *buf, size_t len) {
|
||||
/* bytes a consumer peeked and unshifted (the demux path) re-enter the
|
||||
* stream here, ahead of the kernel's */
|
||||
size_t took = scr_net_sock_take_buffered(t->sock, (char *)buf, len);
|
||||
if (took > 0) return (int)took;
|
||||
if (took > 0) {
|
||||
scr_tls_note_head(t, buf, took);
|
||||
return (int)took;
|
||||
}
|
||||
ssize_t n = read(fd, buf, len);
|
||||
if (n > 0) scr_tls_note_head(t, buf, (size_t)n);
|
||||
if (n >= 0) return (int)n; /* 0 = EOF; mbedTLS maps it to CONN_EOF */
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
|
||||
return MBEDTLS_ERR_SSL_WANT_READ;
|
||||
@@ -1026,6 +1076,43 @@ void scr_tls_sni_answer(ScrClosure *self, bool has_err, ScrSecureCtx *ctx /*move
|
||||
scr_net_sock_release(sock);
|
||||
}
|
||||
|
||||
/* A wrapped server socket's failed handshake, reported as Node reports it
|
||||
* on the TLSSocket: a peer that closed first ends the stream, a reset is
|
||||
* ECONNRESET, and bytes that are not a TLS record name what they were
|
||||
* (OpenSSL's reasons without the per-process prefix and source location,
|
||||
* SEMANTICS.md divergence 55). */
|
||||
static void scr_tls_wrapped_failure(ScrTlsSock *t, int r) {
|
||||
if (r == MBEDTLS_ERR_SSL_CONN_EOF) {
|
||||
scr_net_sock_transport_eof(t->sock);
|
||||
return;
|
||||
}
|
||||
if (r == MBEDTLS_ERR_NET_CONN_RESET) {
|
||||
scr_net_sock_transport_error(t->sock, "read ECONNRESET");
|
||||
return;
|
||||
}
|
||||
const unsigned char *h = t->head;
|
||||
bool record = t->head_len >= 3 && h[0] >= 20 && h[0] <= 23 && h[1] == 3;
|
||||
if (t->head_len >= 5 && !record) {
|
||||
static const char *const http[] = { "GET ", "POST ", "HEAD ", "PATCH", "OPTIO",
|
||||
"DELET", "TRACE", "PUT ", NULL };
|
||||
const char *reason = "error:0A00010B:SSL routines:tls_validate_record_header:wrong version number";
|
||||
const char *code = "ERR_SSL_WRONG_VERSION_NUMBER";
|
||||
for (size_t i = 0; http[i] != NULL; i++) {
|
||||
if (memcmp(h, http[i], strlen(http[i])) == 0) {
|
||||
reason = "error:0A00009C:SSL routines:tls_validate_record_header:http request";
|
||||
code = "ERR_SSL_HTTP_REQUEST";
|
||||
}
|
||||
}
|
||||
if (memcmp(h, "CONNE", 5) == 0) {
|
||||
reason = "error:0A00009B:SSL routines:tls_validate_record_header:https proxy request";
|
||||
code = "ERR_SSL_HTTPS_PROXY_REQUEST";
|
||||
}
|
||||
scr_net_sock_transport_error_code(t->sock, reason, code);
|
||||
return;
|
||||
}
|
||||
scr_net_sock_transport_error(t->sock, "server TLS handshake failed");
|
||||
}
|
||||
|
||||
static int scr_tls_handshake(void *tctx) {
|
||||
ScrTlsSock *t = (ScrTlsSock *)tctx;
|
||||
if (t->cli != NULL && t->cli->no_protocols) {
|
||||
@@ -1046,6 +1133,10 @@ static int scr_tls_handshake(void *tctx) {
|
||||
scr_tls_current = NULL;
|
||||
if (r == 0) return 1;
|
||||
if (r == MBEDTLS_ERR_SSL_WANT_READ || r == MBEDTLS_ERR_SSL_WANT_WRITE) return 0;
|
||||
if (t->srv != NULL && t->wrapped) {
|
||||
scr_tls_wrapped_failure(t, r);
|
||||
return -1;
|
||||
}
|
||||
if (t->srv != NULL) {
|
||||
/* Node's server default: 'tlsClientError' with no listener destroys
|
||||
* the socket without an error event — exactly what portless (no
|
||||
@@ -1133,11 +1224,31 @@ static void scr_tls_free(void *tctx) {
|
||||
if (t->srv) scr_tls_srv_release(t->srv);
|
||||
if (t->cli) scr_tls_cli_free(t->cli);
|
||||
scr_net_ls_drop(&t->session_ls);
|
||||
scr_net_ls_drop(&t->secure_ls);
|
||||
free(t->hello);
|
||||
scr_secure_ctx_release(t->sni_ctx);
|
||||
free(t);
|
||||
}
|
||||
|
||||
/* The 'secure' event: after a client's secureConnect listeners, as Node
|
||||
* emits it. */
|
||||
static void scr_tls_after_established(void *tctx) {
|
||||
ScrTlsSock *t = (ScrTlsSock *)tctx;
|
||||
scr_net_fire0_this(&t->secure_ls, t->sock, SCR_DYNH_NET_SOCKET);
|
||||
scr_net_ls_drop(&t->secure_ls);
|
||||
}
|
||||
|
||||
/* The closed socket settles: its 'secure' and 'session' listeners can no
|
||||
* longer fire, and a listener that captures the socket would otherwise
|
||||
* keep it, and this transport, alive (a handshake that fails never fires
|
||||
* 'secure'). */
|
||||
static void scr_tls_settle(void *tctx) {
|
||||
ScrTlsSock *t = (ScrTlsSock *)tctx;
|
||||
t->settled = true;
|
||||
scr_net_ls_drop(&t->secure_ls);
|
||||
scr_net_ls_drop(&t->session_ls);
|
||||
}
|
||||
|
||||
static const ScrNetTransportOps SCR_TLS_OPS = {
|
||||
.xread = scr_tls_xread,
|
||||
.xwrite = scr_tls_xwrite,
|
||||
@@ -1146,6 +1257,8 @@ static const ScrNetTransportOps SCR_TLS_OPS = {
|
||||
.pending = scr_tls_pending,
|
||||
.shutdown_write = scr_tls_shutdown_write,
|
||||
.free = scr_tls_free,
|
||||
.settle = scr_tls_settle,
|
||||
.after_established = scr_tls_after_established,
|
||||
};
|
||||
|
||||
/* ── the server surface ──────────────────────────────────────────────── */
|
||||
@@ -2088,6 +2201,129 @@ ScrNetServer *scr_https_create_server_dyn(const ScrDyn *opts /*borrowed*/,
|
||||
return s;
|
||||
}
|
||||
|
||||
/* ── new tls.TLSSocket(socket, options) ───────────────────────────────── */
|
||||
|
||||
/* The server-mode wrap: a fresh TLS socket takes over raw's connection
|
||||
* (scr_net_sock_adopt) and runs the server handshake over it. It serves
|
||||
* `ctx` (options.secureContext, which the lowering passes apart from the
|
||||
* record; borrowed, nullable) or the record's cert and key. The record
|
||||
* reads at runtime like tls.createServer's: ALPNProtocols, minVersion and
|
||||
* maxVersion apply, and options without a lowering throw the fence. Node
|
||||
* never starts the handshake of a client-mode TLSSocket (no isServer), so
|
||||
* that form throws the fence too. +1, or NULL with the exception pending. */
|
||||
static const char *const SCR_TLS_WRAP_FENCED_OPTIONS[] = {
|
||||
"server", "session", "requestOCSP", "pskCallback", "enableTrace", NULL,
|
||||
};
|
||||
|
||||
/* The socket-level options of a wrap that serves a secureContext, which
|
||||
* the server-options walk would otherwise check: requestCert (client
|
||||
* certificates are not modeled), SNICallback and ALPNCallback fence.
|
||||
* False with the exception pending. */
|
||||
static bool scr_tls_wrap_socket_opts_check(const ScrDyn *record, const char *api) {
|
||||
for (size_t i = 0; record != NULL && i < record->v.obj.len; i++) {
|
||||
const ScrDynEntry *e = &record->v.obj.entries[i];
|
||||
if (e->value->kind == SCR_DYN_UNDEF) continue;
|
||||
if (strcmp(e->key, "requestCert") == 0) {
|
||||
if (!scr_tls_dyn_truthy(e->value)) continue;
|
||||
scr_tls_opt_fence(api, "requestCert",
|
||||
"client-certificate handshakes are not modeled (SEMANTICS.md divergence 55) — requestCert stays at Node's false default");
|
||||
return false;
|
||||
}
|
||||
if (strcmp(e->key, "SNICallback") == 0 || strcmp(e->key, "ALPNCallback") == 0) {
|
||||
scr_tls_opt_fence(api, e->key,
|
||||
"isServer, cert, key, secureContext, ALPNProtocols, minVersion and maxVersion are the implemented members");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ScrNetSocket *scr_tls_wrap_socket(ScrNetSocket *raw, const ScrDyn *opts /*borrowed*/,
|
||||
ScrSecureCtx *ctx /*borrowed, nullable*/) {
|
||||
static const char api[] = "new tls.TLSSocket";
|
||||
bool is_server = false;
|
||||
const ScrDyn *record = opts != NULL && opts->kind == SCR_DYN_OBJ ? opts : NULL;
|
||||
for (size_t i = 0; record != NULL && i < record->v.obj.len; i++) {
|
||||
const ScrDynEntry *e = &record->v.obj.entries[i];
|
||||
if (e->value->kind == SCR_DYN_UNDEF) continue;
|
||||
if (strcmp(e->key, "isServer") == 0) {
|
||||
is_server = scr_tls_dyn_truthy(e->value);
|
||||
continue;
|
||||
}
|
||||
if (strcmp(e->key, "secureContext") == 0) {
|
||||
scr_tls_opt_fence(api, "secureContext",
|
||||
"secureContext lowers as a member of an options object literal");
|
||||
return NULL;
|
||||
}
|
||||
for (size_t k = 0; SCR_TLS_WRAP_FENCED_OPTIONS[k] != NULL; k++) {
|
||||
if (strcmp(e->key, SCR_TLS_WRAP_FENCED_OPTIONS[k]) == 0) {
|
||||
scr_tls_opt_fence(api, e->key,
|
||||
"isServer, cert, key, secureContext, ALPNProtocols, minVersion and maxVersion are the implemented members");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!is_server) {
|
||||
scr_tls_opt_fence(api, "isServer",
|
||||
"Node starts no handshake for a client-mode TLSSocket; tls.connect({ socket }) runs the client handshake over an open socket");
|
||||
return NULL;
|
||||
}
|
||||
/* Node serves a secureContext as it is: the record's cert, key and
|
||||
* other context options are not read then. The socket's own options
|
||||
* still apply, so the ones without a lowering fence either way:
|
||||
* requestCert asks for a client certificate this runtime cannot
|
||||
* verify. */
|
||||
int min_version = 12, max_version = 13;
|
||||
ScrBytes *cert = NULL, *key = NULL;
|
||||
if (ctx == NULL) {
|
||||
if (!scr_tls_versions_dyn(record, &min_version, &max_version)) return NULL;
|
||||
if (!scr_tls_srv_opts_walk_ex(record, api, &cert, &key, true)) return NULL;
|
||||
} else if (!scr_tls_wrap_socket_opts_check(record, api)) {
|
||||
return NULL;
|
||||
}
|
||||
char **alpn = NULL;
|
||||
const ScrDyn *protocols = record != NULL ? scr_dyn_obj_get(record, "ALPNProtocols", 13) : NULL;
|
||||
if (protocols != NULL && protocols->kind != SCR_DYN_UNDEF && !scr_tls_alpn_dyn(protocols, &alpn)) {
|
||||
scr_bytes_release(cert);
|
||||
scr_bytes_release(key);
|
||||
return NULL;
|
||||
}
|
||||
ScrTlsSrv *srv = ctx != NULL ? scr_tls_srv_from_ctx(ctx)
|
||||
: scr_tls_srv_new((const char *)cert->data, cert->len,
|
||||
(const char *)key->data, key->len, false);
|
||||
scr_bytes_release(cert);
|
||||
scr_bytes_release(key);
|
||||
srv->alpn = alpn; /* moves */
|
||||
if (alpn != NULL) mbedtls_ssl_conf_alpn_protocols(&srv->conf, (const char **)srv->alpn);
|
||||
if (!scr_tls_conf_versions(&srv->conf, min_version, max_version)) {
|
||||
scr_tls_srv_release(srv);
|
||||
scr_tls_opt_fence(api, "maxVersion",
|
||||
"a version range with neither TLSv1.2 nor TLSv1.3 has no lowering");
|
||||
return NULL;
|
||||
}
|
||||
srv->max_tls12 = max_version == 12;
|
||||
ScrNetSocket *t = scr_net_sock_adopt(raw);
|
||||
if (t == NULL) {
|
||||
scr_tls_srv_release(srv);
|
||||
static const char msg[] =
|
||||
"new tls.TLSSocket over a socket that is not connected, or is already wrapped, is not supported yet";
|
||||
scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
|
||||
return NULL;
|
||||
}
|
||||
ScrTlsSock *ts = calloc(1, sizeof *ts);
|
||||
if (!ts) scr_tls_oom();
|
||||
mbedtls_ssl_init(&ts->ssl);
|
||||
if (mbedtls_ssl_setup(&ts->ssl, &srv->conf) != 0) scr_tls_oom();
|
||||
ts->sock = t;
|
||||
ts->srv = srv; /* moves */
|
||||
ts->wrapped = true;
|
||||
if (srv->max_tls12) ts->sni_state = SCR_SNI_COLLECT; /* pre-parse the hello */
|
||||
mbedtls_ssl_set_bio(&ts->ssl, ts, scr_tls_bio_send, scr_tls_bio_recv, NULL);
|
||||
scr_net_sock_set_transport(t, &SCR_TLS_OPS, ts);
|
||||
scr_net_sock_adopt_start(t);
|
||||
return t;
|
||||
}
|
||||
|
||||
/* ── tls.connect (the TLS client socket) ──────────────────────────────── */
|
||||
|
||||
/* Node's inspect of a Buffer inside ERR_INVALID_ARG_VALUE's message:
|
||||
@@ -2239,7 +2475,7 @@ static const char *const SCR_TLS_CONNECT_FENCED_OPTIONS[] = {
|
||||
"localAddress", "localPort", "lookup", "minDHSize", "noDelay", "onread", "passphrase", "path", "pfx",
|
||||
"privateKeyEngine", "privateKeyIdentifier", "pskCallback", "readable",
|
||||
"secureContext", "secureOptions", "secureProtocol", "session",
|
||||
"sessionIdContext", "sigalgs", "signal", "socket", "ticketKeys",
|
||||
"sessionIdContext", "sigalgs", "signal", "ticketKeys",
|
||||
"writable", NULL,
|
||||
};
|
||||
|
||||
@@ -2255,6 +2491,7 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable
|
||||
const ScrDyn *alpn_value = NULL;
|
||||
const ScrDyn *timeout_value = NULL;
|
||||
int min_version = 12, max_version = 13;
|
||||
ScrNetSocket *wrap = NULL; /* options.socket: the open socket to take over */
|
||||
bool ok = true;
|
||||
if (opts != NULL && opts->kind != SCR_DYN_UNDEF && opts->kind != SCR_DYN_NULL) {
|
||||
if (opts->kind != SCR_DYN_OBJ) {
|
||||
@@ -2322,6 +2559,15 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable
|
||||
timeout_value = scr_tls_dyn_truthy(v) ? v : NULL;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(e->key, "socket") == 0) {
|
||||
if (v->kind == SCR_DYN_HANDLE && v->v.handle.tag == SCR_DYNH_NET_SOCKET) {
|
||||
wrap = (ScrNetSocket *)v->v.handle.ptr;
|
||||
} else {
|
||||
scr_tls_opt_fence("tls.connect", "socket", "a net.Socket is the implemented socket value");
|
||||
ok = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (strcmp(e->key, "servername") == 0) {
|
||||
if (v->kind == SCR_DYN_STR) servername = v->v.str;
|
||||
else {
|
||||
@@ -2333,7 +2579,7 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable
|
||||
for (size_t k = 0; SCR_TLS_CONNECT_FENCED_OPTIONS[k] != NULL; k++) {
|
||||
if (strcmp(e->key, SCR_TLS_CONNECT_FENCED_OPTIONS[k]) == 0) {
|
||||
scr_tls_opt_fence("tls.connect", e->key,
|
||||
"port/host/rejectUnauthorized/ca/servername/ALPNProtocols/minVersion/maxVersion/timeout are the implemented members of a runtime options record");
|
||||
"port/host/socket/rejectUnauthorized/ca/servername/ALPNProtocols/minVersion/maxVersion/timeout are the implemented members of a runtime options record");
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
@@ -2361,7 +2607,7 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable
|
||||
timeout_ms = timeout_value->v.num;
|
||||
}
|
||||
}
|
||||
if (ok && port < 0) {
|
||||
if (ok && port < 0 && wrap == NULL) {
|
||||
scr_dyn_arg_type_fail("options.port", "of type number", scr_dyn_undefined());
|
||||
ok = false;
|
||||
}
|
||||
@@ -2377,6 +2623,9 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable
|
||||
* name otherwise (Node's servername default); "" sends no SNI. */
|
||||
ScrStr *verify_host = dial_host;
|
||||
ScrStr *localhost_fallback = NULL;
|
||||
/* over options.socket, the host that socket dialed (Node's socket._host) */
|
||||
ScrStr *socket_host = verify_host == NULL && wrap != NULL ? scr_net_sock_dial_name(wrap) : NULL;
|
||||
if (socket_host != NULL) verify_host = socket_host;
|
||||
if (verify_host == NULL) {
|
||||
localhost_fallback = scr_str_new("localhost", 9);
|
||||
verify_host = localhost_fallback;
|
||||
@@ -2384,9 +2633,25 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable
|
||||
/* Resolve for the dial only (the http client's rule): verify_host above
|
||||
* already holds the NAME, which is what SNI and certificate verification
|
||||
* must see. A null dial_host stays null — scr_net_connect's own arm. */
|
||||
ScrStr *dial_addr = dial_host != NULL ? scr_net_blocking_lookup(dial_host) : NULL;
|
||||
ScrNetSocket *s = scr_net_connect(port, dial_addr, cb); /* cb fires post-handshake — secureConnect */
|
||||
if (dial_addr) scr_str_release(dial_addr);
|
||||
ScrNetSocket *s;
|
||||
if (wrap != NULL) {
|
||||
s = scr_net_sock_adopt(wrap);
|
||||
if (s == NULL) {
|
||||
scr_tls_alpn_free(alpn);
|
||||
scr_bytes_release(ca);
|
||||
scr_closure_release(cb);
|
||||
scr_str_release(localhost_fallback);
|
||||
scr_str_release(socket_host);
|
||||
static const char msg[] =
|
||||
"tls.connect over a socket that is not connected, or is already wrapped, is not supported yet";
|
||||
scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
ScrStr *dial_addr = dial_host != NULL ? scr_net_blocking_lookup(dial_host) : NULL;
|
||||
s = scr_net_connect(port, dial_addr, cb); /* cb fires post-handshake — secureConnect */
|
||||
if (dial_addr) scr_str_release(dial_addr);
|
||||
}
|
||||
ScrTlsCli *cli = scr_tls_cli_new2(verify_host, reject,
|
||||
ca != NULL ? (const char *)ca->data : NULL,
|
||||
ca != NULL ? ca->len : 0, !reject);
|
||||
@@ -2397,8 +2662,13 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable
|
||||
}
|
||||
cli->no_protocols = !scr_tls_conf_versions(&cli->conf, min_version, max_version);
|
||||
scr_tls_cli_wrap(s, cli);
|
||||
if (wrap != NULL) {
|
||||
if (cb != NULL) scr_net_sock_on_established(s, cb, true); /* secureConnect */
|
||||
scr_net_sock_adopt_start(s);
|
||||
}
|
||||
if (timeout_ms > 0) scr_net_sock_set_timeout(s, timeout_ms);
|
||||
scr_str_release(localhost_fallback);
|
||||
scr_str_release(socket_host);
|
||||
scr_bytes_release(ca);
|
||||
return s;
|
||||
}
|
||||
@@ -2610,12 +2880,39 @@ ScrDyn *scr_tls_sock_alpn_protocol(ScrNetSocket *s) {
|
||||
return d;
|
||||
}
|
||||
|
||||
/* secureConnect: a client socket's 'connect' list fires at establishment
|
||||
* (scr_net.c's transport pump), Node's secureConnect timing. Once TCP is up
|
||||
* (a socket tls.connect({ socket }) took over is up from the start) the
|
||||
* listener waits for the handshake instead. Server-side sockets never emit
|
||||
* it. */
|
||||
static void scr_tls_add_secure_connect(ScrNetSocket *s, ScrTlsSock *t, ScrClosure *cb, bool once) {
|
||||
if (t->srv != NULL) {
|
||||
scr_closure_release(cb);
|
||||
return;
|
||||
}
|
||||
if (scr_net_sock_connecting(s)) scr_net_sock_on_connect(s, cb, once);
|
||||
else scr_net_sock_on_established(s, cb, once);
|
||||
}
|
||||
|
||||
void scr_tls_sock_on_secure_connect(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once) {
|
||||
if (scr_tls_sock_of(s) == NULL) {
|
||||
ScrTlsSock *t = scr_tls_sock_of(s);
|
||||
if (t == NULL) {
|
||||
scr_closure_release(cb); /* a plain socket never fires it, like Node */
|
||||
return;
|
||||
}
|
||||
scr_net_sock_on_connect(s, cb, once); /* fires at establishment — secureConnect timing */
|
||||
scr_tls_add_secure_connect(s, t, cb, once);
|
||||
}
|
||||
|
||||
/* The 'secure' event: the handshake completed (after secureConnect on a
|
||||
* client). A plain socket never emits it, and a registration after the
|
||||
* handshake comes too late, as in Node. */
|
||||
void scr_tls_sock_on_secure(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once) {
|
||||
ScrTlsSock *t = scr_tls_sock_of(s);
|
||||
if (t == NULL || t->established || t->settled) {
|
||||
scr_closure_release(cb);
|
||||
return;
|
||||
}
|
||||
scr_net_ls_add(&t->secure_ls, cb, NULL, once);
|
||||
}
|
||||
|
||||
void scr_tls_sock_on_session(ScrNetSocket *s, ScrClosure *cb /*moves*/, void *fn, bool once) {
|
||||
@@ -2624,6 +2921,10 @@ void scr_tls_sock_on_session(ScrNetSocket *s, ScrClosure *cb /*moves*/, void *fn
|
||||
scr_closure_release(cb); /* a plain socket never fires it, like Node */
|
||||
return;
|
||||
}
|
||||
if (t->settled) {
|
||||
scr_closure_release(cb);
|
||||
return;
|
||||
}
|
||||
scr_net_ls_add(&t->session_ls, cb, fn, once);
|
||||
if (t->established) scr_tls_fire_session(t); /* the ticket already arrived */
|
||||
}
|
||||
@@ -2660,9 +2961,11 @@ static bool scr_tls_dynh_on(ScrNetSocket *s, const char *event, const ScrDyn *cb
|
||||
ScrTlsSock *t = scr_tls_sock_of(s);
|
||||
if (t == NULL) return false;
|
||||
if (strcmp(event, "secureConnect") == 0) {
|
||||
/* a TLS socket's 'connect' list fires at establishment — exactly
|
||||
* Node's secureConnect timing (scr_net.c's transport pump) */
|
||||
scr_net_sock_on_connect(s, scr_dyn_listener_closure0(cb), once);
|
||||
scr_tls_add_secure_connect(s, t, scr_dyn_listener_closure0(cb), once);
|
||||
return true;
|
||||
}
|
||||
if (strcmp(event, "secure") == 0) {
|
||||
scr_tls_sock_on_secure(s, scr_dyn_listener_closure0(cb), once);
|
||||
return true;
|
||||
}
|
||||
if (strcmp(event, "session") == 0) {
|
||||
|
||||
@@ -62,6 +62,10 @@ ScrDyn *scr_tls_sock_get_cipher(ScrNetSocket *s); /* +1: object, undefined or
|
||||
ScrDyn *scr_tls_sock_alpn_protocol(ScrNetSocket *s); /* +1: string, false or null */
|
||||
void scr_tls_sock_on_secure_connect(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once);
|
||||
void scr_tls_sock_on_session(ScrNetSocket *s, ScrClosure *cb /*moves*/, void *fn, bool once);
|
||||
/* The 'secure' event: fires when the handshake completes (after the
|
||||
* secureConnect listeners on a client); released unread on a plain socket
|
||||
* or after the handshake. */
|
||||
void scr_tls_sock_on_secure(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once);
|
||||
|
||||
/* ── h2 over TLS (scr_tls.c ↔ scr_http2.c): the ALPN=h2 server wrap and
|
||||
* the https-authority client wrap. scr_http2.c calls both directly —
|
||||
@@ -93,6 +97,11 @@ ScrSecureCtx *scr_tls_create_secure_context(const char *cert, size_t cert_len, c
|
||||
* validations first, then the pem walk (+1, or NULL with the exception
|
||||
* pending). Borrowed. */
|
||||
ScrSecureCtx *scr_tls_create_secure_context_dyn(const ScrDyn *opts);
|
||||
/* new tls.TLSSocket(socket, options) in server mode: a fresh TLS socket
|
||||
* takes over socket's connection and runs the server handshake, serving
|
||||
* ctx (options.secureContext; borrowed, nullable) or the options record's
|
||||
* cert and key. +1, or NULL with the exception pending. */
|
||||
ScrNetSocket *scr_tls_wrap_socket(ScrNetSocket *raw, const ScrDyn *opts /*borrowed*/, ScrSecureCtx *ctx /*borrowed, nullable*/);
|
||||
/* tls.getCACertificates(type): validateString + the documented name set,
|
||||
* then the compiler-rendered fence — always leaves an exception pending. */
|
||||
void scr_tls_ca_certs_chk(const ScrDyn *type, const ScrStr *fence);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
* compiler links the unit only into programs that call these methods
|
||||
* (the webcrypto feature). */
|
||||
#include "scr_runtime.h"
|
||||
#include "scr_webcrypto.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
@@ -58,13 +59,12 @@ static const char *const scr_wc_key_names[] = {
|
||||
"MLKEM768-X25519", "MLKEM1024-P384", "RSA-OAEP", "RSA-PSS", "RSASSA-PKCS1-v1_5",
|
||||
"X25519", "X448",
|
||||
};
|
||||
static const char *const scr_wc_digest_names[] = {
|
||||
const char *const scr_wc_digest_names[] = {
|
||||
"SHA-1", "SHA-256", "SHA-384", "SHA-512", "SHA3-256", "SHA3-384",
|
||||
"SHA3-512", "cSHAKE128", "cSHAKE256", "KT128", "KT256", "TurboSHAKE128",
|
||||
"TurboSHAKE256",
|
||||
};
|
||||
|
||||
#define SCR_WC_COUNT(names) (sizeof(names) / sizeof((names)[0]))
|
||||
|
||||
/* The implemented hashes: WebCrypto name and node:crypto digest name. */
|
||||
static const struct {
|
||||
@@ -90,7 +90,7 @@ static bool scr_wc_ascii_ieq(const ScrStr *s, const char *name) {
|
||||
}
|
||||
|
||||
/* The canonical registered spelling of `name`, or NULL. */
|
||||
static const char *scr_wc_lookup(const char *const *names, size_t count, const ScrStr *name) {
|
||||
const char *scr_wc_lookup(const char *const *names, size_t count, const ScrStr *name) {
|
||||
for (size_t i = 0; i < count; i++)
|
||||
if (scr_wc_ascii_ieq(name, names[i])) return names[i];
|
||||
return NULL;
|
||||
@@ -104,11 +104,17 @@ static const char *scr_wc_native_hash(const char *name) {
|
||||
|
||||
/* ── errors ────────────────────────────────────────────────────────── */
|
||||
|
||||
static const char *const scr_wc_ordinals[] = {"1st", "2nd", "3rd", "4th", "5th", "6th", "7th"};
|
||||
const char *const scr_wc_ordinals[] = {"1st", "2nd", "3rd", "4th", "5th"};
|
||||
|
||||
static void scr_wc_type_error(const char *code, const char *fmt, ...)
|
||||
__attribute__((format(printf, 2, 3)));
|
||||
static void scr_wc_type_error(const char *code, const char *fmt, ...) {
|
||||
/* An argument's ordinal: unwrapKey's seven arguments run past the shared
|
||||
* table. */
|
||||
static const char *scr_wc_ordinal(size_t index) {
|
||||
static const char *const later[] = {"6th", "7th"};
|
||||
size_t shared = SCR_WC_COUNT(scr_wc_ordinals);
|
||||
return index < shared ? scr_wc_ordinals[index] : later[index - shared];
|
||||
}
|
||||
|
||||
void scr_wc_type_error(const char *code, const char *fmt, ...) {
|
||||
char message[512];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
@@ -120,7 +126,7 @@ static void scr_wc_type_error(const char *code, const char *fmt, ...) {
|
||||
}
|
||||
|
||||
/* An operation or algorithm Node supports that this slice does not. */
|
||||
static void scr_wc_unsupported(const char *method, const char *what) {
|
||||
void scr_wc_unsupported(const char *method, const char *what) {
|
||||
char message[256];
|
||||
int n = snprintf(message, sizeof message, "crypto.subtle.%s with %s is not supported yet", method,
|
||||
what);
|
||||
@@ -128,7 +134,7 @@ static void scr_wc_unsupported(const char *method, const char *what) {
|
||||
scr_throw_error_msg_code(SCR_ERR_ERROR, message, (size_t)n, "SC2020");
|
||||
}
|
||||
|
||||
static void scr_wc_unsupported_algorithm(const char *method, const char *name) {
|
||||
void scr_wc_unsupported_algorithm(const char *method, const char *name) {
|
||||
char what[96];
|
||||
snprintf(what, sizeof what, "the %s algorithm", name);
|
||||
scr_wc_unsupported(method, what);
|
||||
@@ -138,7 +144,7 @@ static void scr_wc_unsupported_algorithm(const char *method, const char *name) {
|
||||
* Each answers NULL/false with the TypeError pending. `prefix` is
|
||||
* "Failed to execute '<method>' on 'SubtleCrypto'". */
|
||||
|
||||
static bool scr_wc_is_object(const ScrDyn *v) {
|
||||
bool scr_wc_is_object(const ScrDyn *v) {
|
||||
switch (v->kind) {
|
||||
case SCR_DYN_UNDEF:
|
||||
case SCR_DYN_NULL:
|
||||
@@ -154,14 +160,14 @@ static bool scr_wc_is_object(const ScrDyn *v) {
|
||||
}
|
||||
|
||||
/* A typed reference capsule as its plain view; other values retained. */
|
||||
static ScrDyn *scr_wc_plain(ScrDyn *v) {
|
||||
ScrDyn *scr_wc_plain(ScrDyn *v) {
|
||||
if (v->kind == SCR_DYN_TYPED_REF) return scr_dyn_typed_ref_materialize(v);
|
||||
return scr_dyn_retain(v);
|
||||
}
|
||||
|
||||
/* BufferSource: the bytes of a TypedArray, DataView, Buffer or
|
||||
* ArrayBuffer as a u8 view (+1). */
|
||||
static ScrBytes *scr_wc_buffer_source(const char *prefix, size_t index, ScrDyn *value) {
|
||||
ScrBytes *scr_wc_buffer_source(const char *prefix, size_t index, ScrDyn *value) {
|
||||
ScrDyn *plain = scr_wc_plain(value);
|
||||
if (!plain) return NULL;
|
||||
ScrBytes *view = NULL;
|
||||
@@ -171,21 +177,21 @@ static ScrBytes *scr_wc_buffer_source(const char *prefix, size_t index, ScrDyn *
|
||||
scr_wc_type_error("ERR_INVALID_ARG_TYPE",
|
||||
"%s: %s argument is not instance of ArrayBuffer, Buffer, TypedArray, or "
|
||||
"DataView.",
|
||||
prefix, scr_wc_ordinals[index]);
|
||||
prefix, scr_wc_ordinal(index));
|
||||
}
|
||||
scr_dyn_release(plain);
|
||||
return view;
|
||||
}
|
||||
|
||||
/* DOMString conversion (+1). */
|
||||
static ScrStr *scr_wc_dom_string(const ScrDyn *value) { return scr_dyn_string_coerce_js(value); }
|
||||
ScrStr *scr_wc_dom_string(const ScrDyn *value) { return scr_dyn_string_coerce_js(value); }
|
||||
|
||||
static const char *const scr_wc_key_formats[] = {
|
||||
"raw", "raw-public", "raw-seed", "raw-secret", "raw-private", "pkcs8", "spki", "jwk",
|
||||
};
|
||||
|
||||
/* KeyFormat: the canonical enum spelling, or NULL. */
|
||||
static const char *scr_wc_key_format(const char *prefix, ScrDyn *value) {
|
||||
const char *scr_wc_key_format(const char *prefix, ScrDyn *value) {
|
||||
ScrStr *s = scr_wc_dom_string(value);
|
||||
if (!s) return NULL;
|
||||
for (size_t i = 0; i < SCR_WC_COUNT(scr_wc_key_formats); i++) {
|
||||
@@ -203,22 +209,11 @@ static const char *scr_wc_key_format(const char *prefix, ScrDyn *value) {
|
||||
}
|
||||
|
||||
/* Node's canonical usage order; the bit is the index. */
|
||||
static const char *const scr_wc_usages[] = {
|
||||
const char *const scr_wc_usages[] = {
|
||||
"encrypt", "decrypt", "sign", "verify",
|
||||
"deriveKey", "deriveBits", "wrapKey", "unwrapKey",
|
||||
"encapsulateKey", "encapsulateBits", "decapsulateKey", "decapsulateBits",
|
||||
};
|
||||
enum {
|
||||
SCR_WC_USAGE_ENCRYPT = 1u << 0,
|
||||
SCR_WC_USAGE_DECRYPT = 1u << 1,
|
||||
SCR_WC_USAGE_SIGN = 1u << 2,
|
||||
SCR_WC_USAGE_VERIFY = 1u << 3,
|
||||
SCR_WC_USAGE_DERIVE_KEY = 1u << 4,
|
||||
SCR_WC_USAGE_DERIVE_BITS = 1u << 5,
|
||||
SCR_WC_USAGE_WRAP_KEY = 1u << 6,
|
||||
SCR_WC_USAGE_UNWRAP_KEY = 1u << 7,
|
||||
};
|
||||
|
||||
/* The usage index of a KeyUsage string, or -1. */
|
||||
static int scr_wc_usage_index(const ScrStr *s) {
|
||||
for (size_t k = 0; k < SCR_WC_COUNT(scr_wc_usages); k++)
|
||||
@@ -228,12 +223,12 @@ static int scr_wc_usage_index(const ScrStr *s) {
|
||||
}
|
||||
|
||||
/* sequence<KeyUsage> over an array: the usage mask (-1 on failure). */
|
||||
static long scr_wc_key_usages(const char *prefix, size_t index, ScrDyn *value) {
|
||||
long scr_wc_key_usages(const char *prefix, size_t index, ScrDyn *value) {
|
||||
ScrDyn *plain = scr_wc_plain(value);
|
||||
if (!plain) return -1;
|
||||
if (plain->kind != SCR_DYN_ARR) {
|
||||
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: %s argument cannot be converted to sequence.",
|
||||
prefix, scr_wc_ordinals[index]);
|
||||
prefix, scr_wc_ordinal(index));
|
||||
scr_dyn_release(plain);
|
||||
return -1;
|
||||
}
|
||||
@@ -251,7 +246,7 @@ static long scr_wc_key_usages(const char *prefix, size_t index, ScrDyn *value) {
|
||||
if (k < 0) {
|
||||
scr_wc_type_error("ERR_INVALID_ARG_VALUE",
|
||||
"%s: %s argument[%.0f] '%.*s' is not a valid enum value of type KeyUsage.",
|
||||
prefix, scr_wc_ordinals[index], i, (int)(s->len > 200 ? 200 : s->len),
|
||||
prefix, scr_wc_ordinal(index), i, (int)(s->len > 200 ? 200 : s->len),
|
||||
s->data);
|
||||
scr_str_release(s);
|
||||
scr_dyn_release(plain);
|
||||
@@ -274,9 +269,9 @@ typedef struct {
|
||||
double length;
|
||||
} ScrWcAlgorithm;
|
||||
|
||||
static const char scr_wc_normalize_prefix[] = "Failed to normalize algorithm";
|
||||
const char scr_wc_normalize_prefix[] = "Failed to normalize algorithm";
|
||||
|
||||
static void scr_wc_missing_member(const char *dict, const char *member) {
|
||||
void scr_wc_missing_member(const char *dict, const char *member) {
|
||||
scr_wc_type_error("ERR_MISSING_OPTION",
|
||||
"%s: passed algorithm cannot be converted to '%s' because '%s' is required in "
|
||||
"'%s'.",
|
||||
@@ -284,7 +279,7 @@ static void scr_wc_missing_member(const char *dict, const char *member) {
|
||||
}
|
||||
|
||||
/* Reads `key` off an algorithm dictionary (+1; undefined when absent). */
|
||||
static ScrDyn *scr_wc_member(const ScrDyn *dict, const char *key) {
|
||||
ScrDyn *scr_wc_member(const ScrDyn *dict, const char *key) {
|
||||
if (dict->kind != SCR_DYN_OBJ) return scr_dyn_retain(scr_dyn_undefined());
|
||||
ScrDyn *v = scr_dyn_obj_read(dict, key, strlen(key));
|
||||
return v ? v : scr_dyn_retain(scr_dyn_undefined());
|
||||
@@ -315,7 +310,7 @@ static bool scr_wc_enforce_range(const char *prefix, const char *context, const
|
||||
/* The name an AlgorithmIdentifier carries (+1), with the Algorithm
|
||||
* dictionary's missing-name TypeError. `dict` names the dictionary the
|
||||
* value converts to. */
|
||||
static ScrStr *scr_wc_algorithm_name(ScrDyn *identifier, const char *dict) {
|
||||
ScrStr *scr_wc_algorithm_name(ScrDyn *identifier, const char *dict) {
|
||||
if (!scr_wc_is_object(identifier)) return scr_wc_dom_string(identifier);
|
||||
ScrDyn *plain = scr_wc_plain(identifier);
|
||||
if (!plain) return NULL;
|
||||
@@ -330,7 +325,7 @@ static ScrStr *scr_wc_algorithm_name(ScrDyn *identifier, const char *dict) {
|
||||
|
||||
/* The digest a HashAlgorithmIdentifier names: canonical name, or NULL
|
||||
* with NotSupportedError / the SC2020 refusal pending. */
|
||||
static const char *scr_wc_digest_algorithm(ScrDyn *identifier, const char *method) {
|
||||
const char *scr_wc_digest_algorithm(ScrDyn *identifier, const char *method) {
|
||||
ScrStr *name = scr_wc_algorithm_name(identifier, "Algorithm");
|
||||
if (!name) return NULL;
|
||||
const char *canonical = scr_wc_lookup(scr_wc_digest_names, SCR_WC_COUNT(scr_wc_digest_names), name);
|
||||
@@ -528,10 +523,10 @@ static ScrCryptoKey *scr_crypto_key_of(const ScrDyn *value) {
|
||||
|
||||
bool scr_webcrypto_is_crypto_key(const ScrDyn *value) {
|
||||
if (value->kind == SCR_DYN_TYPED_REF) return false;
|
||||
return scr_crypto_key_of(value) != NULL;
|
||||
return scr_crypto_key_of(value) != NULL || (scr_wc_asymmetric && scr_wc_asymmetric->is_key(value));
|
||||
}
|
||||
|
||||
static ScrDyn *scr_wc_cstr(const char *s) {
|
||||
ScrDyn *scr_wc_cstr(const char *s) {
|
||||
ScrStr *str = scr_str_new(s, strlen(s));
|
||||
ScrDyn *out = scr_dyn_new_str(str);
|
||||
scr_str_release(str);
|
||||
@@ -871,7 +866,55 @@ static ScrBytes *scr_wc_jwk_secret(const ScrWcJwk *jwk, bool extractable, long u
|
||||
* Each takes its argument vector as one dyn array (borrowed) and answers
|
||||
* a +1 promise. */
|
||||
|
||||
static ScrPromise *scr_wc_rejected(void) {
|
||||
const ScrWcAsymmetric *scr_wc_asymmetric = NULL;
|
||||
|
||||
static const char *const scr_wc_asymmetric_names[] = {"ECDSA", "Ed25519", "RSA-PSS", "RSASSA-PKCS1-v1_5"};
|
||||
|
||||
/* Whether the AlgorithmIdentifier at args[index] names ECDSA,
|
||||
* RSASSA-PKCS1-v1_5, RSA-PSS or Ed25519, read without side effects or errors: a
|
||||
* string, or the name member of an object (a statically typed one through
|
||||
* its plain view). A value that does not read cleanly takes the HMAC path,
|
||||
* which reports its errors. */
|
||||
static const char *scr_wc_asymmetric_name(const ScrDyn *args, size_t index) {
|
||||
if ((size_t)scr_dyn_arr_len(args) <= index) return NULL;
|
||||
ScrDyn *identifier = scr_dyn_arr_at(args, (double)index);
|
||||
ScrDyn *plain = scr_wc_plain(identifier);
|
||||
if (!plain) scr_exc_clear();
|
||||
const char *found = NULL;
|
||||
if (plain) {
|
||||
const ScrDyn *name = plain;
|
||||
if (plain->kind == SCR_DYN_OBJ) name = scr_dyn_obj_get(plain, "name", 4);
|
||||
if (name && name->kind == SCR_DYN_STR)
|
||||
found = scr_wc_lookup(scr_wc_asymmetric_names, SCR_WC_COUNT(scr_wc_asymmetric_names),
|
||||
name->v.str);
|
||||
scr_dyn_release(plain);
|
||||
}
|
||||
scr_dyn_release(identifier);
|
||||
return found;
|
||||
}
|
||||
|
||||
/* Whether `value` is an ECDSA, Ed25519 or RSA CryptoKey, which
|
||||
* scr_webcrypto_pki.c holds; wrapKey and unwrapKey wrap secret keys only. */
|
||||
static bool scr_wc_is_asymmetric_key(const ScrDyn *value) {
|
||||
return scr_wc_asymmetric && scr_wc_asymmetric->is_key(value);
|
||||
}
|
||||
|
||||
static bool scr_wc_is_asymmetric_algorithm(const char *name) {
|
||||
for (size_t i = 0; i < SCR_WC_COUNT(scr_wc_asymmetric_names); i++)
|
||||
if (strcmp(name, scr_wc_asymmetric_names[i]) == 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The asymmetric algorithms run in scr_webcrypto_pki.c, which the compiler
|
||||
* links when a string literal in the program names one of them. */
|
||||
static ScrPromise *scr_wc_asymmetric_missing(const char *method, const char *name) {
|
||||
char what[160];
|
||||
snprintf(what, sizeof what, "the %s algorithm named only by a computed string", name);
|
||||
scr_wc_unsupported(method, what);
|
||||
return scr_wc_rejected();
|
||||
}
|
||||
|
||||
ScrPromise *scr_wc_rejected(void) {
|
||||
ScrPromise *p = scr_promise_new();
|
||||
scr_promise_reject_pending(p);
|
||||
return p;
|
||||
@@ -892,7 +935,7 @@ static ScrPromise *scr_wc_deferred(ScrDyn *value) {
|
||||
|
||||
/* The argument count check every method starts with (after the receiver
|
||||
* check the compiler's call shape already guarantees). */
|
||||
static bool scr_wc_required(const char *prefix, const ScrDyn *args, size_t required) {
|
||||
bool scr_wc_required(const char *prefix, const ScrDyn *args, size_t required) {
|
||||
size_t argc = (size_t)scr_dyn_arr_len(args);
|
||||
if (argc >= required) return true;
|
||||
scr_wc_type_error("ERR_MISSING_ARGS", "%s: %zu arguments required, but only %zu present.", prefix,
|
||||
@@ -900,15 +943,12 @@ static bool scr_wc_required(const char *prefix, const ScrDyn *args, size_t requi
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
ScrDyn *v[5];
|
||||
} ScrWcArgs;
|
||||
|
||||
static void scr_wc_args_take(ScrWcArgs *a, const ScrDyn *args, size_t n) {
|
||||
void scr_wc_args_take(ScrWcArgs *a, const ScrDyn *args, size_t n) {
|
||||
for (size_t i = 0; i < n; i++) a->v[i] = scr_dyn_arr_at(args, (double)i);
|
||||
}
|
||||
|
||||
static void scr_wc_args_drop(ScrWcArgs *a, size_t n) {
|
||||
void scr_wc_args_drop(ScrWcArgs *a, size_t n) {
|
||||
for (size_t i = 0; i < n; i++) scr_dyn_release(a->v[i]);
|
||||
}
|
||||
|
||||
@@ -1098,6 +1138,11 @@ done:
|
||||
}
|
||||
|
||||
ScrPromise *scr_webcrypto_import_key(ScrDyn *args) {
|
||||
const char *asymmetric = scr_wc_asymmetric_name(args, 2);
|
||||
if (asymmetric) {
|
||||
return scr_wc_asymmetric ? scr_wc_asymmetric->import_key(args)
|
||||
: scr_wc_asymmetric_missing("importKey", asymmetric);
|
||||
}
|
||||
ScrDyn *key = scr_wc_import(args);
|
||||
if (!key) return scr_wc_rejected();
|
||||
return scr_wc_resolved(key);
|
||||
@@ -1106,8 +1151,14 @@ ScrPromise *scr_webcrypto_import_key(ScrDyn *args) {
|
||||
/* generateKey(algorithm, extractable, keyUsages) for HMAC and AES keys:
|
||||
* random bytes of the requested length (an HMAC key defaults to its
|
||||
* hash's block size; a length that is not a whole number of bytes clears
|
||||
* the final byte's unused low bits). */
|
||||
* the final byte's unused low bits). ECDSA, Ed25519, RSASSA-PKCS1-v1_5 and
|
||||
* RSA-PSS key pairs generate in scr_webcrypto_pki.c. */
|
||||
ScrPromise *scr_webcrypto_generate_key(ScrDyn *args) {
|
||||
const char *asymmetric = scr_wc_asymmetric_name(args, 0);
|
||||
if (asymmetric) {
|
||||
return scr_wc_asymmetric ? scr_wc_asymmetric->generate_key(args)
|
||||
: scr_wc_asymmetric_missing("generateKey", asymmetric);
|
||||
}
|
||||
static const char prefix[] = "Failed to execute 'generateKey' on 'SubtleCrypto'";
|
||||
if (!scr_wc_required(prefix, args, 3)) return scr_wc_rejected();
|
||||
ScrWcArgs a;
|
||||
@@ -1193,8 +1244,10 @@ static ScrStr *scr_wc_jwk_k(const ScrCryptoKey *key) {
|
||||
}
|
||||
|
||||
/* exportKeySync for secret keys: raw and raw-secret answer an ArrayBuffer
|
||||
* of the key bytes, jwk an object { key_ops, ext, alg, kty, k }. */
|
||||
static ScrDyn *scr_wc_export(const ScrDyn *args) {
|
||||
* of the key bytes, jwk an object { key_ops, ext, alg, kty, k }. An ECDSA,
|
||||
* Ed25519 or RSA key sets *asymmetric after the format check instead
|
||||
* (scr_webcrypto_pki.c exports it). */
|
||||
static ScrDyn *scr_wc_export(const ScrDyn *args, bool *asymmetric) {
|
||||
static const char prefix[] = "Failed to execute 'exportKey' on 'SubtleCrypto'";
|
||||
if (!scr_wc_required(prefix, args, 2)) return NULL;
|
||||
ScrWcArgs a;
|
||||
@@ -1203,6 +1256,10 @@ static ScrDyn *scr_wc_export(const ScrDyn *args) {
|
||||
const char *format = scr_wc_key_format(prefix, a.v[0]);
|
||||
if (!format) goto done;
|
||||
ScrCryptoKey *key = scr_crypto_key_of(a.v[1]);
|
||||
if (!key && scr_wc_asymmetric && scr_wc_asymmetric->is_key(a.v[1])) {
|
||||
*asymmetric = true;
|
||||
goto done;
|
||||
}
|
||||
if (!key) {
|
||||
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 2nd argument is not of type CryptoKey.", prefix);
|
||||
goto done;
|
||||
@@ -1230,7 +1287,9 @@ done:
|
||||
}
|
||||
|
||||
ScrPromise *scr_webcrypto_export_key(ScrDyn *args) {
|
||||
ScrDyn *result = scr_wc_export(args);
|
||||
bool asymmetric = false;
|
||||
ScrDyn *result = scr_wc_export(args, &asymmetric);
|
||||
if (asymmetric) return scr_wc_asymmetric->export_key(args);
|
||||
if (!result) return scr_wc_rejected();
|
||||
return scr_wc_resolved(result);
|
||||
}
|
||||
@@ -1255,7 +1314,8 @@ static bool scr_wc_sign_verify(const ScrDyn *args, bool verify, unsigned char ma
|
||||
scr_str_release(s);
|
||||
}
|
||||
ScrCryptoKey *key = scr_crypto_key_of(a.v[1]);
|
||||
if (!key) {
|
||||
bool asymmetric_key = !key && scr_wc_asymmetric && scr_wc_asymmetric->is_key(a.v[1]);
|
||||
if (!key && !asymmetric_key) {
|
||||
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 2nd argument is not of type CryptoKey.", prefix);
|
||||
goto done;
|
||||
}
|
||||
@@ -1273,7 +1333,7 @@ static bool scr_wc_sign_verify(const ScrDyn *args, bool verify, unsigned char ma
|
||||
scr_wc_unsupported_algorithm(method, algorithm.name);
|
||||
goto done;
|
||||
}
|
||||
if (strcmp(key->name, algorithm.name) != 0) {
|
||||
if (asymmetric_key || strcmp(key->name, algorithm.name) != 0) {
|
||||
scr_throw_domex("InvalidAccessError", "Key algorithm mismatch");
|
||||
goto done;
|
||||
}
|
||||
@@ -1297,6 +1357,11 @@ done:
|
||||
}
|
||||
|
||||
ScrPromise *scr_webcrypto_sign(ScrDyn *args) {
|
||||
const char *asymmetric = scr_wc_asymmetric_name(args, 0);
|
||||
if (asymmetric) {
|
||||
return scr_wc_asymmetric ? scr_wc_asymmetric->sign(args)
|
||||
: scr_wc_asymmetric_missing("sign", asymmetric);
|
||||
}
|
||||
unsigned char mac[64];
|
||||
size_t mac_len = 0;
|
||||
if (!scr_wc_sign_verify(args, false, mac, &mac_len, NULL)) return scr_wc_rejected();
|
||||
@@ -1319,6 +1384,11 @@ static void scr_wc_settle_verified(ScrClosure *cb, ScrError *error, ScrBytes *va
|
||||
}
|
||||
|
||||
ScrPromise *scr_webcrypto_verify(ScrDyn *args) {
|
||||
const char *asymmetric = scr_wc_asymmetric_name(args, 0);
|
||||
if (asymmetric) {
|
||||
return scr_wc_asymmetric ? scr_wc_asymmetric->verify(args)
|
||||
: scr_wc_asymmetric_missing("verify", asymmetric);
|
||||
}
|
||||
unsigned char mac[64];
|
||||
size_t mac_len = 0;
|
||||
ScrBytes *signature = NULL;
|
||||
@@ -1619,7 +1689,9 @@ ScrPromise *scr_webcrypto_cipher(ScrDyn *args, bool decrypt, ScrWebCryptoAesFn a
|
||||
ScrPromise *result = NULL;
|
||||
if (!scr_wc_algorithm_identifier(a.v[0])) goto done;
|
||||
ScrCryptoKey *key = scr_crypto_key_of(a.v[1]);
|
||||
if (!key) {
|
||||
/* an ECDSA, Ed25519 or RSA key (scr_webcrypto_pki.c) is no cipher key */
|
||||
bool asymmetric_key = !key && scr_wc_asymmetric && scr_wc_asymmetric->is_key(a.v[1]);
|
||||
if (!key && !asymmetric_key) {
|
||||
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 2nd argument is not of type CryptoKey.", prefix);
|
||||
goto done;
|
||||
}
|
||||
@@ -1627,7 +1699,7 @@ ScrPromise *scr_webcrypto_cipher(ScrDyn *args, bool decrypt, ScrWebCryptoAesFn a
|
||||
if (!data) goto done;
|
||||
const char *name = scr_wc_normalize_cipher(a.v[0], method, ¶ms);
|
||||
if (!name) goto done;
|
||||
if (strcmp(key->name, name) != 0) {
|
||||
if (asymmetric_key || strcmp(key->name, name) != 0) {
|
||||
scr_throw_domex("InvalidAccessError", "Key algorithm mismatch");
|
||||
goto done;
|
||||
}
|
||||
@@ -1717,19 +1789,20 @@ ScrPromise *scr_webcrypto_wrap(ScrDyn *args, ScrWebCryptoAesFn aes) {
|
||||
const char *format = scr_wc_key_format(prefix, a.v[0]);
|
||||
if (!format) goto done;
|
||||
ScrCryptoKey *key = scr_crypto_key_of(a.v[1]);
|
||||
if (!key) {
|
||||
if (!key && !scr_wc_is_asymmetric_key(a.v[1])) {
|
||||
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 2nd argument is not of type CryptoKey.", prefix);
|
||||
goto done;
|
||||
}
|
||||
ScrCryptoKey *wrapping = scr_crypto_key_of(a.v[2]);
|
||||
if (!wrapping) {
|
||||
if (!wrapping && !scr_wc_is_asymmetric_key(a.v[2])) {
|
||||
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 3rd argument is not of type CryptoKey.", prefix);
|
||||
goto done;
|
||||
}
|
||||
if (!scr_wc_algorithm_identifier(a.v[3])) goto done;
|
||||
const char *name = scr_wc_normalize_wrap(a.v[3], "wrapKey", ¶ms);
|
||||
if (!name) goto done;
|
||||
if (strcmp(wrapping->name, name) != 0) {
|
||||
/* An ECDSA, Ed25519 or RSA key never has an AES algorithm. */
|
||||
if (!wrapping || strcmp(wrapping->name, name) != 0) {
|
||||
scr_throw_domex("InvalidAccessError", "Key algorithm mismatch");
|
||||
goto done;
|
||||
}
|
||||
@@ -1737,6 +1810,10 @@ ScrPromise *scr_webcrypto_wrap(ScrDyn *args, ScrWebCryptoAesFn aes) {
|
||||
scr_throw_domex("InvalidAccessError", "Unable to use this key to wrapKey");
|
||||
goto done;
|
||||
}
|
||||
if (!key) {
|
||||
scr_wc_unsupported("wrapKey", "an ECDSA, Ed25519 or RSA key");
|
||||
goto done;
|
||||
}
|
||||
if (!scr_wc_exportable(format, key)) goto done;
|
||||
payload = scr_wc_wrap_payload(format, key, strcmp(name, "AES-KW") == 0);
|
||||
result = scr_wc_run_cipher(aes, name, false, wrapping, ¶ms, payload);
|
||||
@@ -1782,7 +1859,7 @@ ScrPromise *scr_webcrypto_unwrap(ScrDyn *args, ScrWebCryptoAesFn aes) {
|
||||
wrapped = scr_wc_buffer_source(prefix, 1, v[1]);
|
||||
if (!wrapped) goto done;
|
||||
ScrCryptoKey *unwrapping = scr_crypto_key_of(v[2]);
|
||||
if (!unwrapping) {
|
||||
if (!unwrapping && !scr_wc_is_asymmetric_key(v[2])) {
|
||||
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 3rd argument is not of type CryptoKey.", prefix);
|
||||
goto done;
|
||||
}
|
||||
@@ -1796,7 +1873,7 @@ ScrPromise *scr_webcrypto_unwrap(ScrDyn *args, ScrWebCryptoAesFn aes) {
|
||||
if (!scr_wc_normalize(v[4], scr_wc_import_names, SCR_WC_COUNT(scr_wc_import_names),
|
||||
SCR_WC_OP_IMPORT, &imported))
|
||||
goto done;
|
||||
if (strcmp(unwrapping->name, name) != 0) {
|
||||
if (!unwrapping || strcmp(unwrapping->name, name) != 0) {
|
||||
scr_throw_domex("InvalidAccessError", "Key algorithm mismatch");
|
||||
goto done;
|
||||
}
|
||||
@@ -1804,6 +1881,10 @@ ScrPromise *scr_webcrypto_unwrap(ScrDyn *args, ScrWebCryptoAesFn aes) {
|
||||
scr_throw_domex("InvalidAccessError", "Unable to use this key to unwrapKey");
|
||||
goto done;
|
||||
}
|
||||
if (scr_wc_is_asymmetric_algorithm(imported.name)) {
|
||||
scr_wc_unsupported_algorithm("unwrapKey", imported.name);
|
||||
goto done;
|
||||
}
|
||||
if (!scr_wc_max_length(wrapped, "data")) goto done;
|
||||
ScrBytes *bytes = scr_wc_cipher_bytes(aes, name, true, unwrapping, ¶ms, wrapped);
|
||||
if (!bytes) {
|
||||
@@ -2002,7 +2083,8 @@ static ScrBytes *scr_wc_kdf_derive(const ScrWcKdfParams *params, const ScrCrypto
|
||||
/* The base key checks deriveBits and deriveKey share. */
|
||||
static bool scr_wc_check_base_key(const ScrCryptoKey *key, const char *algorithm, unsigned usage,
|
||||
const char *usage_name) {
|
||||
if (!(key->usages & usage)) {
|
||||
/* NULL: an asymmetric key, which never has a derive usage */
|
||||
if (key == NULL || !(key->usages & usage)) {
|
||||
char message[64];
|
||||
snprintf(message, sizeof message, "baseKey does not have %s usage", usage_name);
|
||||
scr_throw_domex("InvalidAccessError", message);
|
||||
@@ -2028,7 +2110,9 @@ ScrPromise *scr_webcrypto_derive_bits(ScrDyn *args) {
|
||||
ScrPromise *result = NULL;
|
||||
if (!scr_wc_algorithm_identifier(a.v[0])) goto done;
|
||||
ScrCryptoKey *key = scr_crypto_key_of(a.v[1]);
|
||||
if (!key) {
|
||||
/* an ECDSA, Ed25519 or RSA key (scr_webcrypto_pki.c) has no derive usage */
|
||||
bool asymmetric_key = !key && scr_wc_asymmetric && scr_wc_asymmetric->is_key(a.v[1]);
|
||||
if (!key && !asymmetric_key) {
|
||||
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 2nd argument is not of type CryptoKey.", prefix);
|
||||
goto done;
|
||||
}
|
||||
@@ -2038,7 +2122,9 @@ ScrPromise *scr_webcrypto_derive_bits(ScrDyn *args) {
|
||||
if (!null_length && !scr_wc_enforce_range(prefix, "3rd argument", a.v[2], 4294967295.0, &length))
|
||||
goto done;
|
||||
if (!scr_wc_normalize_kdf(a.v[0], "deriveBits", ¶ms)) goto done;
|
||||
if (!scr_wc_check_base_key(key, params.name, SCR_WC_USAGE_DERIVE_BITS, "deriveBits")) goto done;
|
||||
if (!scr_wc_check_base_key(asymmetric_key ? NULL : key, params.name, SCR_WC_USAGE_DERIVE_BITS,
|
||||
"deriveBits"))
|
||||
goto done;
|
||||
if (!scr_wc_kdf_length(¶ms, null_length, length)) goto done;
|
||||
if (length == 0) {
|
||||
ScrBytes *empty = scr_bytes_new(SCR_BYTES_U8, 0);
|
||||
@@ -2099,7 +2185,9 @@ ScrPromise *scr_webcrypto_derive_key(ScrDyn *args) {
|
||||
ScrPromise *result = NULL;
|
||||
if (!scr_wc_algorithm_identifier(a.v[0])) goto done;
|
||||
ScrCryptoKey *key = scr_crypto_key_of(a.v[1]);
|
||||
if (!key) {
|
||||
/* an ECDSA, Ed25519 or RSA key (scr_webcrypto_pki.c) has no derive usage */
|
||||
bool asymmetric_key = !key && scr_wc_asymmetric && scr_wc_asymmetric->is_key(a.v[1]);
|
||||
if (!key && !asymmetric_key) {
|
||||
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 2nd argument is not of type CryptoKey.", prefix);
|
||||
goto done;
|
||||
}
|
||||
@@ -2120,7 +2208,9 @@ ScrPromise *scr_webcrypto_derive_key(ScrDyn *args) {
|
||||
scr_wc_unsupported_algorithm("deriveKey", imported.name);
|
||||
goto done;
|
||||
}
|
||||
if (!scr_wc_check_base_key(key, params.name, SCR_WC_USAGE_DERIVE_KEY, "deriveKey")) goto done;
|
||||
if (!scr_wc_check_base_key(asymmetric_key ? NULL : key, params.name, SCR_WC_USAGE_DERIVE_KEY,
|
||||
"deriveKey"))
|
||||
goto done;
|
||||
if (!scr_wc_kdf_length(¶ms, null_length, length)) goto done;
|
||||
ScrBytes *bits = scr_wc_kdf_derive(¶ms, key, length);
|
||||
if (!bits) goto done;
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/* The Web IDL and error helpers of scr_webcrypto.c that the asymmetric
|
||||
* algorithms (scr_webcrypto_pki.c) share, and the table through which
|
||||
* scr_webcrypto.c reaches them when that unit is linked. */
|
||||
#ifndef SCR_WEBCRYPTO_H
|
||||
#define SCR_WEBCRYPTO_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define SCR_WC_COUNT(names) (sizeof(names) / sizeof((names)[0]))
|
||||
|
||||
enum {
|
||||
SCR_WC_USAGE_ENCRYPT = 1u << 0,
|
||||
SCR_WC_USAGE_DECRYPT = 1u << 1,
|
||||
SCR_WC_USAGE_SIGN = 1u << 2,
|
||||
SCR_WC_USAGE_VERIFY = 1u << 3,
|
||||
SCR_WC_USAGE_DERIVE_KEY = 1u << 4,
|
||||
SCR_WC_USAGE_DERIVE_BITS = 1u << 5,
|
||||
SCR_WC_USAGE_WRAP_KEY = 1u << 6,
|
||||
SCR_WC_USAGE_UNWRAP_KEY = 1u << 7,
|
||||
};
|
||||
|
||||
/* Node's canonical usage order; the bit is the index. */
|
||||
extern const char *const scr_wc_usages[12];
|
||||
extern const char *const scr_wc_ordinals[5];
|
||||
extern const char scr_wc_normalize_prefix[];
|
||||
extern const char *const scr_wc_digest_names[13];
|
||||
|
||||
typedef struct {
|
||||
ScrDyn *v[5];
|
||||
} ScrWcArgs;
|
||||
|
||||
void scr_wc_type_error(const char *code, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
|
||||
void scr_wc_unsupported(const char *method, const char *what);
|
||||
void scr_wc_unsupported_algorithm(const char *method, const char *name);
|
||||
bool scr_wc_is_object(const ScrDyn *v);
|
||||
ScrDyn *scr_wc_plain(ScrDyn *v);
|
||||
ScrBytes *scr_wc_buffer_source(const char *prefix, size_t index, ScrDyn *value);
|
||||
ScrStr *scr_wc_dom_string(const ScrDyn *value);
|
||||
const char *scr_wc_key_format(const char *prefix, ScrDyn *value);
|
||||
long scr_wc_key_usages(const char *prefix, size_t index, ScrDyn *value);
|
||||
void scr_wc_missing_member(const char *dict, const char *member);
|
||||
ScrDyn *scr_wc_member(const ScrDyn *dict, const char *key);
|
||||
ScrStr *scr_wc_algorithm_name(ScrDyn *identifier, const char *dict);
|
||||
const char *scr_wc_digest_algorithm(ScrDyn *identifier, const char *method);
|
||||
const char *scr_wc_lookup(const char *const *names, size_t count, const ScrStr *name);
|
||||
ScrDyn *scr_wc_cstr(const char *s);
|
||||
ScrPromise *scr_wc_rejected(void);
|
||||
bool scr_wc_required(const char *prefix, const ScrDyn *args, size_t required);
|
||||
void scr_wc_args_take(ScrWcArgs *a, const ScrDyn *args, size_t n);
|
||||
void scr_wc_args_drop(ScrWcArgs *a, size_t n);
|
||||
|
||||
/* ECDSA, RSASSA-PKCS1-v1_5 and RSA-PSS (scr_webcrypto_pki.c, linked with
|
||||
* the pki unit): each takes the method's argument vector (borrowed) and
|
||||
* answers a +1 promise. NULL until that unit installs it. */
|
||||
typedef struct {
|
||||
ScrPromise *(*generate_key)(const ScrDyn *args);
|
||||
ScrPromise *(*import_key)(const ScrDyn *args);
|
||||
ScrPromise *(*export_key)(const ScrDyn *args);
|
||||
ScrPromise *(*sign)(const ScrDyn *args);
|
||||
ScrPromise *(*verify)(const ScrDyn *args);
|
||||
bool (*is_key)(const ScrDyn *value);
|
||||
} ScrWcAsymmetric;
|
||||
|
||||
extern const ScrWcAsymmetric *scr_wc_asymmetric;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,841 @@
|
||||
/* WebCrypto's ECDSA, RSASSA-PKCS1-v1_5, RSA-PSS and Ed25519 algorithms:
|
||||
* crypto.subtle.generateKey, importKey (spki, pkcs8, jwk, and raw for ECDSA
|
||||
* and Ed25519 public keys), exportKey, sign and verify, and the CryptoKey
|
||||
* values of these keys. ECDSA keys are on P-256, P-384 and P-521; ECDSA
|
||||
* signatures are IEEE P1363 r || s.
|
||||
*
|
||||
* Argument conversion, algorithm normalization and the DOMException
|
||||
* shapes follow Node 24's lib/internal/crypto/webcrypto.js, ec.js and
|
||||
* rsa.js, through the Web IDL helpers of scr_webcrypto.c. The keys and
|
||||
* signatures come from the pki unit (scr_pki.c). This unit installs
|
||||
* itself into scr_webcrypto.c's dispatch table at startup; the compiler
|
||||
* links it when a program uses WebCrypto and names one of these
|
||||
* algorithms. All arguments are borrowed. */
|
||||
#include "scr_runtime.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "scr_pki.h"
|
||||
#include "scr_webcrypto.h"
|
||||
|
||||
typedef enum { SW_ECDSA, SW_RSASSA, SW_PSS, SW_ED25519 } SwKind;
|
||||
|
||||
static const char *const sw_names[] = {"ECDSA", "RSASSA-PKCS1-v1_5", "RSA-PSS", "Ed25519"};
|
||||
|
||||
static const char *const sw_curves[] = {"P-256", "P-384", "P-521"};
|
||||
|
||||
/* ── CryptoKey ────────────────────────────────────────────────────────── */
|
||||
|
||||
typedef struct SwKey {
|
||||
size_t rc;
|
||||
ScrPkiKey *key;
|
||||
SwKind kind;
|
||||
const char *hash; /* RSA keys: the WebCrypto hash name */
|
||||
unsigned usages;
|
||||
bool extractable;
|
||||
ScrDyn *algorithm; /* cached getter results, or NULL */
|
||||
ScrDyn *usage_list;
|
||||
} SwKey;
|
||||
|
||||
static void *sw_key_retain(void *p) {
|
||||
((SwKey *)p)->rc++;
|
||||
return p;
|
||||
}
|
||||
|
||||
static void sw_key_release(void *p) {
|
||||
SwKey *k = p;
|
||||
if (!k || --k->rc != 0) return;
|
||||
scr_pki_key_release(k->key);
|
||||
if (k->algorithm) scr_dyn_release(k->algorithm);
|
||||
if (k->usage_list) scr_dyn_release(k->usage_list);
|
||||
free(k);
|
||||
}
|
||||
|
||||
static SwKey *sw_key_of(const ScrDyn *value) {
|
||||
if (value->kind != SCR_DYN_HANDLE || value->v.handle.tag != SCR_DYNH_ASYMMETRIC_CRYPTO_KEY) return NULL;
|
||||
return value->v.handle.ptr;
|
||||
}
|
||||
|
||||
static bool sw_is_key(const ScrDyn *value) { return sw_key_of(value) != NULL; }
|
||||
|
||||
static ScrDyn *sw_key_algorithm(SwKey *k) {
|
||||
if (!k->algorithm) {
|
||||
ScrDyn *algorithm = scr_dyn_new_obj();
|
||||
scr_dyn_obj_set(algorithm, "name", 4, scr_wc_cstr(sw_names[k->kind]));
|
||||
if (k->kind == SW_ED25519) {
|
||||
/* { name } only */
|
||||
} else if (k->kind == SW_ECDSA) {
|
||||
scr_dyn_obj_set(algorithm, "namedCurve", 10, scr_wc_cstr(scr_pki_key_curve(k->key)));
|
||||
} else {
|
||||
scr_dyn_obj_set(algorithm, "modulusLength", 13, scr_dyn_new_num((double)scr_pki_key_bits(k->key)));
|
||||
ScrBytes *exponent = scr_pki_key_exponent(k->key);
|
||||
scr_dyn_obj_set(algorithm, "publicExponent", 14, scr_dyn_new_bytes(exponent));
|
||||
scr_bytes_release(exponent);
|
||||
ScrDyn *hash = scr_dyn_new_obj();
|
||||
scr_dyn_obj_set(hash, "name", 4, scr_wc_cstr(k->hash));
|
||||
scr_dyn_obj_set(algorithm, "hash", 4, hash);
|
||||
}
|
||||
k->algorithm = algorithm;
|
||||
}
|
||||
return scr_dyn_retain(k->algorithm);
|
||||
}
|
||||
|
||||
static ScrDyn *sw_usage_array(unsigned usages) {
|
||||
ScrDyn *list = scr_dyn_new_arr();
|
||||
for (size_t i = 0; i < SCR_WC_COUNT(scr_wc_usages); i++)
|
||||
if (usages & (1u << i)) scr_dyn_arr_push(list, scr_wc_cstr(scr_wc_usages[i]));
|
||||
return list;
|
||||
}
|
||||
|
||||
static ScrDyn *sw_key_usages(SwKey *k) {
|
||||
if (!k->usage_list) k->usage_list = sw_usage_array(k->usages);
|
||||
return scr_dyn_retain(k->usage_list);
|
||||
}
|
||||
|
||||
static bool sw_name_is(const char *name, size_t len, const char *literal) {
|
||||
return len == strlen(literal) && memcmp(name, literal, len) == 0;
|
||||
}
|
||||
|
||||
static ScrDyn *sw_key_get(void *h, const char *name, size_t len) {
|
||||
SwKey *k = h;
|
||||
if (sw_name_is(name, len, "type"))
|
||||
return scr_wc_cstr(scr_pki_key_is_private(k->key) ? "private" : "public");
|
||||
if (sw_name_is(name, len, "extractable")) return scr_dyn_new_bool(k->extractable);
|
||||
if (sw_name_is(name, len, "algorithm")) return sw_key_algorithm(k);
|
||||
if (sw_name_is(name, len, "usages")) return sw_key_usages(k);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool sw_key_set(void *h, const char *name, size_t len, const ScrDyn *value) {
|
||||
(void)h;
|
||||
(void)value;
|
||||
if (!sw_name_is(name, len, "type") && !sw_name_is(name, len, "extractable") &&
|
||||
!sw_name_is(name, len, "algorithm") && !sw_name_is(name, len, "usages"))
|
||||
return false;
|
||||
scr_pki_throw_fmt(SCR_ERR_TYPE, NULL, "Cannot set property %.*s of #<CryptoKey> which has only a getter", (int)len,
|
||||
name);
|
||||
return true;
|
||||
}
|
||||
|
||||
static ScrDyn *sw_key_invoke(void *h, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc,
|
||||
const char *what) {
|
||||
(void)h;
|
||||
(void)self;
|
||||
(void)args;
|
||||
(void)argc;
|
||||
if (strcmp(method, "toString") == 0) return scr_wc_cstr("[object CryptoKey]");
|
||||
scr_pki_throw_fmt(SCR_ERR_TYPE, NULL, "%s is not a function", what);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ScrDyn *sw_key_symbol_get(void *h, const ScrDyn *key) {
|
||||
(void)h;
|
||||
if (key->kind != SCR_DYN_SYMBOL || !key->v.symbol.description) return NULL;
|
||||
ScrStr *description = key->v.symbol.description(key->v.symbol.value);
|
||||
bool tag = description && description->len == 18 && memcmp(description->data, "Symbol.toStringTag", 18) == 0;
|
||||
if (description) scr_str_release(description);
|
||||
return tag ? scr_wc_cstr("CryptoKey") : NULL;
|
||||
}
|
||||
|
||||
/* As for HMAC CryptoKeys, util.inspect refuses these keys rather than
|
||||
* printing a different shape than Node's custom inspector. */
|
||||
static const ScrDynHandleOps sw_key_ops = {
|
||||
.cls = "CryptoKey",
|
||||
.retain = sw_key_retain,
|
||||
.release = sw_key_release,
|
||||
.invoke = sw_key_invoke,
|
||||
.get = sw_key_get,
|
||||
.set = sw_key_set,
|
||||
.json_empty_object = true,
|
||||
.to_string_tag = "CryptoKey",
|
||||
.symbol_get = sw_key_symbol_get,
|
||||
};
|
||||
|
||||
/* Boxes a CryptoKey over `key` (consumed). */
|
||||
static ScrDyn *sw_key_new(ScrPkiKey *key, SwKind kind, const char *hash, unsigned usages, bool extractable) {
|
||||
static SCR_TL bool installed;
|
||||
if (!installed) {
|
||||
installed = true;
|
||||
scr_dyn_handle_install(SCR_DYNH_ASYMMETRIC_CRYPTO_KEY, &sw_key_ops);
|
||||
}
|
||||
SwKey *k = calloc(1, sizeof *k);
|
||||
if (!k) scr_trap("scriptc: out of memory\n");
|
||||
k->rc = 1;
|
||||
k->key = key;
|
||||
k->kind = kind;
|
||||
k->hash = hash;
|
||||
k->usages = usages;
|
||||
k->extractable = extractable;
|
||||
ScrDyn *box = scr_dyn_new_handle(k, SCR_DYNH_ASYMMETRIC_CRYPTO_KEY);
|
||||
sw_key_release(k);
|
||||
return box;
|
||||
}
|
||||
|
||||
/* ── algorithm parameters ─────────────────────────────────────────────── */
|
||||
|
||||
static SwKind sw_kind_of(const char *name) {
|
||||
if (strcmp(name, "ECDSA") == 0) return SW_ECDSA;
|
||||
if (strcmp(name, "Ed25519") == 0) return SW_ED25519;
|
||||
return strcmp(name, "RSA-PSS") == 0 ? SW_PSS : SW_RSASSA;
|
||||
}
|
||||
|
||||
static bool sw_is_rsa_kind(SwKind kind) { return kind == SW_RSASSA || kind == SW_PSS; }
|
||||
|
||||
/* The canonical algorithm name of an AlgorithmIdentifier the dispatcher
|
||||
* already recognized as one of this unit's algorithms, or NULL with the
|
||||
* error pending. */
|
||||
static const char *sw_algorithm_name(ScrDyn *identifier) {
|
||||
ScrStr *name = scr_wc_algorithm_name(identifier, "Algorithm");
|
||||
if (!name) return NULL;
|
||||
const char *found = scr_wc_lookup(sw_names, SCR_WC_COUNT(sw_names), name);
|
||||
scr_str_release(name);
|
||||
if (!found) scr_throw_domex("NotSupportedError", "Unrecognized algorithm name");
|
||||
return found;
|
||||
}
|
||||
|
||||
/* A required dictionary member (+1), or NULL with ERR_MISSING_OPTION. */
|
||||
static ScrDyn *sw_required(ScrDyn *plain, const char *dict, const char *member) {
|
||||
ScrDyn *v = scr_wc_member(plain, member);
|
||||
if (v->kind == SCR_DYN_UNDEF) {
|
||||
scr_dyn_release(v);
|
||||
scr_wc_missing_member(dict, member);
|
||||
return NULL;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/* namedCurve (DOMString, required): one of the implemented curves, or
|
||||
* NULL with the error pending. */
|
||||
static const char *sw_named_curve(ScrDyn *plain, const char *dict) {
|
||||
ScrDyn *value = sw_required(plain, dict, "namedCurve");
|
||||
if (!value) return NULL;
|
||||
ScrStr *s = scr_wc_dom_string(value);
|
||||
scr_dyn_release(value);
|
||||
if (!s) return NULL;
|
||||
const char *curve = NULL;
|
||||
for (size_t i = 0; i < SCR_WC_COUNT(sw_curves); i++)
|
||||
if (s->len == strlen(sw_curves[i]) && memcmp(s->data, sw_curves[i], s->len) == 0) curve = sw_curves[i];
|
||||
scr_str_release(s);
|
||||
if (!curve) scr_throw_domex("NotSupportedError", "Unrecognized namedCurve");
|
||||
return curve;
|
||||
}
|
||||
|
||||
/* hash (HashAlgorithmIdentifier, required): with `sha_only`, Node's
|
||||
* ensureSHA check first ("Only SHA hashes are supported in <dict>"). */
|
||||
static const char *sw_hash(ScrDyn *plain, const char *dict, bool sha_only, const char *method) {
|
||||
ScrDyn *hash = sw_required(plain, dict, "hash");
|
||||
if (!hash) return NULL;
|
||||
if (sha_only) {
|
||||
ScrStr *name = NULL;
|
||||
if (scr_wc_is_object(hash)) {
|
||||
ScrDyn *hp = scr_wc_plain(hash);
|
||||
ScrDyn *member = hp ? scr_wc_member(hp, "name") : NULL;
|
||||
if (hp) scr_dyn_release(hp);
|
||||
if (member && member->kind == SCR_DYN_STR) name = scr_str_retain(member->v.str);
|
||||
if (member) scr_dyn_release(member);
|
||||
} else {
|
||||
name = scr_wc_dom_string(hash);
|
||||
if (!name) {
|
||||
scr_dyn_release(hash);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
bool sha = name && name->len >= 3 && (name->data[0] | 32) == 's' && (name->data[1] | 32) == 'h' &&
|
||||
(name->data[2] | 32) == 'a';
|
||||
if (name) scr_str_release(name);
|
||||
if (!sha) {
|
||||
char message[96];
|
||||
snprintf(message, sizeof message, "Only SHA hashes are supported in %s", dict);
|
||||
scr_throw_domex("NotSupportedError", message);
|
||||
scr_dyn_release(hash);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
const char *canonical = scr_wc_digest_algorithm(hash, method);
|
||||
scr_dyn_release(hash);
|
||||
return canonical;
|
||||
}
|
||||
|
||||
/* An [EnforceRange] unsigned long member; false with the TypeError
|
||||
* pending. */
|
||||
static bool sw_unsigned_long(ScrDyn *value, const char *member, double *out) {
|
||||
double x;
|
||||
if (!scr_dyn_number_coerce_js(value, &x)) return false;
|
||||
if (x != x || x == 1.0 / 0.0 || x == -1.0 / 0.0) {
|
||||
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: %s in passed algorithm is not a finite number.",
|
||||
scr_wc_normalize_prefix, member);
|
||||
return false;
|
||||
}
|
||||
x = trunc(x);
|
||||
if (x < 0 || x > 4294967295.0) {
|
||||
scr_wc_type_error("ERR_OUT_OF_RANGE",
|
||||
"%s: %s in passed algorithm is outside the expected range of 0 to 4294967295.",
|
||||
scr_wc_normalize_prefix, member);
|
||||
return false;
|
||||
}
|
||||
*out = x == 0 ? 0 : x;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void sw_unsupported_usage(SwKind kind) {
|
||||
char message[96];
|
||||
snprintf(message, sizeof message, "Unsupported key usage for %s key", sw_names[kind]);
|
||||
scr_throw_domex("SyntaxError", message);
|
||||
}
|
||||
|
||||
/* ── generateKey ──────────────────────────────────────────────────────── */
|
||||
|
||||
static ScrPromise *sw_generate_key(const ScrDyn *args) {
|
||||
static const char prefix[] = "Failed to execute 'generateKey' on 'SubtleCrypto'";
|
||||
if (!scr_wc_required(prefix, args, 3)) return scr_wc_rejected();
|
||||
ScrWcArgs a;
|
||||
scr_wc_args_take(&a, args, 3);
|
||||
ScrDyn *pair = NULL;
|
||||
ScrDyn *plain = NULL;
|
||||
if (!scr_wc_is_object(a.v[0])) {
|
||||
ScrStr *s = scr_wc_dom_string(a.v[0]);
|
||||
if (!s) goto done;
|
||||
scr_str_release(s);
|
||||
}
|
||||
bool extractable = scr_dyn_truthy(a.v[1]);
|
||||
long usages = scr_wc_key_usages(prefix, 2, a.v[2]);
|
||||
if (usages < 0) goto done;
|
||||
const char *name = sw_algorithm_name(a.v[0]);
|
||||
if (!name) goto done;
|
||||
SwKind kind = sw_kind_of(name);
|
||||
plain = scr_wc_plain(a.v[0]);
|
||||
if (!plain) goto done;
|
||||
const char *curve = NULL;
|
||||
const char *hash = NULL;
|
||||
double bits = 0;
|
||||
ScrBytes *exponent = NULL;
|
||||
if (kind == SW_ED25519) {
|
||||
/* no parameters */
|
||||
} else if (kind == SW_ECDSA) {
|
||||
if (!scr_wc_is_object(plain)) {
|
||||
scr_wc_missing_member("EcKeyGenParams", "namedCurve");
|
||||
goto done;
|
||||
}
|
||||
curve = sw_named_curve(plain, "EcKeyGenParams");
|
||||
if (!curve) goto done;
|
||||
} else {
|
||||
static const char dict[] = "RsaHashedKeyGenParams";
|
||||
if (!scr_wc_is_object(plain)) {
|
||||
scr_wc_missing_member(dict, "hash");
|
||||
goto done;
|
||||
}
|
||||
/* Dictionary members convert in name order: hash, modulusLength,
|
||||
* publicExponent. */
|
||||
hash = sw_hash(plain, dict, true, "generateKey");
|
||||
if (!hash) goto done;
|
||||
ScrDyn *length = sw_required(plain, dict, "modulusLength");
|
||||
if (!length) goto done;
|
||||
bool ok = sw_unsigned_long(length, "modulusLength", &bits);
|
||||
scr_dyn_release(length);
|
||||
if (!ok) goto done;
|
||||
ScrDyn *e = sw_required(plain, dict, "publicExponent");
|
||||
if (!e) goto done;
|
||||
if (e->kind != SCR_DYN_BYTES || e->v.bytes->elem != SCR_BYTES_U8) {
|
||||
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: publicExponent in passed algorithm is not instance of Uint8Array.",
|
||||
scr_wc_normalize_prefix);
|
||||
scr_dyn_release(e);
|
||||
goto done;
|
||||
}
|
||||
exponent = scr_bytes_buffer_source(e);
|
||||
scr_dyn_release(e);
|
||||
}
|
||||
if (usages & ~(long)(SCR_WC_USAGE_SIGN | SCR_WC_USAGE_VERIFY)) {
|
||||
sw_unsupported_usage(kind);
|
||||
if (exponent) scr_bytes_release(exponent);
|
||||
goto done;
|
||||
}
|
||||
ScrPkiKey *key;
|
||||
if (kind == SW_ED25519) {
|
||||
key = scr_pki_generate_ed25519();
|
||||
if (!key) goto done;
|
||||
} else if (kind == SW_ECDSA) {
|
||||
key = scr_pki_generate_ec(curve);
|
||||
} else {
|
||||
/* publicExponent: big-endian, at most a 32-bit value. */
|
||||
size_t n = (size_t)scr_bytes_byte_len(exponent);
|
||||
uint64_t value = 0;
|
||||
bool fits = true;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
if (value > 0xffffffu && fits) fits = false;
|
||||
value = (value << 8) | exponent->data[i];
|
||||
}
|
||||
scr_bytes_release(exponent);
|
||||
if (!fits || value > 0xffffffffu) {
|
||||
scr_throw_domex("OperationError", "The publicExponent must be equivalent to an unsigned 32-bit value");
|
||||
goto done;
|
||||
}
|
||||
if (bits < 512) {
|
||||
scr_throw_domex("OperationError", "algorithm.modulusLength must be at least 512");
|
||||
goto done;
|
||||
}
|
||||
if ((value & 1) == 0) {
|
||||
scr_throw_domex("OperationError", "algorithm.publicExponent must be odd");
|
||||
goto done;
|
||||
}
|
||||
if (bits > 16384 || value < 3) {
|
||||
scr_wc_unsupported("generateKey", "this RSA modulus length or public exponent");
|
||||
goto done;
|
||||
}
|
||||
key = scr_pki_generate_rsa((unsigned)bits, value);
|
||||
}
|
||||
if (!key) {
|
||||
scr_throw_domex("OperationError", "Key generation failed");
|
||||
goto done;
|
||||
}
|
||||
unsigned private_usages = (unsigned)usages & SCR_WC_USAGE_SIGN;
|
||||
unsigned public_usages = (unsigned)usages & SCR_WC_USAGE_VERIFY;
|
||||
if (private_usages == 0) {
|
||||
scr_pki_key_release(key);
|
||||
scr_throw_domex("SyntaxError", "Usages cannot be empty when creating a key.");
|
||||
goto done;
|
||||
}
|
||||
ScrPkiKey *public_key = scr_pki_key_public_of(key);
|
||||
if (!public_key) {
|
||||
scr_pki_key_release(key);
|
||||
scr_throw_domex("OperationError", "Key generation failed");
|
||||
goto done;
|
||||
}
|
||||
pair = scr_dyn_new_obj();
|
||||
scr_dyn_obj_set(pair, "publicKey", 9, sw_key_new(public_key, kind, hash, public_usages, true));
|
||||
scr_dyn_obj_set(pair, "privateKey", 10, sw_key_new(key, kind, hash, private_usages, extractable));
|
||||
done:
|
||||
if (plain) scr_dyn_release(plain);
|
||||
scr_wc_args_drop(&a, 3);
|
||||
if (!pair) return scr_wc_rejected();
|
||||
ScrPromise *p = scr_promise_new();
|
||||
scr_crypto_defer_dyn(pair, NULL, scr_crypto_promise_callback(p), scr_crypto_promise_settle_dyn);
|
||||
return p;
|
||||
}
|
||||
|
||||
/* ── importKey ────────────────────────────────────────────────────────── */
|
||||
|
||||
/* A JWK string member, or NULL. */
|
||||
static const ScrDyn *sw_jwk_member(const ScrDyn *jwk, const char *key) {
|
||||
const ScrDyn *v = scr_dyn_obj_get(jwk, key, strlen(key));
|
||||
return v && v->kind != SCR_DYN_UNDEF ? v : NULL;
|
||||
}
|
||||
|
||||
static bool sw_jwk_is(const ScrDyn *v, const char *text) {
|
||||
return v && v->kind == SCR_DYN_STR && v->v.str->len == strlen(text) &&
|
||||
memcmp(v->v.str->data, text, v->v.str->len) == 0;
|
||||
}
|
||||
|
||||
/* The JWK checks Node runs before reading the key: use, key_ops and ext. */
|
||||
static bool sw_jwk_checks(const ScrDyn *jwk, unsigned usages, bool extractable) {
|
||||
const ScrDyn *use = sw_jwk_member(jwk, "use");
|
||||
if (use && !sw_jwk_is(use, "sig")) {
|
||||
scr_throw_domex("DataError", "Invalid JWK \"use\" Parameter");
|
||||
return false;
|
||||
}
|
||||
const ScrDyn *ops = sw_jwk_member(jwk, "key_ops");
|
||||
if (ops) {
|
||||
if (ops->kind != SCR_DYN_ARR) {
|
||||
scr_throw_domex("DataError", "Key operations and usage mismatch");
|
||||
return false;
|
||||
}
|
||||
for (size_t u = 0; u < SCR_WC_COUNT(scr_wc_usages); u++) {
|
||||
if (!(usages & (1u << u))) continue;
|
||||
bool listed = false;
|
||||
double n = scr_dyn_arr_len(ops);
|
||||
for (double i = 0; i < n && !listed; i++) {
|
||||
ScrDyn *item = scr_dyn_arr_at(ops, i);
|
||||
listed = sw_jwk_is(item, scr_wc_usages[u]);
|
||||
scr_dyn_release(item);
|
||||
}
|
||||
if (!listed) {
|
||||
scr_throw_domex("DataError", "Key operations and usage mismatch");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
const ScrDyn *ext = sw_jwk_member(jwk, "ext");
|
||||
if (ext && ext->kind == SCR_DYN_BOOL && !ext->v.b && extractable) {
|
||||
scr_throw_domex("DataError", "JWK \"ext\" Parameter and extractable mismatch");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* The JWK "alg" an RSA key with this hash carries ("RS256", "PS384"). */
|
||||
static void sw_rsa_jwk_alg(SwKind kind, const char *hash, char out[8]) {
|
||||
const char *bits = strcmp(hash, "SHA-1") == 0 ? "1" : hash + 4;
|
||||
snprintf(out, 8, "%s%s", kind == SW_PSS ? "PS" : "RS", bits);
|
||||
}
|
||||
|
||||
static ScrDyn *sw_import(const ScrDyn *args) {
|
||||
static const char prefix[] = "Failed to execute 'importKey' on 'SubtleCrypto'";
|
||||
if (!scr_wc_required(prefix, args, 5)) return NULL;
|
||||
ScrWcArgs a;
|
||||
scr_wc_args_take(&a, args, 5);
|
||||
ScrDyn *result = NULL;
|
||||
ScrBytes *data = NULL;
|
||||
ScrDyn *jwk = NULL;
|
||||
ScrDyn *plain = NULL;
|
||||
const char *format = scr_wc_key_format(prefix, a.v[0]);
|
||||
if (!format) goto done;
|
||||
bool is_jwk = strcmp(format, "jwk") == 0;
|
||||
if (is_jwk) {
|
||||
if (!scr_wc_is_object(a.v[1])) {
|
||||
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 2nd argument is not of type JsonWebKey.", prefix);
|
||||
goto done;
|
||||
}
|
||||
jwk = scr_wc_plain(a.v[1]);
|
||||
if (!jwk) goto done;
|
||||
} else {
|
||||
data = scr_wc_buffer_source(prefix, 1, a.v[1]);
|
||||
if (!data) goto done;
|
||||
}
|
||||
if (!scr_wc_is_object(a.v[2])) {
|
||||
ScrStr *s = scr_wc_dom_string(a.v[2]);
|
||||
if (!s) goto done;
|
||||
scr_str_release(s);
|
||||
}
|
||||
bool extractable = scr_dyn_truthy(a.v[3]);
|
||||
long usages = scr_wc_key_usages(prefix, 4, a.v[4]);
|
||||
if (usages < 0) goto done;
|
||||
const char *name = sw_algorithm_name(a.v[2]);
|
||||
if (!name) goto done;
|
||||
SwKind kind = sw_kind_of(name);
|
||||
plain = scr_wc_plain(a.v[2]);
|
||||
if (!plain) goto done;
|
||||
const char *curve = NULL;
|
||||
const char *hash = NULL;
|
||||
if (kind == SW_ED25519) {
|
||||
/* no parameters */
|
||||
} else if (kind == SW_ECDSA) {
|
||||
if (!scr_wc_is_object(plain)) {
|
||||
scr_wc_missing_member("EcKeyImportParams", "namedCurve");
|
||||
goto done;
|
||||
}
|
||||
curve = sw_named_curve(plain, "EcKeyImportParams");
|
||||
if (!curve) goto done;
|
||||
} else {
|
||||
if (!scr_wc_is_object(plain)) {
|
||||
scr_wc_missing_member("RsaHashedImportParams", "hash");
|
||||
goto done;
|
||||
}
|
||||
hash = sw_hash(plain, "RsaHashedImportParams", true, "importKey");
|
||||
if (!hash) goto done;
|
||||
}
|
||||
bool private_key;
|
||||
if (is_jwk) {
|
||||
private_key = sw_jwk_member(jwk, "d") != NULL;
|
||||
} else if (strcmp(format, "pkcs8") == 0) {
|
||||
private_key = true;
|
||||
} else if (strcmp(format, "spki") == 0 ||
|
||||
((kind == SW_ECDSA || kind == SW_ED25519) && strcmp(format, "raw") == 0)) {
|
||||
private_key = false;
|
||||
} else {
|
||||
char message[96];
|
||||
snprintf(message, sizeof message, "Unable to import %s using %s format", sw_names[kind], format);
|
||||
scr_throw_domex("NotSupportedError", message);
|
||||
goto done;
|
||||
}
|
||||
unsigned allowed = private_key ? SCR_WC_USAGE_SIGN : SCR_WC_USAGE_VERIFY;
|
||||
ScrPkiKey *key = NULL;
|
||||
/* An Ed25519 private key derives its public key in the Ed25519 unit. */
|
||||
if (kind == SW_ED25519 && private_key && !scr_pki_ed25519_ready()) goto done;
|
||||
if (is_jwk) {
|
||||
const ScrDyn *kty = sw_jwk_member(jwk, "kty");
|
||||
if (!kty) {
|
||||
scr_throw_domex("DataError", "Invalid keyData");
|
||||
goto done;
|
||||
}
|
||||
if (!sw_jwk_is(kty, kind == SW_ECDSA ? "EC" : kind == SW_ED25519 ? "OKP" : "RSA")) {
|
||||
scr_throw_domex("DataError", "Invalid JWK \"kty\" Parameter");
|
||||
goto done;
|
||||
}
|
||||
if (kind == SW_ECDSA && !sw_jwk_is(sw_jwk_member(jwk, "crv"), curve)) {
|
||||
scr_throw_domex("DataError", "JWK \"crv\" does not match the requested algorithm");
|
||||
goto done;
|
||||
}
|
||||
if (kind == SW_ED25519 && !sw_jwk_is(sw_jwk_member(jwk, "crv"), "Ed25519")) {
|
||||
scr_throw_domex("DataError", "JWK \"crv\" Parameter and algorithm name mismatch");
|
||||
goto done;
|
||||
}
|
||||
if ((unsigned)usages & ~allowed) {
|
||||
sw_unsupported_usage(kind);
|
||||
goto done;
|
||||
}
|
||||
if (!sw_jwk_checks(jwk, (unsigned)usages, extractable)) goto done;
|
||||
const ScrDyn *alg = sw_jwk_member(jwk, "alg");
|
||||
if (kind == SW_ED25519 && alg && !sw_jwk_is(alg, "Ed25519") && !sw_jwk_is(alg, "EdDSA")) {
|
||||
scr_throw_domex("DataError", "JWK \"alg\" does not match the requested algorithm");
|
||||
goto done;
|
||||
}
|
||||
if (sw_is_rsa_kind(kind) && alg) {
|
||||
char expected[8];
|
||||
sw_rsa_jwk_alg(kind, hash, expected);
|
||||
if (!sw_jwk_is(alg, expected)) {
|
||||
scr_throw_domex("DataError", "JWK \"alg\" does not match the requested algorithm");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
key = scr_pki_key_from_jwk(jwk, private_key);
|
||||
} else {
|
||||
if ((unsigned)usages & ~allowed) {
|
||||
sw_unsupported_usage(kind);
|
||||
goto done;
|
||||
}
|
||||
ScrBytes *view = data;
|
||||
SCR_BYTES_SNAPSHOT(view);
|
||||
size_t len = (size_t)scr_bytes_byte_len(view);
|
||||
if (strcmp(format, "raw") == 0)
|
||||
key = kind == SW_ED25519 ? scr_pki_key_from_ed25519_raw(view->data, len)
|
||||
: scr_pki_key_from_point(curve, view->data, len);
|
||||
else key = scr_pki_key_parse_der(view->data, len, private_key);
|
||||
}
|
||||
if (!key) {
|
||||
scr_throw_domex("DataError", "Invalid keyData");
|
||||
goto done;
|
||||
}
|
||||
bool key_matches = kind == SW_ED25519 ? scr_pki_key_is_ed25519(key)
|
||||
: kind == SW_ECDSA ? !scr_pki_key_is_rsa(key) && !scr_pki_key_is_ed25519(key)
|
||||
: scr_pki_key_is_rsa(key);
|
||||
if (!key_matches) {
|
||||
scr_pki_key_release(key);
|
||||
scr_throw_domex("DataError", "Invalid key type");
|
||||
goto done;
|
||||
}
|
||||
if (kind == SW_ECDSA && strcmp(scr_pki_key_curve(key), curve) != 0) {
|
||||
scr_pki_key_release(key);
|
||||
scr_throw_domex("DataError", "Named curve mismatch");
|
||||
goto done;
|
||||
}
|
||||
if (private_key && usages == 0) {
|
||||
scr_pki_key_release(key);
|
||||
scr_throw_domex("SyntaxError", "Usages cannot be empty when importing a private key.");
|
||||
goto done;
|
||||
}
|
||||
result = sw_key_new(key, kind, hash, (unsigned)usages, extractable);
|
||||
done:
|
||||
if (data) scr_bytes_release(data);
|
||||
if (jwk) scr_dyn_release(jwk);
|
||||
if (plain) scr_dyn_release(plain);
|
||||
scr_wc_args_drop(&a, 5);
|
||||
return result;
|
||||
}
|
||||
|
||||
static ScrPromise *sw_import_key(const ScrDyn *args) {
|
||||
ScrDyn *key = sw_import(args);
|
||||
if (!key) return scr_wc_rejected();
|
||||
return scr_promise_settled_ref(key, &scr_dyn_retain_v, &scr_dyn_release_v, NULL);
|
||||
}
|
||||
|
||||
/* ── exportKey ────────────────────────────────────────────────────────── */
|
||||
|
||||
static ScrDyn *sw_array_buffer(ScrBytes *bytes) {
|
||||
ScrDyn *out = scr_array_buffer_from_bytes(bytes);
|
||||
scr_bytes_release(bytes);
|
||||
return out;
|
||||
}
|
||||
|
||||
static ScrDyn *sw_export(const ScrDyn *args) {
|
||||
ScrWcArgs a;
|
||||
scr_wc_args_take(&a, args, 2);
|
||||
ScrDyn *result = NULL;
|
||||
const char *format = scr_wc_key_format("Failed to execute 'exportKey' on 'SubtleCrypto'", a.v[0]);
|
||||
SwKey *k = sw_key_of(a.v[1]);
|
||||
bool private_key = scr_pki_key_is_private(k->key);
|
||||
if (!format) goto done;
|
||||
if (!k->extractable) {
|
||||
scr_throw_domex("InvalidAccessError", "key is not extractable");
|
||||
goto done;
|
||||
}
|
||||
if (strcmp(format, "spki") == 0 || strcmp(format, "pkcs8") == 0) {
|
||||
bool want_private = strcmp(format, "pkcs8") == 0;
|
||||
if (want_private != private_key) {
|
||||
scr_throw_domex("InvalidAccessError", want_private ? "Key must be a private key" : "Key must be a public key");
|
||||
goto done;
|
||||
}
|
||||
ScrBytes *der = scr_pki_export_der(k->key);
|
||||
if (!der) {
|
||||
scr_throw_domex("OperationError", "Failed to export key");
|
||||
goto done;
|
||||
}
|
||||
result = sw_array_buffer(der);
|
||||
} else if (strcmp(format, "raw") == 0 && (k->kind == SW_ECDSA || k->kind == SW_ED25519) &&
|
||||
!private_key) {
|
||||
ScrBytes *point = k->kind == SW_ED25519 ? scr_pki_export_ed25519_raw(k->key) : scr_pki_export_point(k->key);
|
||||
if (!point) {
|
||||
scr_throw_domex("OperationError", "Failed to export key");
|
||||
goto done;
|
||||
}
|
||||
result = sw_array_buffer(point);
|
||||
} else if (strcmp(format, "jwk") == 0) {
|
||||
ScrDyn *members = scr_pki_export_jwk(k->key);
|
||||
ScrDyn *jwk = scr_dyn_new_obj();
|
||||
scr_dyn_obj_set(jwk, "key_ops", 7, sw_usage_array(k->usages));
|
||||
scr_dyn_obj_set(jwk, "ext", 3, scr_dyn_new_bool(k->extractable));
|
||||
if (sw_is_rsa_kind(k->kind)) {
|
||||
char alg[8];
|
||||
sw_rsa_jwk_alg(k->kind, k->hash, alg);
|
||||
scr_dyn_obj_set(jwk, "alg", 3, scr_wc_cstr(alg));
|
||||
} else if (k->kind == SW_ED25519) {
|
||||
scr_dyn_obj_set(jwk, "alg", 3, scr_wc_cstr("Ed25519"));
|
||||
}
|
||||
/* The key members in Node's order for each key type. */
|
||||
static const char *const rsa_names[] = {"kty", "n", "e", "d", "p", "q", "dp", "dq", "qi"};
|
||||
static const char *const ec_names[] = {"kty", "x", "y", "crv", "d"};
|
||||
static const char *const okp_names[] = {"kty", "crv", "x", "d"};
|
||||
const char *const *names = sw_is_rsa_kind(k->kind) ? rsa_names : k->kind == SW_ED25519 ? okp_names : ec_names;
|
||||
size_t count = sw_is_rsa_kind(k->kind) ? SCR_WC_COUNT(rsa_names)
|
||||
: k->kind == SW_ED25519 ? SCR_WC_COUNT(okp_names)
|
||||
: SCR_WC_COUNT(ec_names);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
const ScrDyn *v = scr_dyn_obj_get(members, names[i], strlen(names[i]));
|
||||
if (v) scr_dyn_obj_set(jwk, names[i], strlen(names[i]), scr_dyn_retain((ScrDyn *)v));
|
||||
}
|
||||
scr_dyn_release(members);
|
||||
result = jwk;
|
||||
} else {
|
||||
char message[128];
|
||||
snprintf(message, sizeof message, "Unable to export %s %s key using %s format", sw_names[k->kind],
|
||||
private_key ? "private" : "public", format);
|
||||
scr_throw_domex("NotSupportedError", message);
|
||||
}
|
||||
done:
|
||||
scr_wc_args_drop(&a, 2);
|
||||
return result;
|
||||
}
|
||||
|
||||
static ScrPromise *sw_export_key(const ScrDyn *args) {
|
||||
ScrDyn *value = sw_export(args);
|
||||
if (!value) return scr_wc_rejected();
|
||||
return scr_promise_settled_ref(value, &scr_dyn_retain_v, &scr_dyn_release_v, NULL);
|
||||
}
|
||||
|
||||
/* ── sign and verify ──────────────────────────────────────────────────── */
|
||||
|
||||
/* The deferred verify result: a dyn boolean. */
|
||||
static void sw_settle_verified(ScrClosure *cb, ScrError *error, ScrBytes *value) {
|
||||
(void)error;
|
||||
ScrPromise *p;
|
||||
memcpy(&p, &cb->caps[0]->slot, sizeof p);
|
||||
ScrDyn *verified = (ScrDyn *)value;
|
||||
scr_promise_fulfill_bool(p, scr_dyn_truthy(verified));
|
||||
scr_dyn_release(verified);
|
||||
}
|
||||
|
||||
static ScrPromise *sw_sign_verify(const ScrDyn *args, bool verify) {
|
||||
const char *prefix = verify ? "Failed to execute 'verify' on 'SubtleCrypto'"
|
||||
: "Failed to execute 'sign' on 'SubtleCrypto'";
|
||||
const char *method = verify ? "verify" : "sign";
|
||||
size_t n = verify ? 4 : 3;
|
||||
if (!scr_wc_required(prefix, args, n)) return scr_wc_rejected();
|
||||
ScrWcArgs a;
|
||||
scr_wc_args_take(&a, args, n);
|
||||
ScrPromise *result = NULL;
|
||||
ScrBytes *data = NULL;
|
||||
ScrBytes *signature = NULL;
|
||||
ScrDyn *plain = NULL;
|
||||
if (!scr_wc_is_object(a.v[0])) {
|
||||
ScrStr *s = scr_wc_dom_string(a.v[0]);
|
||||
if (!s) goto done;
|
||||
scr_str_release(s);
|
||||
}
|
||||
SwKey *k = sw_key_of(a.v[1]);
|
||||
if (!k && !scr_webcrypto_is_crypto_key(a.v[1])) {
|
||||
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 2nd argument is not of type CryptoKey.", prefix);
|
||||
goto done;
|
||||
}
|
||||
if (verify) {
|
||||
signature = scr_wc_buffer_source(prefix, 2, a.v[2]);
|
||||
if (!signature) goto done;
|
||||
}
|
||||
data = scr_wc_buffer_source(prefix, verify ? 3 : 2, a.v[verify ? 3 : 2]);
|
||||
if (!data) goto done;
|
||||
const char *name = sw_algorithm_name(a.v[0]);
|
||||
if (!name) goto done;
|
||||
SwKind kind = sw_kind_of(name);
|
||||
plain = scr_wc_plain(a.v[0]);
|
||||
if (!plain) goto done;
|
||||
const char *hash = NULL;
|
||||
int salt = 0;
|
||||
if (kind == SW_ED25519) {
|
||||
/* no parameters */
|
||||
} else if (kind == SW_ECDSA) {
|
||||
if (!scr_wc_is_object(plain)) {
|
||||
scr_wc_missing_member("EcdsaParams", "hash");
|
||||
goto done;
|
||||
}
|
||||
hash = sw_hash(plain, "EcdsaParams", false, method);
|
||||
if (!hash) goto done;
|
||||
} else if (kind == SW_PSS) {
|
||||
if (!scr_wc_is_object(plain)) {
|
||||
scr_wc_missing_member("RsaPssParams", "saltLength");
|
||||
goto done;
|
||||
}
|
||||
ScrDyn *value = sw_required(plain, "RsaPssParams", "saltLength");
|
||||
if (!value) goto done;
|
||||
double x;
|
||||
bool ok = sw_unsigned_long(value, "saltLength", &x);
|
||||
scr_dyn_release(value);
|
||||
if (!ok) goto done;
|
||||
salt = x > 65536 ? 65536 : (int)x;
|
||||
}
|
||||
if (!k || k->kind != kind) {
|
||||
scr_throw_domex("InvalidAccessError", "Key algorithm mismatch");
|
||||
goto done;
|
||||
}
|
||||
if (!(k->usages & (verify ? SCR_WC_USAGE_VERIFY : SCR_WC_USAGE_SIGN))) {
|
||||
char message[64];
|
||||
snprintf(message, sizeof message, "Unable to use this key to %s", method);
|
||||
scr_throw_domex("InvalidAccessError", message);
|
||||
goto done;
|
||||
}
|
||||
if (sw_is_rsa_kind(kind)) hash = k->hash;
|
||||
if (kind == SW_ED25519 && !scr_pki_ed25519_ready()) goto done;
|
||||
{
|
||||
ScrBytes *view = data;
|
||||
SCR_BYTES_SNAPSHOT(view);
|
||||
size_t len = (size_t)scr_bytes_byte_len(view);
|
||||
if (verify) {
|
||||
ScrBytes *sig = signature;
|
||||
SCR_BYTES_SNAPSHOT(sig);
|
||||
bool ok = scr_pki_verify(k->key, hash, kind == SW_PSS, salt, view->data, len, sig->data,
|
||||
(size_t)scr_bytes_byte_len(sig));
|
||||
result = scr_promise_new();
|
||||
scr_crypto_defer_dyn(scr_dyn_new_bool(ok), NULL, scr_crypto_promise_callback(result), sw_settle_verified);
|
||||
} else {
|
||||
ScrBytes *out = scr_pki_sign(k->key, hash, kind == SW_PSS, salt, view->data, len);
|
||||
if (!out) {
|
||||
scr_throw_domex("OperationError", "Failed to sign");
|
||||
goto done;
|
||||
}
|
||||
result = scr_promise_new();
|
||||
scr_crypto_defer_bytes(out, NULL, scr_crypto_promise_callback(result), scr_crypto_promise_settle_array_buffer);
|
||||
}
|
||||
}
|
||||
done:
|
||||
if (data) scr_bytes_release(data);
|
||||
if (signature) scr_bytes_release(signature);
|
||||
if (plain) scr_dyn_release(plain);
|
||||
scr_wc_args_drop(&a, n);
|
||||
return result ? result : scr_wc_rejected();
|
||||
}
|
||||
|
||||
static ScrPromise *sw_sign(const ScrDyn *args) { return sw_sign_verify(args, false); }
|
||||
|
||||
static ScrPromise *sw_verify(const ScrDyn *args) { return sw_sign_verify(args, true); }
|
||||
|
||||
static const ScrWcAsymmetric sw_table = {
|
||||
.generate_key = sw_generate_key,
|
||||
.import_key = sw_import_key,
|
||||
.export_key = sw_export_key,
|
||||
.sign = sw_sign,
|
||||
.verify = sw_verify,
|
||||
.is_key = sw_is_key,
|
||||
};
|
||||
|
||||
/* Installed before main: scr_webcrypto.c dispatches these algorithms
|
||||
* here. */
|
||||
__attribute__((constructor)) static void sw_install(void) { scr_wc_asymmetric = &sw_table; }
|
||||
Reference in New Issue
Block a user