Skip to main content

max / makenotwork

server: bump jsonwebtoken to 10.4, on the rust_crypto provider 10.x makes the crypto backend pluggable and its default feature set enables NEITHER provider, so the crate compiles clean and then panics on the first sign or verify: "Could not automatically determine the process-level CryptoProvider". cargo check was clean while 14 of 20 synckit_auth tests failed, which would have shipped a server that panics on every sync login. Provider is rust_crypto (Max's call, 2026-07-22, standing preference for all projects): the same pure-Rust stack already backing sha2, hmac, and chacha20poly1305 here. The aws_lc_rs alternative was measured, not assumed — both are near-free on dependency count (rust_crypto nets +ed25519/-untrusted, and the rsa crate it pulls was already in the tree via apple-codesign and authenticode), so this is a stack preference rather than a cost tradeoff. Existing tokens keep working. A sync JWT built independently as raw HMAC-SHA256 over the base64url header/payload was pinned BEFORE the bump and still validates after, so nothing in the field is logged out on deploy; its exp is year-2100 so the vector will not rot, and it asserts a wrong secret is still rejected. 1890 lib + 1190 integration green, clippy clean at --all-targets --all-features. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-22 20:43 UTC
Commit: f6f693946dc4dad7d99c7bc2bfcb4c2fb22f88c7
Parent: 3935637
3 files changed, +60 insertions, -4 deletions
@@ -2277,10 +2277,12 @@ dependencies = [
2277 2277 "cfg-if",
2278 2278 "cpufeatures 0.2.17",
2279 2279 "curve25519-dalek-derive",
2280 + "digest 0.10.7",
2280 2281 "fiat-crypto",
2281 2282 "rand_core 0.6.4",
2282 2283 "rustc_version",
2283 2284 "subtle",
2285 + "zeroize",
2284 2286 ]
2285 2287
2286 2288 [[package]]
@@ -2646,6 +2648,30 @@ dependencies = [
2646 2648 ]
2647 2649
2648 2650 [[package]]
2651 + name = "ed25519"
2652 + version = "2.2.3"
2653 + source = "registry+https://github.com/rust-lang/crates.io-index"
2654 + checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
2655 + dependencies = [
2656 + "pkcs8",
2657 + "signature",
2658 + ]
2659 +
2660 + [[package]]
2661 + name = "ed25519-dalek"
2662 + version = "2.2.0"
2663 + source = "registry+https://github.com/rust-lang/crates.io-index"
2664 + checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
2665 + dependencies = [
2666 + "curve25519-dalek",
2667 + "ed25519",
2668 + "serde",
2669 + "sha2 0.10.9",
2670 + "subtle",
2671 + "zeroize",
2672 + ]
2673 +
2674 + [[package]]
2649 2675 name = "either"
2650 2676 version = "1.16.0"
2651 2677 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4011,17 +4037,26 @@ dependencies = [
4011 4037
4012 4038 [[package]]
4013 4039 name = "jsonwebtoken"
4014 - version = "9.3.1"
4040 + version = "10.4.0"
4015 4041 source = "registry+https://github.com/rust-lang/crates.io-index"
4016 - checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde"
4042 + checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc"
4017 4043 dependencies = [
4018 4044 "base64 0.22.1",
4045 + "ed25519-dalek",
4046 + "getrandom 0.2.17",
4047 + "hmac 0.12.1",
4019 4048 "js-sys",
4049 + "p256",
4050 + "p384",
4020 4051 "pem",
4021 - "ring",
4052 + "rand 0.8.6",
4053 + "rsa",
4022 4054 "serde",
4023 4055 "serde_json",
4056 + "sha2 0.10.9",
4057 + "signature",
4024 4058 "simple_asn1",
4059 + "zeroize",
4025 4060 ]
4026 4061
4027 4062 [[package]]
@@ -57,7 +57,7 @@ tower_governor = "0.8.0"
57 57 governor = "0.10.4"
58 58
59 59 # JWT (SyncKit)
60 - jsonwebtoken = "9.3.1"
60 + jsonwebtoken = { version = "10.4.0", features = ["rust_crypto"] }
61 61
62 62 # TOTP / 2FA
63 63 totp-rs = { version = "5.7", features = ["qr"] }
@@ -340,6 +340,27 @@ mod tests {
340 340 const TEST_SECRET: &str = "test-secret-key-for-synckit-jwt";
341 341 const TEST_KEY: &str = "test-key";
342 342
343 + /// A sync JWT minted before the jsonwebtoken 10 upgrade must keep
344 + /// validating, or every client in the field is logged out on deploy. The
345 + /// token is built independently (raw HMAC-SHA256 over the base64url
346 + /// header/payload), so it pins the wire format rather than whatever the
347 + /// crate happens to emit today. `exp` is year-2100 so the vector does not
348 + /// rot; `iat` is in the past, as the future-iat guard requires.
349 + #[test]
350 + fn pre_upgrade_token_still_validates() {
351 + const SECRET: &str = "known-answer-sync-secret";
352 + const TOKEN: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMTExMTExMS0xMTExLTQxMTEtODExMS0xMTExMTExMTExMTEiLCJhcHAiOiIyMjIyMjIyMi0yMjIyLTQyMjItODIyMi0yMjIyMjIyMjIyMjIiLCJrZXkiOiJzZGsta2V5LTEiLCJpc3MiOiJtYWtlbm90d29yay1zeW5ja2l0IiwiYXVkIjoibWFrZW5vdHdvcmstc3luY2tpdC1jbGllbnRzIiwiZXhwIjo0MTAyNDQ0ODAwLCJpYXQiOjE3MDAwMDAwMDB9.V5Iu9mkok7ryyPo_T2rQNo3jNi-i2Pq-xtuIIfVSttg";
353 +
354 + let claims = decode_sync_token(SECRET, TOKEN).expect("pre-upgrade token must validate");
355 + assert_eq!(claims.key, "sdk-key-1");
356 + assert_eq!(claims.iss, SYNCKIT_JWT_ISSUER);
357 + assert_eq!(claims.aud, SYNCKIT_JWT_AUDIENCE);
358 +
359 + // The vector must also prove the checks still bite, not just that
360 + // decoding succeeds: a wrong secret is rejected.
361 + assert!(decode_sync_token("not-the-secret", TOKEN).is_err());
362 + }
363 +
343 364 #[test]
344 365 fn oauth_access_token_round_trips_scope() {
345 366 let scopes = GrantedScopes::parse("profile:read perks:read");