@scriptc/runtime 0.0.31 → 0.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scriptc/runtime",
3
- "version": "0.0.31",
3
+ "version": "0.0.32",
4
4
  "description": "scriptc native runtime — C sources, compiled into every scriptc binary",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://scriptc.dev",
package/src/scr_musl.c CHANGED
@@ -2,7 +2,7 @@
2
2
  * this TU together with -DSCR_MUSL). musl deliberately provides no libc
3
3
  * identification macro, so the target-selected project macro is the guard.
4
4
  *
5
- * The x86_64 ucontext implementation below is derived from libucontext
5
+ * The x86_64 and AArch64 ucontext implementations below are derived from libucontext
6
6
  * commit 49e671dd52ff6791295d8161ad3b6da7dc5f6f9d:
7
7
  * https://github.com/kaniini/libucontext
8
8
  *
@@ -27,8 +27,8 @@
27
27
  #include <ucontext.h>
28
28
  #endif
29
29
 
30
- #if !defined(__x86_64__)
31
- #error "scriptc's musl runtime currently supports x86_64 only"
30
+ #if !defined(__x86_64__) && !defined(__aarch64__)
31
+ #error "scriptc's musl runtime currently supports x86_64 and AArch64 only"
32
32
  #endif
33
33
 
34
34
  /* The scriptc runtime uses arc4random_buf as its infallible CSPRNG contract.
@@ -57,9 +57,10 @@ void arc4random_buf(void *buf, size_t n) {
57
57
 
58
58
  /* musl exposes the POSIX ucontext types but intentionally omits these legacy
59
59
  * functions. scriptc's async/generator fibers need only user-space register
60
- * swaps; they never use a context to mutate the process signal mask. This is
61
- * libucontext's fast (non-POSIX-signal-mask) x86_64 implementation, emitted
62
- * under the standard symbol names that musl leaves unresolved. */
60
+ * swaps; they never use a context to mutate the process signal mask. These are
61
+ * libucontext's fast (non-POSIX-signal-mask) implementations, emitted under
62
+ * the standard symbol names that musl leaves unresolved. */
63
+ #if defined(__x86_64__)
63
64
  _Static_assert(offsetof(ucontext_t, uc_mcontext.gregs) == 40,
64
65
  "unexpected musl x86_64 ucontext layout");
65
66
  _Static_assert(offsetof(ucontext_t, uc_mcontext.fpregs) == 224,
@@ -265,6 +266,169 @@ void makecontext(ucontext_t *ucp, void (*func)(void), int argc, ...) {
265
266
  #undef SCR_STR
266
267
  #undef SCR_STR_INNER
267
268
 
269
+ #elif defined(__aarch64__)
270
+
271
+ /* musl's AArch64 ucontext embeds the kernel sigcontext after a 128-byte
272
+ * signal mask. Keep the offsets pinned to the Zig 0.16 musl headers: the
273
+ * assembly below saves the ABI's callee-preserved GPR and SIMD state there. */
274
+ _Static_assert(offsetof(ucontext_t, uc_mcontext.regs[0]) == 184,
275
+ "unexpected musl AArch64 register layout");
276
+ _Static_assert(offsetof(ucontext_t, uc_mcontext.sp) == 432,
277
+ "unexpected musl AArch64 stack-pointer layout");
278
+ _Static_assert(offsetof(ucontext_t, uc_mcontext.pc) == 440,
279
+ "unexpected musl AArch64 program-counter layout");
280
+ _Static_assert(offsetof(ucontext_t, uc_mcontext.pstate) == 448,
281
+ "unexpected musl AArch64 processor-state layout");
282
+ _Static_assert(offsetof(ucontext_t, uc_mcontext.__reserved) == 464,
283
+ "unexpected musl AArch64 SIMD-state layout");
284
+
285
+ #define SCR_STR_INNER(x) #x
286
+ #define SCR_STR(x) SCR_STR_INNER(x)
287
+ #define SCR_A64_UC_REG(reg) (184 + ((reg) * 8))
288
+ #define SCR_A64_UC_SP 432
289
+ #define SCR_A64_UC_PC 440
290
+ #define SCR_A64_UC_PSTATE 448
291
+ #define SCR_A64_UC_FPSIMD 464
292
+
293
+ __asm__(
294
+ ".text\n"
295
+ ".p2align 2\n"
296
+ ".global getcontext\n"
297
+ "getcontext:\n"
298
+ " str xzr, [x0, #" SCR_STR(SCR_A64_UC_REG(0)) "]\n"
299
+ " stp x2, x3, [x0, #" SCR_STR(SCR_A64_UC_REG(2)) "]\n"
300
+ " str x30, [x0, #" SCR_STR(SCR_A64_UC_PC) "]\n"
301
+ " mov x2, sp\n"
302
+ " str x2, [x0, #" SCR_STR(SCR_A64_UC_SP) "]\n"
303
+ " str xzr, [x0, #" SCR_STR(SCR_A64_UC_PSTATE) "]\n"
304
+ " add x2, x0, #" SCR_STR(SCR_A64_UC_FPSIMD) "\n"
305
+ " stp q8, q9, [x2, #144]\n"
306
+ " stp q10, q11, [x2, #176]\n"
307
+ " stp q12, q13, [x2, #208]\n"
308
+ " stp q14, q15, [x2, #240]\n"
309
+ " mov x2, x0\n"
310
+ " mov x0, #0\n"
311
+ " stp x0, x1, [x2, #" SCR_STR(SCR_A64_UC_REG(0)) "]\n"
312
+ " stp x4, x5, [x2, #" SCR_STR(SCR_A64_UC_REG(4)) "]\n"
313
+ " stp x6, x7, [x2, #" SCR_STR(SCR_A64_UC_REG(6)) "]\n"
314
+ " stp x8, x9, [x2, #" SCR_STR(SCR_A64_UC_REG(8)) "]\n"
315
+ " stp x10, x11, [x2, #" SCR_STR(SCR_A64_UC_REG(10)) "]\n"
316
+ " stp x12, x13, [x2, #" SCR_STR(SCR_A64_UC_REG(12)) "]\n"
317
+ " stp x14, x15, [x2, #" SCR_STR(SCR_A64_UC_REG(14)) "]\n"
318
+ " stp x16, x17, [x2, #" SCR_STR(SCR_A64_UC_REG(16)) "]\n"
319
+ " stp x18, x19, [x2, #" SCR_STR(SCR_A64_UC_REG(18)) "]\n"
320
+ " stp x20, x21, [x2, #" SCR_STR(SCR_A64_UC_REG(20)) "]\n"
321
+ " stp x22, x23, [x2, #" SCR_STR(SCR_A64_UC_REG(22)) "]\n"
322
+ " stp x24, x25, [x2, #" SCR_STR(SCR_A64_UC_REG(24)) "]\n"
323
+ " stp x26, x27, [x2, #" SCR_STR(SCR_A64_UC_REG(26)) "]\n"
324
+ " stp x28, x29, [x2, #" SCR_STR(SCR_A64_UC_REG(28)) "]\n"
325
+ " str x30, [x2, #" SCR_STR(SCR_A64_UC_REG(30)) "]\n"
326
+ " ret\n"
327
+
328
+ ".p2align 2\n"
329
+ ".global setcontext\n"
330
+ "setcontext:\n"
331
+ " ldp x18, x19, [x0, #" SCR_STR(SCR_A64_UC_REG(18)) "]\n"
332
+ " ldp x20, x21, [x0, #" SCR_STR(SCR_A64_UC_REG(20)) "]\n"
333
+ " ldp x22, x23, [x0, #" SCR_STR(SCR_A64_UC_REG(22)) "]\n"
334
+ " ldp x24, x25, [x0, #" SCR_STR(SCR_A64_UC_REG(24)) "]\n"
335
+ " ldp x26, x27, [x0, #" SCR_STR(SCR_A64_UC_REG(26)) "]\n"
336
+ " ldp x28, x29, [x0, #" SCR_STR(SCR_A64_UC_REG(28)) "]\n"
337
+ " ldr x30, [x0, #" SCR_STR(SCR_A64_UC_REG(30)) "]\n"
338
+ " ldr x2, [x0, #" SCR_STR(SCR_A64_UC_SP) "]\n"
339
+ " mov sp, x2\n"
340
+ " add x2, x0, #" SCR_STR(SCR_A64_UC_FPSIMD) "\n"
341
+ " ldp q8, q9, [x2, #144]\n"
342
+ " ldp q10, q11, [x2, #176]\n"
343
+ " ldp q12, q13, [x2, #208]\n"
344
+ " ldp q14, q15, [x2, #240]\n"
345
+ " ldr x16, [x0, #" SCR_STR(SCR_A64_UC_PC) "]\n"
346
+ " ldp x2, x3, [x0, #" SCR_STR(SCR_A64_UC_REG(2)) "]\n"
347
+ " ldp x4, x5, [x0, #" SCR_STR(SCR_A64_UC_REG(4)) "]\n"
348
+ " ldp x6, x7, [x0, #" SCR_STR(SCR_A64_UC_REG(6)) "]\n"
349
+ " ldp x0, x1, [x0, #" SCR_STR(SCR_A64_UC_REG(0)) "]\n"
350
+ " br x16\n"
351
+
352
+ ".p2align 2\n"
353
+ ".global swapcontext\n"
354
+ "swapcontext:\n"
355
+ " str xzr, [x0, #" SCR_STR(SCR_A64_UC_REG(0)) "]\n"
356
+ " stp x2, x3, [x0, #" SCR_STR(SCR_A64_UC_REG(2)) "]\n"
357
+ " stp x4, x5, [x0, #" SCR_STR(SCR_A64_UC_REG(4)) "]\n"
358
+ " stp x6, x7, [x0, #" SCR_STR(SCR_A64_UC_REG(6)) "]\n"
359
+ " stp x8, x9, [x0, #" SCR_STR(SCR_A64_UC_REG(8)) "]\n"
360
+ " stp x10, x11, [x0, #" SCR_STR(SCR_A64_UC_REG(10)) "]\n"
361
+ " stp x12, x13, [x0, #" SCR_STR(SCR_A64_UC_REG(12)) "]\n"
362
+ " stp x14, x15, [x0, #" SCR_STR(SCR_A64_UC_REG(14)) "]\n"
363
+ " stp x16, x17, [x0, #" SCR_STR(SCR_A64_UC_REG(16)) "]\n"
364
+ " stp x18, x19, [x0, #" SCR_STR(SCR_A64_UC_REG(18)) "]\n"
365
+ " stp x20, x21, [x0, #" SCR_STR(SCR_A64_UC_REG(20)) "]\n"
366
+ " stp x22, x23, [x0, #" SCR_STR(SCR_A64_UC_REG(22)) "]\n"
367
+ " stp x24, x25, [x0, #" SCR_STR(SCR_A64_UC_REG(24)) "]\n"
368
+ " stp x26, x27, [x0, #" SCR_STR(SCR_A64_UC_REG(26)) "]\n"
369
+ " stp x28, x29, [x0, #" SCR_STR(SCR_A64_UC_REG(28)) "]\n"
370
+ " str x30, [x0, #" SCR_STR(SCR_A64_UC_REG(30)) "]\n"
371
+ " str x30, [x0, #" SCR_STR(SCR_A64_UC_PC) "]\n"
372
+ " mov x2, sp\n"
373
+ " str x2, [x0, #" SCR_STR(SCR_A64_UC_SP) "]\n"
374
+ " str xzr, [x0, #" SCR_STR(SCR_A64_UC_PSTATE) "]\n"
375
+ " add x2, x0, #" SCR_STR(SCR_A64_UC_FPSIMD) "\n"
376
+ " stp q8, q9, [x2, #144]\n"
377
+ " stp q10, q11, [x2, #176]\n"
378
+ " stp q12, q13, [x2, #208]\n"
379
+ " stp q14, q15, [x2, #240]\n"
380
+ " mov x0, x1\n"
381
+ " b setcontext\n"
382
+
383
+ ".p2align 2\n"
384
+ ".hidden scr_musl_context_trampoline\n"
385
+ "scr_musl_context_trampoline:\n"
386
+ " mov x0, x19\n"
387
+ " cbz x0, 1f\n"
388
+ " bl setcontext\n"
389
+ " brk #0\n"
390
+ "1:\n"
391
+ " mov w0, #0\n"
392
+ " bl exit\n"
393
+ " brk #0\n");
394
+
395
+ extern void scr_musl_context_trampoline(void);
396
+
397
+ void makecontext(ucontext_t *ucp, void (*func)(void), int argc, ...) {
398
+ greg_t *sp;
399
+ va_list va;
400
+ int i;
401
+
402
+ sp = (greg_t *)((uintptr_t)ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size);
403
+ sp -= argc < 8 ? 0 : argc - 8;
404
+ sp = (greg_t *)((uintptr_t)sp & ~(uintptr_t)15);
405
+
406
+ ucp->uc_mcontext.sp = (greg_t)(uintptr_t)sp;
407
+ ucp->uc_mcontext.pc = (greg_t)(uintptr_t)func;
408
+ ucp->uc_mcontext.regs[19] = (greg_t)(uintptr_t)ucp->uc_link;
409
+ ucp->uc_mcontext.regs[30] =
410
+ (greg_t)(uintptr_t)&scr_musl_context_trampoline;
411
+
412
+ va_start(va, argc);
413
+ for (i = 0; i < argc && i < 8; i++) {
414
+ ucp->uc_mcontext.regs[i] = va_arg(va, greg_t);
415
+ }
416
+ for (; i < argc; i++) {
417
+ *sp++ = va_arg(va, greg_t);
418
+ }
419
+ va_end(va);
420
+ }
421
+
422
+ #undef SCR_A64_UC_FPSIMD
423
+ #undef SCR_A64_UC_PSTATE
424
+ #undef SCR_A64_UC_PC
425
+ #undef SCR_A64_UC_SP
426
+ #undef SCR_A64_UC_REG
427
+ #undef SCR_STR
428
+ #undef SCR_STR_INNER
429
+
430
+ #endif /* architecture */
431
+
268
432
  #endif /* !SCR_LIB */
269
433
 
270
434
  #else /* !SCR_MUSL */
package/src/scr_runtime.h CHANGED
@@ -5493,18 +5493,25 @@ long scr_secure_ctx_live_count(void);
5493
5493
  #endif
5494
5494
 
5495
5495
  /* ── node:tls, the CA-store introspection slice (scr_tls_ca.c — its own
5496
- * unit and link gate; plain PEM-block bookkeeping, NO mbedTLS, so a
5497
- * getCACertificates-only binary never builds the archive; cc.ts also
5498
- * compiles it whenever scr_tls.c does). The HOST bundle (the
5499
- * /etc/ssl/cert.pem probe order scr_tls.c documents) stands in for both
5500
- * Node's compiled-in Mozilla roots ('bundled', rootCertificates) and the
5501
- * platform store ('system') the established SEMANTICS divergence,
5502
- * extended to introspection; 'extra' uses the NODE_EXTRA_CA_CERTS file
5503
- * captured before user code runs. Arrays are cached per type (+1 retained
5504
- * answers each call — Node's own caching, and the identity the suite pins
5505
- * with strictEqual). */
5496
+ * unit and link gate; NO mbedTLS, so a getCACertificates-only binary never
5497
+ * builds the archive; cc.ts also compiles it whenever scr_tls.c does). The
5498
+ * HOST roots (Windows' system certificate stores, the established bundle probe on
5499
+ * POSIX) stand in for both Node's compiled-in Mozilla roots ('bundled',
5500
+ * rootCertificates) and the platform store ('system') the established
5501
+ * SEMANTICS divergence, extended to introspection; 'extra' uses the
5502
+ * NODE_EXTRA_CA_CERTS file captured before user code runs. Arrays are cached
5503
+ * per type (+1 retained answers each call Node's own caching, and the
5504
+ * identity the suite pins with strictEqual). */
5506
5505
  void scr_tls_ca_install(void); /* snapshots NODE_EXTRA_CA_CERTS + file bytes */
5507
5506
  bool scr_tls_ca_extra_pem(const char **pem, size_t *len); /* borrowed launch snapshot */
5507
+ #ifdef _WIN32
5508
+ /* Enumerates Windows' trusted ROOT, intermediate CA, and TrustedPeople store
5509
+ * locations, yielding only certificates whose effective Windows EKU policy
5510
+ * permits TLS server authentication. DER is borrowed for the call. */
5511
+ typedef void (*ScrTlsCaWindowsCertFn)(void *ctx, const unsigned char *der, size_t len);
5512
+ bool scr_tls_ca_windows_cert_server_auth(const void *cert_context);
5513
+ void scr_tls_ca_windows_certs(ScrTlsCaWindowsCertFn fn, void *ctx);
5514
+ #endif
5508
5515
  ScrArr *scr_tls_ca_get(ScrStr *type); /* +1; throws ERR_INVALID_ARG_VALUE on unknown types */
5509
5516
  ScrArr *scr_tls_ca_root(void); /* +1; === getCACertificates("bundled") */
5510
5517
  /* Replaces the 'default' set: entries filter to their PEM certificate
package/src/scr_tls.c CHANGED
@@ -56,7 +56,7 @@
56
56
  * head buffers in the socket's write buffer until the handshake
57
57
  * establishes (the mid-connect buffering path, reused verbatim).
58
58
  * rejectUnauthorized:false maps to VERIFY_NONE; `ca` replaces the trust
59
- * anchors; with neither, /etc/ssl/cert.pem stands in for Node's bundled
59
+ * anchors; with neither, the host trust store stands in for Node's bundled
60
60
  * Mozilla roots (SEMANTICS.md documents the divergence). Verify
61
61
  * failures surface as Node-shaped 'error' messages on the request
62
62
  * ("self-signed certificate", "self-signed certificate in certificate
@@ -336,15 +336,15 @@ typedef struct ScrTlsCli {
336
336
  bool verify_recorded;
337
337
  } ScrTlsCli;
338
338
 
339
- /* The default trust anchors when no `ca` option is given: the system
340
- * bundle, standing in for Node's compiled-in Mozilla roots, plus
341
- * NODE_EXTRA_CA_CERTS. The system bundle is probed once across the distro
342
- * spellings /etc/ssl/cert.pem first (macOS ships it; Alpine links it),
343
- * then Debian/Ubuntu's ca-certificates.crt, Fedora/RHEL's ca-bundle.crt,
344
- * and openSUSE's ca-bundle.pem. On macOS only the first path exists, so
345
- * the probe order changes nothing there. A host with none leaves only the
346
- * optional extra chain. Windows is that host by construction (no PEM
347
- * bundle ships; the OS store is cert-database-shaped). */
339
+ /* The default trust anchors when no `ca` option is given: the host trust
340
+ * store, standing in for Node's compiled-in Mozilla roots, plus
341
+ * NODE_EXTRA_CA_CERTS. Windows certificates live as DER entries across the
342
+ * machine, enterprise, current-user, and policy ROOT/CA/TrustedPeople store
343
+ * locations; mbedTLS copies each successfully parsed entry into its own chain.
344
+ * POSIX hosts probe the established bundle spellings /etc/ssl/cert.pem
345
+ * first (macOS ships it; Alpine links it), then Debian/Ubuntu's
346
+ * ca-certificates.crt, Fedora/RHEL's ca-bundle.crt, and openSUSE's
347
+ * ca-bundle.pem. */
348
348
  static mbedtls_x509_crt scr_tls_system_roots;
349
349
  static bool scr_tls_system_roots_loaded = false;
350
350
 
@@ -356,6 +356,12 @@ static bool scr_tls_system_roots_loaded = false;
356
356
  static mbedtls_x509_crt scr_tls_override_roots;
357
357
  static uint64_t scr_tls_override_parsed_gen = 0;
358
358
 
359
+ #ifdef _WIN32
360
+ static void scr_tls_add_windows_ca(void *ctx, const unsigned char *der, size_t len) {
361
+ (void)mbedtls_x509_crt_parse_der((mbedtls_x509_crt *)ctx, der, len);
362
+ }
363
+ #endif
364
+
359
365
  static mbedtls_x509_crt *scr_tls_system_ca(void) {
360
366
  const char *pem = NULL;
361
367
  size_t pem_len = 0;
@@ -375,6 +381,12 @@ static mbedtls_x509_crt *scr_tls_system_ca(void) {
375
381
  if (!scr_tls_system_roots_loaded) {
376
382
  scr_tls_system_roots_loaded = true;
377
383
  mbedtls_x509_crt_init(&scr_tls_system_roots);
384
+ #ifdef _WIN32
385
+ /* The shared enumerator applies Windows' effective server-auth EKU
386
+ * policy before yielding DER. mbedTLS copies every successfully parsed
387
+ * entry, so the store contexts may die immediately after each callback. */
388
+ scr_tls_ca_windows_certs(scr_tls_add_windows_ca, &scr_tls_system_roots);
389
+ #else
378
390
  static const char *const bundles[] = {
379
391
  "/etc/ssl/cert.pem", /* macOS, Alpine */
380
392
  "/etc/ssl/certs/ca-certificates.crt", /* Debian/Ubuntu */
@@ -384,6 +396,7 @@ static mbedtls_x509_crt *scr_tls_system_ca(void) {
384
396
  for (size_t i = 0; i < sizeof bundles / sizeof bundles[0]; i++) {
385
397
  if (mbedtls_x509_crt_parse_file(&scr_tls_system_roots, bundles[i]) == 0) break;
386
398
  }
399
+ #endif
387
400
  const char *extra = NULL;
388
401
  size_t extra_len = 0;
389
402
  if (scr_tls_ca_extra_pem(&extra, &extra_len) && extra_len > 0) {
package/src/scr_tls_ca.c CHANGED
@@ -2,28 +2,33 @@
2
2
  * rootCertificates, and setDefaultCACertificates (scr_runtime.h has the
3
3
  * contracts). Its own link gate ("tlsca." libCalls), deliberately free
4
4
  * of mbedTLS: everything here is PEM-BLOCK bookkeeping over the host
5
- * bundle, so a program that only inspects the CA store keeps the lean
6
- * link line. scr_tls.c (when it links — cc.ts compiles this unit
7
- * alongside it) consults scr_tls_ca_default_override for its client
8
- * trust anchors, which is what makes setDefaultCACertificates LIVE
9
- * semantics: the next https/tls dial verifies against the replaced set.
5
+ * roots (plus crypt32 store access on Windows), so a program that only
6
+ * inspects the CA store never builds the TLS archive. scr_tls.c (when it
7
+ * links — cc.ts compiles this unit alongside it) consults
8
+ * scr_tls_ca_default_override for its client trust anchors, which is what
9
+ * makes setDefaultCACertificates LIVE semantics: the next https/tls dial
10
+ * verifies against the replaced set.
10
11
  *
11
12
  * Divergences, documented: the host bundle stands in for Node's
12
13
  * compiled-in Mozilla roots ('bundled' and rootCertificates) AND for the
13
- * platform store ('system') — the same /etc/ssl/cert.pem stance
14
- * scr_tls.c's anchors established; and set-time validation is PEM-BLOCK
15
- * shaped (a well-formed block with corrupt DER inside is kept here where
16
- * Node's X509 parse would drop it — such a cert then fails verification
17
- * instead, the same observable outcome one step later). Deduplication is
18
- * byte-exact over the extracted block where Node compares parsed X509
19
- * identities; re-encoded duplicates of one certificate stay distinct
20
- * entries, which no equality the tests observe can distinguish without a
21
- * parser. */
14
+ * platform store ('system') — Windows' system certificate stores and the POSIX
15
+ * bundle probe are the same sources scr_tls.c's anchors use; and set-time
16
+ * validation is PEM-BLOCK shaped (a well-formed block with corrupt DER
17
+ * inside is kept here where Node's X509 parse would drop it — such a cert
18
+ * then fails verification instead, the same observable outcome one step
19
+ * later). Deduplication is byte-exact over the extracted block where Node
20
+ * compares parsed X509 identities; re-encoded duplicates of one certificate
21
+ * stay distinct entries, which no equality the tests observe can distinguish
22
+ * without a parser. */
22
23
  #include "scr_runtime.h"
23
24
 
24
25
  #include <stdio.h>
25
26
  #include <stdlib.h>
26
27
  #include <string.h>
28
+ #ifdef _WIN32
29
+ #include <windows.h>
30
+ #include <wincrypt.h>
31
+ #endif
27
32
 
28
33
  /* The per-type caches: +1 held here for the process lifetime (an atexit
29
34
  * teardown releases them so the RC audit stays clean). 'default' also
@@ -154,9 +159,121 @@ bool scr_tls_ca_extra_pem(const char **pem, size_t *len) {
154
159
  return true;
155
160
  }
156
161
 
157
- /* The host bundle, probed in scr_tls.c's documented order. */
162
+ #ifdef _WIN32
163
+ /* Windows can restrict a certificate's purposes in a store property that is
164
+ * not part of its DER. CertGetEnhancedKeyUsage with flags 0 intersects that
165
+ * property with the encoded EKU extension. An empty result means either all
166
+ * purposes (CRYPT_E_NOT_FOUND) or no purposes (ERROR_SUCCESS); every other
167
+ * API failure is fail-closed too. */
168
+ bool scr_tls_ca_windows_cert_server_auth(const void *cert_context) {
169
+ PCCERT_CONTEXT cert = (PCCERT_CONTEXT)cert_context;
170
+ DWORD usage_len = 0;
171
+ if (!CertGetEnhancedKeyUsage(cert, 0, NULL, &usage_len) || usage_len == 0) {
172
+ return false;
173
+ }
174
+ CERT_ENHKEY_USAGE *usage = malloc((size_t)usage_len);
175
+ if (usage == NULL) scr_ca_oom();
176
+ if (!CertGetEnhancedKeyUsage(cert, 0, usage, &usage_len)) {
177
+ free(usage);
178
+ return false;
179
+ }
180
+ DWORD usage_error = GetLastError();
181
+ bool allowed = usage->cUsageIdentifier == 0 &&
182
+ usage_error == CRYPT_E_NOT_FOUND;
183
+ for (DWORD i = 0; !allowed && usage->rgpszUsageIdentifier != NULL &&
184
+ i < usage->cUsageIdentifier; i++) {
185
+ const char *oid = usage->rgpszUsageIdentifier[i];
186
+ allowed = oid != NULL &&
187
+ (strcmp(oid, szOID_PKIX_KP_SERVER_AUTH) == 0 ||
188
+ strcmp(oid, szOID_ANY_ENHANCED_KEY_USAGE) == 0);
189
+ }
190
+ free(usage);
191
+ return allowed;
192
+ }
193
+
194
+ static void scr_tls_ca_windows_certs_at(
195
+ DWORD location, const char *store_name, HCERTSTORE seen,
196
+ ScrTlsCaWindowsCertFn fn, void *ctx) {
197
+ HCERTSTORE store = CertOpenStore(
198
+ CERT_STORE_PROV_SYSTEM_A, 0, 0,
199
+ location | CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG,
200
+ store_name);
201
+ if (store == NULL) return;
202
+ PCCERT_CONTEXT cert = NULL;
203
+ /* CertEnumCertificatesInStore frees the previous context on every call,
204
+ * including the final NULL result. Each callback must copy DER it keeps. */
205
+ while ((cert = CertEnumCertificatesInStore(store, cert)) != NULL) {
206
+ /* The logical stores overlap (notably CurrentUser inherits machine
207
+ * roots). CERT_STORE_ADD_NEW turns the memory store into a process-local
208
+ * identity set: only the first allowed context reaches the consumer. */
209
+ if (scr_tls_ca_windows_cert_server_auth(cert) &&
210
+ CertAddCertificateContextToStore(
211
+ seen, cert, CERT_STORE_ADD_NEW, NULL)) {
212
+ fn(ctx, cert->pbCertEncoded, (size_t)cert->cbCertEncoded);
213
+ }
214
+ }
215
+ (void)CertCloseStore(store, 0);
216
+ }
217
+
218
+ void scr_tls_ca_windows_certs(ScrTlsCaWindowsCertFn fn, void *ctx) {
219
+ /* Match Node's Windows system-certificate sources: roots and intermediate
220
+ * CAs from all five applicable locations, plus explicitly trusted server
221
+ * certificates from the three local-machine TrustedPeople locations.
222
+ * A memory store deduplicates their overlapping contents. */
223
+ typedef struct ScrTlsCaWindowsStore {
224
+ DWORD location;
225
+ const char *name;
226
+ } ScrTlsCaWindowsStore;
227
+ static const ScrTlsCaWindowsStore stores[] = {
228
+ {CERT_SYSTEM_STORE_LOCAL_MACHINE, "ROOT"},
229
+ {CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY, "ROOT"},
230
+ {CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE, "ROOT"},
231
+ {CERT_SYSTEM_STORE_CURRENT_USER, "ROOT"},
232
+ {CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY, "ROOT"},
233
+ {CERT_SYSTEM_STORE_LOCAL_MACHINE, "CA"},
234
+ {CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY, "CA"},
235
+ {CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE, "CA"},
236
+ {CERT_SYSTEM_STORE_CURRENT_USER, "CA"},
237
+ {CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY, "CA"},
238
+ {CERT_SYSTEM_STORE_LOCAL_MACHINE, "TrustedPeople"},
239
+ {CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY, "TrustedPeople"},
240
+ {CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE, "TrustedPeople"},
241
+ };
242
+ HCERTSTORE seen = CertOpenStore(
243
+ CERT_STORE_PROV_MEMORY, 0, 0, CERT_STORE_CREATE_NEW_FLAG, NULL);
244
+ if (seen == NULL) return;
245
+ for (size_t i = 0; i < sizeof stores / sizeof stores[0]; i++) {
246
+ scr_tls_ca_windows_certs_at(
247
+ stores[i].location, stores[i].name, seen, fn, ctx);
248
+ }
249
+ (void)CertCloseStore(seen, 0);
250
+ }
251
+
252
+ static void scr_ca_push_windows_cert(void *ctx, const unsigned char *der, size_t len) {
253
+ if (len > UINT32_MAX) return;
254
+ DWORD pem_cap = 0;
255
+ DWORD flags = CRYPT_STRING_BASE64HEADER | CRYPT_STRING_NOCR;
256
+ if (!CryptBinaryToStringA(der, (DWORD)len, flags, NULL, &pem_cap) ||
257
+ pem_cap == 0) {
258
+ return;
259
+ }
260
+ char *pem = malloc((size_t)pem_cap);
261
+ if (pem == NULL) scr_ca_oom();
262
+ DWORD pem_len = pem_cap;
263
+ if (CryptBinaryToStringA(der, (DWORD)len, flags, pem, &pem_len)) {
264
+ scr_arr_push_ref((ScrArr *)ctx, scr_str_new(pem, (size_t)pem_len));
265
+ }
266
+ free(pem);
267
+ }
268
+ #endif
269
+
270
+ /* The host roots, from the Windows logical store or the POSIX bundle probe
271
+ * shared with scr_tls.c. */
158
272
  static ScrArr *scr_ca_load_host_bundle(void) {
159
273
  ScrArr *arr = scr_arr_new(SCR_ELEM_STR, 128);
274
+ #ifdef _WIN32
275
+ scr_tls_ca_windows_certs(scr_ca_push_windows_cert, arr);
276
+ #else
160
277
  static const char *const bundles[] = {
161
278
  "/etc/ssl/cert.pem", /* macOS, Alpine */
162
279
  "/etc/ssl/certs/ca-certificates.crt", /* Debian/Ubuntu */
@@ -171,6 +288,7 @@ static ScrArr *scr_ca_load_host_bundle(void) {
171
288
  free(text);
172
289
  break; /* first bundle wins, like the anchor probe */
173
290
  }
291
+ #endif
174
292
  return arr;
175
293
  }
176
294
 
package/vendor/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  - Commit: 49e671dd52ff6791295d8161ad3b6da7dc5f6f9d
7
7
  - License: ISC (copyright and permission notice reproduced in `src/scr_musl.c`)
8
8
 
9
- The x86_64 register-save/restore and `makecontext` logic is adapted into `src/scr_musl.c`, rather than carried as a separate library, to supply the legacy `ucontext` functions that musl declares but does not implement. It is compiled only for the explicit `x86_64-linux-musl` target and uses musl's public `ucontext_t` layout. The signal-mask compatibility wrapper is deliberately not included: scriptc fibers perform user-space register swaps and do not change a per-fiber signal mask.
9
+ The x86_64 and AArch64 register-save/restore and `makecontext` logic is adapted into `src/scr_musl.c`, rather than carried as a separate library, to supply the legacy `ucontext` functions that musl declares but does not implement. It is compiled for the explicit `x86_64-linux-musl` and `aarch64-linux-musl` targets and uses musl's public `ucontext_t` layouts. The signal-mask compatibility wrapper is deliberately not included: scriptc fibers perform user-space register swaps and do not change a per-fiber signal mask.
10
10
 
11
11
  ## ryu/
12
12