@sproutboat/runtime 0.40.0 → 0.41.0
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/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/native-fetch-prelude.js +16 -7
- package/src/subtle/README.md +4 -4
- package/src/subtle/subtle.c +19 -7
- package/src/subtle/subtle.test.ts +1 -1
- package/src/transport-embedded.js +7 -2
- package/test/bearssl.ts +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @sproutboat/runtime
|
|
2
2
|
|
|
3
|
+
## 0.41.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 1eacf43: Secrets read from the environment (a standalone binary, a sproutd worker) are decoded as UTF-8 from the proposed compatibility date 2026-11-07. Before, the handler got the environment's bytes one char per byte, so `é` arrived as `é`. The broker transport is unchanged: its secrets already arrive as real strings. Bytes that are not valid UTF-8 pass through as before.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- f74835a: `crypto.subtle` signs and verifies P-256 (ES256 and ECDH) with BearSSL's 64-bit `ec_p256_m64` where the target has a 128-bit multiply, and keeps the portable code everywhere else (and for P-384 and P-521). BearSSL never picks m64 by itself, so the C asks for it with `br_ec_p256_m64_get()`, which returns null on a target without one. Through the runtime on macOS arm64, ES256 signs 2.2x faster (2,563 to 5,621 per second) and verifies 3.1x faster (1,318 to 4,050 per second). Results are unchanged, so there is no new compatibility date.
|
|
12
|
+
|
|
13
|
+
This needs the BearSSL that the CLI pins from the release that ships with it: m64 is not in BearSSL 0.6, so a CLI that still links 0.6 cannot link this runtime.
|
|
14
|
+
|
|
3
15
|
## 0.40.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -9397,6 +9397,14 @@ static const sbc_u8 sbc_oid_x25519[] = { 0x2b, 0x65, 0x6e };
|
|
|
9397
9397
|
static int sbc_oid_eq(const sbc_u8* a, size_t al, const sbc_u8* b, size_t bl) { return al == bl && memcmp(a, b, al) == 0; }
|
|
9398
9398
|
|
|
9399
9399
|
static int sbc_curve_br(int c) { return c == 1 ? BR_EC_secp256r1 : c == 2 ? BR_EC_secp384r1 : c == 3 ? BR_EC_secp521r1 : 0; }
|
|
9400
|
+
|
|
9401
|
+
static const br_ec_impl* sbc_ec(int br_curve) {
|
|
9402
|
+
if (br_curve == BR_EC_secp256r1) {
|
|
9403
|
+
const br_ec_impl* fast = br_ec_p256_m64_get();
|
|
9404
|
+
if (fast) return fast;
|
|
9405
|
+
}
|
|
9406
|
+
return br_ec_get_default();
|
|
9407
|
+
}
|
|
9400
9408
|
static int sbc_curve_from_br(int c) { return c == BR_EC_secp256r1 ? 1 : c == BR_EC_secp384r1 ? 2 : c == BR_EC_secp521r1 ? 3 : 0; }
|
|
9401
9409
|
static size_t sbc_curve_size(int c) { return c == 1 ? 32 : c == 2 ? 48 : c == 3 ? 66 : 0; }
|
|
9402
9410
|
static void sbc_curve_oid(int c, const sbc_u8** o, size_t* n) {
|
|
@@ -9579,7 +9587,7 @@ static int sbc_decode(int pkcs8, const sbc_u8* der, size_t len, sbc_buf* out) {
|
|
|
9579
9587
|
if (cv) {
|
|
9580
9588
|
sbc_u8 pub[133];
|
|
9581
9589
|
br_ec_public_key pk;
|
|
9582
|
-
if (br_ec_compute_pub(
|
|
9590
|
+
if (br_ec_compute_pub(sbc_ec(sk->curve), &pk, pub, sk) != 0) {
|
|
9583
9591
|
sbc_put1(out, SBC_K_EC); sbc_put1(out, (sbc_u8)cv); sbc_put1(out, 1);
|
|
9584
9592
|
sbc_field(out, pk.q, pk.qlen);
|
|
9585
9593
|
|
|
@@ -9862,7 +9870,7 @@ static int sbc_ecdsa_sign(int bits, const sbc_u8* key, size_t kl, const sbc_u8*
|
|
|
9862
9870
|
sbc_u8 h[64], sig[132];
|
|
9863
9871
|
rc = SBC_OPERATION;
|
|
9864
9872
|
if (sbc_digest(bits, m, ml, h)) {
|
|
9865
|
-
size_t n = br_ecdsa_sign_raw_get_default()(
|
|
9873
|
+
size_t n = br_ecdsa_sign_raw_get_default()(sbc_ec(sk->curve), sbc_hash(bits), h, sk, sig);
|
|
9866
9874
|
if (n) { sbc_put(out, sig, n); rc = SBC_OK; }
|
|
9867
9875
|
}
|
|
9868
9876
|
free(dc);
|
|
@@ -9877,7 +9885,7 @@ static int sbc_ecdsa_verify(int bits, int curve, const sbc_u8* point, size_t pl,
|
|
|
9877
9885
|
if (sl != 2 * fs) return SBC_OK;
|
|
9878
9886
|
br_ec_public_key pk;
|
|
9879
9887
|
pk.curve = sbc_curve_br(curve); pk.q = (sbc_u8*)point; pk.qlen = pl;
|
|
9880
|
-
if (br_ecdsa_vrfy_raw_get_default()(
|
|
9888
|
+
if (br_ecdsa_vrfy_raw_get_default()(sbc_ec(pk.curve), h, sbc_hlen(bits), &pk, sig, sl)) *ok = 1;
|
|
9881
9889
|
return SBC_OK;
|
|
9882
9890
|
}
|
|
9883
9891
|
|
|
@@ -9887,12 +9895,12 @@ static int sbc_ec_point_ok(int curve, const sbc_u8* point, size_t pl) {
|
|
|
9887
9895
|
sbc_u8 q[133];
|
|
9888
9896
|
memcpy(q, point, pl);
|
|
9889
9897
|
sbc_u8 one[1] = { 1 };
|
|
9890
|
-
return
|
|
9898
|
+
return sbc_ec(sbc_curve_br(curve))->mul(q, pl, one, 1, sbc_curve_br(curve)) != 0;
|
|
9891
9899
|
}
|
|
9892
9900
|
static int sbc_ec_keygen(int curve, sbc_buf* out) {
|
|
9893
9901
|
br_ec_private_key sk; br_ec_public_key pk;
|
|
9894
9902
|
sbc_u8 kb[BR_EC_KBUF_PRIV_MAX_SIZE], pb[BR_EC_KBUF_PUB_MAX_SIZE];
|
|
9895
|
-
const br_ec_impl* impl =
|
|
9903
|
+
const br_ec_impl* impl = sbc_ec(sbc_curve_br(curve));
|
|
9896
9904
|
if (!br_ec_keygen(&sbc_prng, impl, &sk, kb, sbc_curve_br(curve))) return SBC_OPERATION;
|
|
9897
9905
|
if (!br_ec_compute_pub(impl, &pk, pb, &sk)) return SBC_OPERATION;
|
|
9898
9906
|
size_t need = br_encode_ec_pkcs8_der(0, &sk, &pk);
|
|
@@ -10059,8 +10067,9 @@ static int sbc_ecdh(int curve, const sbc_u8* pkcs8, size_t kl, const sbc_u8* pee
|
|
|
10059
10067
|
if (sk->curve == sbc_curve_br(curve) && fs && pl == 1 + 2 * fs && peer[0] == 4) {
|
|
10060
10068
|
sbc_u8 pt[133];
|
|
10061
10069
|
memcpy(pt, peer, pl);
|
|
10062
|
-
|
|
10063
|
-
|
|
10070
|
+
const br_ec_impl* impl = sbc_ec(sk->curve);
|
|
10071
|
+
if (impl->mul(pt, pl, sk->x, sk->xlen, sk->curve)) {
|
|
10072
|
+
size_t xl; size_t xo = impl->xoff(sk->curve, &xl);
|
|
10064
10073
|
sbc_put(out, pt + xo, xl); rc = SBC_OK;
|
|
10065
10074
|
}
|
|
10066
10075
|
}
|
package/src/subtle/README.md
CHANGED
|
@@ -12,10 +12,10 @@ uses it.
|
|
|
12
12
|
|
|
13
13
|
| Need | Source | Licence |
|
|
14
14
|
| --- | --- | --- |
|
|
15
|
-
| SHA-1/256/384/512, HMAC, HKDF | BearSSL
|
|
16
|
-
| RSA (private and public operations, key generation, PKCS#1 v1.5 padding), RSA-OAEP | BearSSL
|
|
17
|
-
| ECDSA and ECDH on P-256, P-384, P-521, EC key generation | BearSSL
|
|
18
|
-
| AES (constant-time), CBC, CTR, GCM | BearSSL
|
|
15
|
+
| SHA-1/256/384/512, HMAC, HKDF | BearSSL (master 7bea48e) | MIT |
|
|
16
|
+
| RSA (private and public operations, key generation, PKCS#1 v1.5 padding), RSA-OAEP | BearSSL (master 7bea48e) | MIT |
|
|
17
|
+
| ECDSA and ECDH on P-256, P-384, P-521, EC key generation | BearSSL (master 7bea48e) | MIT |
|
|
18
|
+
| AES (constant-time), CBC, CTR, GCM | BearSSL (master 7bea48e) | MIT |
|
|
19
19
|
| Ed25519, X25519 | Monocypher 4.0.2, vendored in `vendor/` | BSD-2-Clause or CC0-1.0 |
|
|
20
20
|
|
|
21
21
|
BearSSL is already linked into every embedded sprout (standalone binaries and sproutd) for
|
package/src/subtle/subtle.c
CHANGED
|
@@ -183,6 +183,17 @@ static int sbc_oid_eq(const sbc_u8* a, size_t al, const sbc_u8* b, size_t bl) {
|
|
|
183
183
|
|
|
184
184
|
/* Curve codes used on the wire to JS: 1 = P-256, 2 = P-384, 3 = P-521. */
|
|
185
185
|
static int sbc_curve_br(int c) { return c == 1 ? BR_EC_secp256r1 : c == 2 ? BR_EC_secp384r1 : c == 3 ? BR_EC_secp521r1 : 0; }
|
|
186
|
+
/* BearSSL's default EC implementation is the portable m31 (m15 where there is no 64-bit multiply
|
|
187
|
+
result). P-256 has a 64-bit specialisation, m64, that is roughly 3x faster to sign and 5x to verify,
|
|
188
|
+
but BearSSL never selects it by itself. br_ec_p256_m64_get() returns null on a target without a
|
|
189
|
+
128-bit multiply, and m64 only knows P-256, so every other curve keeps the default. */
|
|
190
|
+
static const br_ec_impl* sbc_ec(int br_curve) {
|
|
191
|
+
if (br_curve == BR_EC_secp256r1) {
|
|
192
|
+
const br_ec_impl* fast = br_ec_p256_m64_get();
|
|
193
|
+
if (fast) return fast;
|
|
194
|
+
}
|
|
195
|
+
return br_ec_get_default();
|
|
196
|
+
}
|
|
186
197
|
static int sbc_curve_from_br(int c) { return c == BR_EC_secp256r1 ? 1 : c == BR_EC_secp384r1 ? 2 : c == BR_EC_secp521r1 ? 3 : 0; }
|
|
187
198
|
static size_t sbc_curve_size(int c) { return c == 1 ? 32 : c == 2 ? 48 : c == 3 ? 66 : 0; }
|
|
188
199
|
static void sbc_curve_oid(int c, const sbc_u8** o, size_t* n) {
|
|
@@ -380,7 +391,7 @@ static int sbc_decode(int pkcs8, const sbc_u8* der, size_t len, sbc_buf* out) {
|
|
|
380
391
|
if (cv) {
|
|
381
392
|
sbc_u8 pub[133];
|
|
382
393
|
br_ec_public_key pk;
|
|
383
|
-
if (br_ec_compute_pub(
|
|
394
|
+
if (br_ec_compute_pub(sbc_ec(sk->curve), &pk, pub, sk) != 0) {
|
|
384
395
|
sbc_put1(out, SBC_K_EC); sbc_put1(out, (sbc_u8)cv); sbc_put1(out, 1);
|
|
385
396
|
sbc_field(out, pk.q, pk.qlen);
|
|
386
397
|
/* The scalar, left padded to the field size as JWK d wants it. */
|
|
@@ -677,7 +688,7 @@ static int sbc_ecdsa_sign(int bits, const sbc_u8* key, size_t kl, const sbc_u8*
|
|
|
677
688
|
sbc_u8 h[64], sig[132];
|
|
678
689
|
rc = SBC_OPERATION;
|
|
679
690
|
if (sbc_digest(bits, m, ml, h)) {
|
|
680
|
-
size_t n = br_ecdsa_sign_raw_get_default()(
|
|
691
|
+
size_t n = br_ecdsa_sign_raw_get_default()(sbc_ec(sk->curve), sbc_hash(bits), h, sk, sig);
|
|
681
692
|
if (n) { sbc_put(out, sig, n); rc = SBC_OK; }
|
|
682
693
|
}
|
|
683
694
|
free(dc);
|
|
@@ -692,7 +703,7 @@ static int sbc_ecdsa_verify(int bits, int curve, const sbc_u8* point, size_t pl,
|
|
|
692
703
|
if (sl != 2 * fs) return SBC_OK;
|
|
693
704
|
br_ec_public_key pk;
|
|
694
705
|
pk.curve = sbc_curve_br(curve); pk.q = (sbc_u8*)point; pk.qlen = pl;
|
|
695
|
-
if (br_ecdsa_vrfy_raw_get_default()(
|
|
706
|
+
if (br_ecdsa_vrfy_raw_get_default()(sbc_ec(pk.curve), h, sbc_hlen(bits), &pk, sig, sl)) *ok = 1;
|
|
696
707
|
return SBC_OK;
|
|
697
708
|
}
|
|
698
709
|
/* A public point is valid when it is on the curve (the group is prime order, cofactor 1). */
|
|
@@ -702,12 +713,12 @@ static int sbc_ec_point_ok(int curve, const sbc_u8* point, size_t pl) {
|
|
|
702
713
|
sbc_u8 q[133];
|
|
703
714
|
memcpy(q, point, pl);
|
|
704
715
|
sbc_u8 one[1] = { 1 };
|
|
705
|
-
return
|
|
716
|
+
return sbc_ec(sbc_curve_br(curve))->mul(q, pl, one, 1, sbc_curve_br(curve)) != 0;
|
|
706
717
|
}
|
|
707
718
|
static int sbc_ec_keygen(int curve, sbc_buf* out) {
|
|
708
719
|
br_ec_private_key sk; br_ec_public_key pk;
|
|
709
720
|
sbc_u8 kb[BR_EC_KBUF_PRIV_MAX_SIZE], pb[BR_EC_KBUF_PUB_MAX_SIZE];
|
|
710
|
-
const br_ec_impl* impl =
|
|
721
|
+
const br_ec_impl* impl = sbc_ec(sbc_curve_br(curve));
|
|
711
722
|
if (!br_ec_keygen(&sbc_prng, impl, &sk, kb, sbc_curve_br(curve))) return SBC_OPERATION;
|
|
712
723
|
if (!br_ec_compute_pub(impl, &pk, pb, &sk)) return SBC_OPERATION;
|
|
713
724
|
size_t need = br_encode_ec_pkcs8_der(0, &sk, &pk);
|
|
@@ -885,8 +896,9 @@ static int sbc_ecdh(int curve, const sbc_u8* pkcs8, size_t kl, const sbc_u8* pee
|
|
|
885
896
|
if (sk->curve == sbc_curve_br(curve) && fs && pl == 1 + 2 * fs && peer[0] == 4) {
|
|
886
897
|
sbc_u8 pt[133];
|
|
887
898
|
memcpy(pt, peer, pl);
|
|
888
|
-
|
|
889
|
-
|
|
899
|
+
const br_ec_impl* impl = sbc_ec(sk->curve);
|
|
900
|
+
if (impl->mul(pt, pl, sk->x, sk->xlen, sk->curve)) {
|
|
901
|
+
size_t xl; size_t xo = impl->xoff(sk->curve, &xl);
|
|
890
902
|
sbc_put(out, pt + xo, xl); rc = SBC_OK;
|
|
891
903
|
}
|
|
892
904
|
}
|
|
@@ -9,7 +9,7 @@ import { assembleSubtleC } from "./assemble";
|
|
|
9
9
|
|
|
10
10
|
const bear = join(
|
|
11
11
|
process.env.SPROUTBOAT_TOOLCHAIN_CACHE ?? join(homedir(), ".cache/sproutboat"),
|
|
12
|
-
"bearssl-
|
|
12
|
+
"bearssl-7bea48e/BearSSL-7bea48e",
|
|
13
13
|
);
|
|
14
14
|
const lib = join(bear, "build/libbearssl.a");
|
|
15
15
|
// SUBTLE_DRIVER: a prebuilt driver binary (a machine with no C compiler, or a cross-built one).
|
|
@@ -4962,8 +4962,13 @@ function __sbEmbeddedDispatch(msg) {
|
|
|
4962
4962
|
if (op === "secret.get") {
|
|
4963
4963
|
// Secrets reach a standalone binary through the environment, the same
|
|
4964
4964
|
// channel the launcher and the supervisor use; there is nothing to decrypt.
|
|
4965
|
-
const
|
|
4966
|
-
if (!
|
|
4965
|
+
const raw = __sbEnv(String(msg.name));
|
|
4966
|
+
if (!raw) throw new Error("secret not set: " + msg.name);
|
|
4967
|
+
// __sbEnv hands back the environment's bytes as a bytestring, one char per byte, so a UTF-8 secret ("é" is
|
|
4968
|
+
// C3 A9) reached the app as "é". From compatibility date 2026-11-07 it is decoded as UTF-8, as workerd's
|
|
4969
|
+
// secrets are text. Bytes that are not valid UTF-8 pass through unchanged (__sbFromUtf8's rule). The broker
|
|
4970
|
+
// transport is not touched: its secrets arrive as JSON, already real strings.
|
|
4971
|
+
const value = __sbCompatDate() >= "2026-11-07" ? __sbFromUtf8(raw) : raw;
|
|
4967
4972
|
return { ok: true, value };
|
|
4968
4973
|
}
|
|
4969
4974
|
|
package/test/bearssl.ts
CHANGED
|
@@ -7,9 +7,10 @@ import { join, resolve } from "node:path";
|
|
|
7
7
|
// The prelude carries crypto.subtle's native half on every transport, so a test that compiles a
|
|
8
8
|
// handler using crypto.subtle needs BearSSL on the include path and link line, as the CLI arranges
|
|
9
9
|
// for a build. Same pinned source as the CLI's src/bearssl.ts; built once into the toolchain cache.
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
10
|
+
const COMMIT = "7bea48e5e850ab4cafbe68d3765cdaba13a86d6f";
|
|
11
|
+
const VERSION = COMMIT.slice(0, 7);
|
|
12
|
+
const URL_ = `https://bearssl.org/gitweb/?p=BearSSL;a=snapshot;h=${COMMIT};sf=tgz`;
|
|
13
|
+
const SHA256 = "ed397832b1613582737d9c1ce01c1af74ad6a9e9c52a341e24433987cda0f372";
|
|
13
14
|
|
|
14
15
|
const walk = async (dir: string): Promise<string[]> =>
|
|
15
16
|
(
|
|
@@ -28,7 +29,7 @@ export async function bearsslEnv(): Promise<{ SB_EXTRA_CFLAGS: string; SB_EXTRA_
|
|
|
28
29
|
process.env.SPROUTBOAT_TOOLCHAIN_CACHE ?? join(homedir(), ".cache/sproutboat"),
|
|
29
30
|
`bearssl-${VERSION}-test`,
|
|
30
31
|
);
|
|
31
|
-
const tree = join(root, `
|
|
32
|
+
const tree = join(root, `BearSSL-${VERSION}`);
|
|
32
33
|
const objects = join(root, "obj");
|
|
33
34
|
const marker = join(objects, ".complete");
|
|
34
35
|
if (!existsSync(marker)) {
|