From 45f1e19232b363ff10030cffafd8314a925e8933 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 03:22:00 +0000 Subject: [PATCH 1/4] fix(deps): patch rustls-webpki, webpki-roots and wasmtime for four advisories rustls-webpki 0.103.15 -> 0.103.16 (RUSTSEC-2026-0382) and webpki-roots 1.0.8 -> 1.0.11 (RUSTSEC-2026-0381) in both lockfiles, the workspace and crates/cluster. wasmtime 49.0.3 -> 49.0.4 (RUSTSEC-2026-0378, RUSTSEC-2026-0379) in the workspace lockfile only, staying on 49.x; the cranelift and pulley crates move with it from 0.136.3 / 49.0.3 to 0.136.4 / 49.0.4. Lockfile-only: no manifest changes, no new crate. crypto-bigint 0.5.5 (RUSTSEC-2026-0387) is the fifth advisory and needs the p256 0.14 migration in the next commit. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HKcmLyFYX5vcY81ze4tHQ1 --- Cargo.lock | 122 +++++++++++++++++++------------------- crates/cluster/Cargo.lock | 8 +-- 2 files changed, 65 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f585755..b7f5b2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1138,27 +1138,27 @@ dependencies = [ [[package]] name = "cranelift-assembler-x64" -version = "0.136.3" +version = "0.136.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d504c8c7ba3f3accaf8e98b7096beb98e1a271404357744c7e9e730c6865ff" +checksum = "c9c587da2696fbaa58e7a2fa2e5bfdd365d0928219f36992304ac1f15e5bc254" dependencies = [ "cranelift-assembler-x64-meta", ] [[package]] name = "cranelift-assembler-x64-meta" -version = "0.136.3" +version = "0.136.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c065757905596bc06e82969ebd7b92d691b7fd710a7e9e8181ae0c30dab588d0" +checksum = "f28a60027b7556b680b051651291053ec381edf4ffbc01784997ae51a64269bb" dependencies = [ "cranelift-srcgen", ] [[package]] name = "cranelift-bforest" -version = "0.136.3" +version = "0.136.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca7162cd4d4fc5f9f2f88d1459d939882203d93a8c5a19ef2c405b2e82ef13f" +checksum = "62bfdb846acebfd591d601994ea283c98d525ac0bc9a84ca3f2c36bcf5b69ff1" dependencies = [ "cranelift-entity", "wasmtime-internal-core", @@ -1166,9 +1166,9 @@ dependencies = [ [[package]] name = "cranelift-bitset" -version = "0.136.3" +version = "0.136.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec2ba2d4f3d959ec4023a10266c3dcfb28381d1938d507a9a5e1abb78bbcc88" +checksum = "aa9f8cc1080739d5f72b4ee7e00aff7d589f1284beaf0a19472c07878be7f299" dependencies = [ "serde", "serde_derive", @@ -1177,9 +1177,9 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.136.3" +version = "0.136.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51a64cb821d5fed56bdd6053acd36fa928cd82b70bf6b19b8b14b8b27d047b9a" +checksum = "d85417d493ecec833b5134546b4a252c78cc0bd5be11729a35d25dcff9e4e70b" dependencies = [ "bumpalo", "cranelift-assembler-x64", @@ -1208,9 +1208,9 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.136.3" +version = "0.136.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142be1e267a7497ba3c2b5a829eaf5387b92b9bcdba6e0a6020f1ffccae7f2b8" +checksum = "d01c8ed0098360bae69ad6b82952d4247516799ce8050d45ad168d6689f4eae6" dependencies = [ "cranelift-assembler-x64-meta", "cranelift-codegen-shared", @@ -1221,24 +1221,24 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.136.3" +version = "0.136.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e41f75410da6cce8601330137d73718f679db81cb790a33de3871b5c90a0de3" +checksum = "04f9b86d809cec9c4e88b201f1b3ed3b0347674c9ccfbb8c355228669e34be81" [[package]] name = "cranelift-control" -version = "0.136.3" +version = "0.136.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2616c6d01c14921ac9289ef65eb7702d9c760dfdc4537a8ee5bf38ea2832c6" +checksum = "ce182038fb10b719841e65fdf5deeee258c684f63439f5be680c2be059274b58" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.136.3" +version = "0.136.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a03e8b44c4877cf2fb21e5788d514af6fd291a066ed9ae37adf114a25fecb4aa" +checksum = "7bca812b03a7b942796391f25bc02aa70a1a007fee3ce2756490c788429e9c39" dependencies = [ "cranelift-bitset", "serde", @@ -1248,9 +1248,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.136.3" +version = "0.136.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "366ff7cb014bd08752210eba55a650969f8db9b52670d69d2ea0b23e1e9e3edf" +checksum = "a0c30563d2aef95e8d1a00a920199c99117b350339599f0e9487a7a1cf5d59de" dependencies = [ "cranelift-codegen", "hashbrown 0.17.1", @@ -1261,15 +1261,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.136.3" +version = "0.136.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea6526ebf9064fff8ce3b1e26afad7027f9e6bb0e45d2b9200e4cbab8343345" +checksum = "9c54184333a275698edb1d3bdff226131a25d47133d792aa716e47e32b8b7f05" [[package]] name = "cranelift-native" -version = "0.136.3" +version = "0.136.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b44d323f3e5108a8a5bc61f3acd227285ece066e19afe9aece211d66b34a64ea" +checksum = "eb67a4c8a3180b5f1d77dffcd6317c05386ced42e5a31c7004523a9d99dc85c1" dependencies = [ "cranelift-codegen", "libc", @@ -1278,9 +1278,9 @@ dependencies = [ [[package]] name = "cranelift-srcgen" -version = "0.136.3" +version = "0.136.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc173168f291ff3b95b5d0769d6a05b1eaef5e6d74c2600affab8396b4e7b420" +checksum = "b533db1679b4b728568bd490da6533cf50974c48a4d7534e78f1e0e02bc39769" [[package]] name = "crc32fast" @@ -2413,7 +2413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3024,7 +3024,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.8", + "webpki-roots 1.0.11", ] [[package]] @@ -4545,9 +4545,9 @@ dependencies = [ [[package]] name = "pulley-interpreter" -version = "49.0.3" +version = "49.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def59f28ccaf2cb4f8dfaac83197fd7d56a4470a70e4f2589454c731370101c5" +checksum = "5cd3de43f4f4cff536de8902097edbdf749321b97dda548f36a37918eb85bba3" dependencies = [ "cranelift-bitset", "log", @@ -4557,9 +4557,9 @@ dependencies = [ [[package]] name = "pulley-macros" -version = "49.0.3" +version = "49.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1834d3b3ef994f266a0c9ed29a8214f2c2faaa9b1c44babfa57566bb05af83" +checksum = "8319a8fa5269b4895957f8795861c42822bd26218e495f70060a963323d94847" dependencies = [ "proc-macro2", "quote", @@ -5089,7 +5089,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.8", + "webpki-roots 1.0.11", ] [[package]] @@ -5199,7 +5199,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5239,9 +5239,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.15" +version = "0.103.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" dependencies = [ "aws-lc-rs", "ring", @@ -5765,7 +5765,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -6731,9 +6731,9 @@ dependencies = [ [[package]] name = "wasmtime" -version = "49.0.3" +version = "49.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fbdd39f5c66f63ba6b5b31391b324b7ade7237d29c866cdb561330118ef436e" +checksum = "36abb25de5518707e5ba96b586366b09db168d2d4976cb9dac57265a317497ea" dependencies = [ "addr2line", "async-trait", @@ -6769,9 +6769,9 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "49.0.3" +version = "49.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e915c27826de4f7b4411b61fd6db32ca448657a5136b8c8e8874a15e90cecbf4" +checksum = "92f16a4d09fb1fbf2dcfeddb9818e1517a258e13b505b3bfdadcf47a19bf31ca" dependencies = [ "anyhow", "cpp_demangle", @@ -6800,15 +6800,15 @@ dependencies = [ [[package]] name = "wasmtime-internal-component-util" -version = "49.0.3" +version = "49.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e754a3ff5690c34eb4713bd7c65f60dd607ead4e39042cbf52ac8011de14455b" +checksum = "4006009fc3598af4e895ab18c6c33d2c6b7c657b1bd0a755ca7ee1abef6f0c16" [[package]] name = "wasmtime-internal-core" -version = "49.0.3" +version = "49.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba00c5db77adb0749ee059068e5a417bfac3d8969f5a3479d9bd1ca8c1ba248d" +checksum = "947a732ad659f42c10ec639706fc8cb1f068dd9a55eebde8a1f24dfd06dfa525" dependencies = [ "hashbrown 0.17.1", "libm", @@ -6817,9 +6817,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-cranelift" -version = "49.0.3" +version = "49.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ceeb4325712a253f463e50a69b5a547fbbecd52e45612360c543eaa25a03ff" +checksum = "fbfb8b455d524939e03df42e3e749f8e26e26ba2acd8b08fa00b3bda55686750" dependencies = [ "cranelift-codegen", "cranelift-control", @@ -6843,9 +6843,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-fiber" -version = "49.0.3" +version = "49.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb3f21bc32b5382ea95f83ce9a5f2f62b68f1b25a37d0802a41a65236eae5ae" +checksum = "bd8aa8711f68b2cdd94c2b85ba5c8317edaa1ac4ee0a0534b957123680239307" dependencies = [ "cc", "libc", @@ -6857,9 +6857,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-debug" -version = "49.0.3" +version = "49.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e786f9925126e0d2e466c5308e572e8b6226f4047596228a3d63d99528a78949" +checksum = "e873e4504faf50aa8ce255d6ea7210d87b39d5f379c2486782e2c5f179c70ca8" dependencies = [ "cc", "wasmtime-internal-versioned-export-macros", @@ -6867,9 +6867,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-icache-coherence" -version = "49.0.3" +version = "49.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92cb1dd5da78f5b21d65e91063bdd8c2eb343c1f077a1dcbc29003d4a794abb0" +checksum = "197ab1fda5da958a5c0b8a97eb241beafc48ac568b82ef4f12f38d6866c74173" dependencies = [ "libc", "wasmtime-internal-core", @@ -6878,9 +6878,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-unwinder" -version = "49.0.3" +version = "49.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0243d244f6dd07ccd114e6bb099e8dd1197b47de5c054576adc9483f80f65d8" +checksum = "5577f3a5a979a7c3b1b23c1e9f13e70a8583acc8828471ec56f282117fc4952b" dependencies = [ "cranelift-codegen", "log", @@ -6890,9 +6890,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-versioned-export-macros" -version = "49.0.3" +version = "49.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20fa5616cd875ec78f5b92020fa47ced7c8bb923079185c8475751098b8c4a69" +checksum = "f6399c9c4050ed431cf66e21c05fadd6f9731db44476f452f8c7142b4597b883" dependencies = [ "proc-macro2", "quote", @@ -6947,14 +6947,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.8", + "webpki-roots 1.0.11", ] [[package]] name = "webpki-roots" -version = "1.0.8" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" +checksum = "f5f280bbb2b287bc8e7de7e3852945a0ff50d1bbcb5ed42b6c7a568c546df171" dependencies = [ "rustls-pki-types", ] @@ -7059,7 +7059,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/crates/cluster/Cargo.lock b/crates/cluster/Cargo.lock index 33ff43f..79287c0 100644 --- a/crates/cluster/Cargo.lock +++ b/crates/cluster/Cargo.lock @@ -1504,9 +1504,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.15" +version = "0.103.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" dependencies = [ "aws-lc-rs", "ring", @@ -2198,9 +2198,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.8" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" +checksum = "f5f280bbb2b287bc8e7de7e3852945a0ff50d1bbcb5ed42b6c7a568c546df171" dependencies = [ "rustls-pki-types", ] From b6a35fffe6977c8e8dd3598031d6646920928fcc Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 03:22:36 +0000 Subject: [PATCH 2/4] fix(deps): move p256 to 0.14, dropping crypto-bigint 0.5.5 (RUSTSEC-2026-0387) p256 0.13 is pulled only by this workspace's own crates (cloud, delegation, dpop, gateway). Moving all six manifest lines to 0.14 brings elliptic-curve 0.14.3, ecdsa 0.17.0, pkcs8 0.11, sec1 0.8, signature 3.0 and rfc6979 0.6, and replaces crypto-bigint 0.5.5 with 0.7.9, so `cargo tree -i crypto-bigint --all-features --target all` no longer shows a 0.5.x. The code changes are two renames, nothing else: - `VerifyingKey::to_encoded_point(compress)` is `to_sec1_point(compress)` in ecdsa 0.17; `x()`, `y()` and `as_bytes()` on the result are unchanged. - `SigningKey::random(&mut OsRng)` is deprecated, and rand_core is 0.10 with no `OsRng`; the test fixtures that made a random key now call `::generate()`. Production key generation (gateway checkpoints) already drew bytes with getrandom 0.2 and `SigningKey::from_slice`, which is unchanged. `Signature::from_slice`, `to_bytes`, `VerifyingKey::from_sec1_bytes`, `SecretKey::from_pkcs8_pem` / `from_sec1_pem` / `from_pkcs8_der` / `from_sec1_der` keep their names and signatures. P-256 keeps `NORMALIZE_S = false`, so a high-S signature still verifies and signing still does not normalise s, as in 0.13. One rustfmt collapse in an audit_sign test follows the shorter name; the assertion is the same. The gateway manifest comment on hmac is corrected: hmac 0.12 reached the lockfile through p256 0.13's rfc6979, and since 0.14 (which uses hmac 0.13) the gateway's own line is its only edge. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HKcmLyFYX5vcY81ze4tHQ1 --- Cargo.lock | 287 +++++++++++++----- crates/cloud/Cargo.toml | 4 +- crates/cloud/src/audit_keyryx.rs | 4 +- crates/cloud/src/audit_keyryx/tests.rs | 4 +- crates/cloud/src/audit_sign.rs | 9 +- crates/cloud/src/devices.rs | 2 +- .../cloud/tests/it/audit_manifest_keyryx.rs | 4 +- crates/cloud/tests/it/device_tokens.rs | 2 +- crates/cloud/tests/it/pairing.rs | 2 +- crates/delegation/Cargo.toml | 4 +- crates/delegation/src/testing.rs | 4 +- crates/dpop/Cargo.toml | 2 +- crates/dpop/src/lib.rs | 6 +- crates/gateway/Cargo.toml | 10 +- crates/gateway/src/checkpoints.rs | 2 +- crates/gateway/tests/it/mcp_broker.rs | 4 +- crates/gateway/tests/it/mcp_door.rs | 4 +- 17 files changed, 245 insertions(+), 109 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b7f5b2e..85c49dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,7 +136,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -147,7 +147,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -645,9 +645,9 @@ dependencies = [ [[package]] name = "base16ct" -version = "0.2.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" [[package]] name = "base64" @@ -749,6 +749,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "borsh" version = "1.7.0" @@ -989,6 +998,12 @@ dependencies = [ "cc", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "cobs" version = "0.3.0" @@ -1050,9 +1065,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.6" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] name = "const-random" @@ -1118,6 +1133,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1200,7 +1221,7 @@ dependencies = [ "rustc-hash", "serde", "serde_derive", - "sha2", + "sha2 0.10.9", "smallvec", "target-lexicon", "wasmtime-internal-core", @@ -1357,12 +1378,16 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-bigint" -version = "0.5.5" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +checksum = "d811ea23fdfbcce2e2d5132344d471abdb8f901ac496a0893a3df92e70525e52" dependencies = [ - "generic-array", - "rand_core 0.6.4", + "cpubits", + "ctutils", + "getrandom 0.4.3", + "hybrid-array", + "num-traits", + "rand_core 0.10.1", "subtle", "zeroize", ] @@ -1377,6 +1402,17 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "getrandom 0.4.3", + "hybrid-array", + "rand_core 0.10.1", +] + [[package]] name = "csscolorparser" version = "0.6.2" @@ -1408,6 +1444,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctutils" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe3e9d21d39e6f39ff46e03d5b621424f88ee23d8a1672c6bfc9ccc7f2da4f6a" +dependencies = [ + "cmov", + "subtle", +] + [[package]] name = "darling" version = "0.20.11" @@ -2159,9 +2205,9 @@ checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" [[package]] name = "der" -version = "0.7.10" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +checksum = "6a996a108feb8b427a6e12fcf6af4aa83409b79d72c15337d7741368131ee004" dependencies = [ "const-oid", "pem-rfc7468", @@ -2254,12 +2300,23 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid", + "crypto-common 0.2.2", + "ctutils", +] + [[package]] name = "dirs" version = "6.0.0" @@ -2278,7 +2335,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2315,16 +2372,17 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "ecdsa" -version = "0.16.9" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0" dependencies = [ "der", - "digest", + "digest 0.11.3", "elliptic-curve", "rfc6979", - "signature", + "signature 3.0.0", "spki", + "zeroize", ] [[package]] @@ -2335,19 +2393,20 @@ checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "elliptic-curve" -version = "0.13.8" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +checksum = "900f37e7e18d74a2347389af6cd69e3a30d5cc9e697f40004865679aa75e9c7c" dependencies = [ "base16ct", "crypto-bigint", - "digest", + "crypto-common 0.2.2", + "digest 0.11.3", "ff", - "generic-array", "group", + "hybrid-array", "pem-rfc7468", "pkcs8", - "rand_core 0.6.4", + "rand_core 0.10.1", "sec1", "subtle", "zeroize", @@ -2413,7 +2472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2502,11 +2561,11 @@ dependencies = [ [[package]] name = "ff" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f" dependencies = [ - "rand_core 0.6.4", + "rand_core 0.10.1", "subtle", ] @@ -2736,7 +2795,6 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] @@ -2808,12 +2866,12 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "group" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4" dependencies = [ "ff", - "rand_core 0.6.4", + "rand_core 0.10.1", "subtle", ] @@ -2935,7 +2993,16 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", +] + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", ] [[package]] @@ -2989,6 +3056,17 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "subtle", + "typenum", + "zeroize", +] + [[package]] name = "hyper" version = "1.10.1" @@ -3369,7 +3447,7 @@ dependencies = [ "pem", "serde", "serde_json", - "signature", + "signature 2.2.0", "simple_asn1", "zeroize", ] @@ -3832,7 +3910,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4111,21 +4189,22 @@ checksum = "c41d7757331aef2d04b9cb09b45583a59217628beaf91895b7e76187b6e8c088" dependencies = [ "flate2", "pkg-config", - "sha2", + "sha2 0.10.9", "tar", "ureq", ] [[package]] name = "p256" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +checksum = "d2c9239b2dbc807adbbe147e8cf72ea7450c3a0aabe62cb8e75ff4ec22e1f72a" dependencies = [ "ecdsa", "elliptic-curve", + "primefield", "primeorder", - "sha2", + "sha2 0.11.1", ] [[package]] @@ -4233,9 +4312,9 @@ dependencies = [ [[package]] name = "pem-rfc7468" -version = "0.7.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" dependencies = [ "base64ct", ] @@ -4286,7 +4365,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" dependencies = [ "pest", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -4399,9 +4478,9 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkcs8" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" dependencies = [ "der", "spki", @@ -4477,13 +4556,31 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primefield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4" +dependencies = [ + "crypto-bigint", + "crypto-common 0.2.2", + "ff", + "rand_core 0.10.1", + "subtle", + "zeroize", +] + [[package]] name = "primeorder" -version = "0.13.6" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06" dependencies = [ "elliptic-curve", + "once_cell", + "primefield", + "serdect", + "wnaf", ] [[package]] @@ -5094,12 +5191,12 @@ dependencies = [ [[package]] name = "rfc6979" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b" dependencies = [ - "hmac", - "subtle", + "crypto-bigint", + "hmac 0.13.0", ] [[package]] @@ -5199,7 +5296,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5305,14 +5402,14 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "sec1" -version = "0.7.3" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" dependencies = [ "base16ct", + "ctutils", "der", - "generic-array", - "pkcs8", + "hybrid-array", "subtle", "zeroize", ] @@ -5422,6 +5519,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serdect" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "sha2" version = "0.10.9" @@ -5430,7 +5537,18 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", ] [[package]] @@ -5485,10 +5603,19 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", "rand_core 0.6.4", ] +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "digest 0.11.3", + "rand_core 0.10.1", +] + [[package]] name = "simd-adler32" version = "0.3.9" @@ -5556,7 +5683,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5572,9 +5699,9 @@ dependencies = [ [[package]] name = "spki" -version = "0.7.3" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +checksum = "7ef958a98b9d5da290cfc78946e9f3e61e1e62a18db0d92cac0b83cc161491a9" dependencies = [ "base64ct", "der", @@ -5765,7 +5892,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5787,7 +5914,7 @@ dependencies = [ "parking_lot", "rustix", "signal-hook", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5836,7 +5963,7 @@ dependencies = [ "pest", "pest_derive", "phf 0.11.3", - "sha2", + "sha2 0.10.9", "signal-hook", "siphasher", "terminfo", @@ -5999,7 +6126,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "tokenfuse-core", "tokenfuse-delegation", "tokenfuse-dpop", @@ -6037,7 +6164,7 @@ dependencies = [ "regex", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "thiserror 2.0.18", ] @@ -6050,7 +6177,7 @@ dependencies = [ "p256", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "tokenfuse-dpop", ] @@ -6062,7 +6189,7 @@ dependencies = [ "jsonwebtoken", "p256", "serde_json", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -6079,14 +6206,14 @@ dependencies = [ "fastembed", "futures", "getrandom 0.2.17", - "hmac", + "hmac 0.12.1", "http-body-util", "p256", "ratatui", "reqwest", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "subtle", "thiserror 2.0.18", "tokenfuse-cluster", @@ -6788,7 +6915,7 @@ dependencies = [ "semver", "serde", "serde_derive", - "sha2", + "sha2 0.10.9", "smallvec", "target-lexicon", "wasm-encoder 0.258.3", @@ -6983,7 +7110,7 @@ checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" dependencies = [ "getrandom 0.3.4", "mac_address", - "sha2", + "sha2 0.10.9", "thiserror 1.0.69", "uuid", ] @@ -7059,7 +7186,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -7318,6 +7445,18 @@ version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +[[package]] +name = "wnaf" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "795ca18b3fdb5e62bf982199278341ddcf7ebf7d32e25e212ad05d496e95f6fa" +dependencies = [ + "ff", + "group", + "hybrid-array", + "primefield", +] + [[package]] name = "writeable" version = "0.6.3" diff --git a/crates/cloud/Cargo.toml b/crates/cloud/Cargo.toml index b67fe77..da92bfb 100644 --- a/crates/cloud/Cargo.toml +++ b/crates/cloud/Cargo.toml @@ -27,7 +27,7 @@ utoipa = { version = "5", features = ["axum_extras"] } # Device pairing + hardware-backed signed mutations, and the signed audit # manifest (ES256 = ECDSA P-256/SHA-256). `pkcs8`/`pem` let the audit signing # key load from a PKCS#8/SEC1 PEM or DER (see `audit_sign.rs`). -p256 = { version = "0.13", features = ["ecdsa", "pkcs8", "pem"] } +p256 = { version = "0.14", features = ["ecdsa", "pkcs8", "pem"] } sha2.workspace = true base64.workspace = true getrandom = "0.2" @@ -50,7 +50,7 @@ http-body-util = "0.1" # The signed-audit-manifest tests re-derive the canonical bytes (core) and # verify the ES256 signature independently (p256), exactly as an auditor would. tokenfuse-core.workspace = true -p256 = { version = "0.13", features = ["ecdsa"] } +p256 = { version = "0.14", features = ["ecdsa"] } base64 = "0.22" [features] diff --git a/crates/cloud/src/audit_keyryx.rs b/crates/cloud/src/audit_keyryx.rs index f97e9dd..cbc6f5a 100644 --- a/crates/cloud/src/audit_keyryx.rs +++ b/crates/cloud/src/audit_keyryx.rs @@ -456,14 +456,14 @@ fn verify_answer(raw: &[u8], bytes: &[u8]) -> Result Ok(KeyryxSignature { kid: kid.to_string(), signature_b64: STANDARD.encode(sig.to_bytes()), - public_key_b64: STANDARD.encode(public.to_encoded_point(false).as_bytes()), + public_key_b64: STANDARD.encode(public.to_sec1_point(false).as_bytes()), }) } /// The RFC 7638 thumbprint of a P-256 key, by the one function this estate /// names a key with ([`tokenfuse_dpop::thumbprint_of_json`]). fn thumbprint(public: &VerifyingKey) -> Result { - let point = public.to_encoded_point(false); + let point = public.to_sec1_point(false); let (Some(x), Some(y)) = (point.x(), point.y()) else { return Err(NotSigned::same( "keyryx's answer carries no P-256 public key", diff --git a/crates/cloud/src/audit_keyryx/tests.rs b/crates/cloud/src/audit_keyryx/tests.rs index a0876e3..7188066 100644 --- a/crates/cloud/src/audit_keyryx/tests.rs +++ b/crates/cloud/src/audit_keyryx/tests.rs @@ -55,7 +55,7 @@ fn answer_from(key: &SigningKey, kid: &str, alg: &str, bytes: &[u8], compressed: "alg": alg, "kid": kid, "signature_b64": STANDARD.encode(sig.to_bytes()), - "public_key_b64": STANDARD.encode(key.verifying_key().to_encoded_point(compressed).as_bytes()), + "public_key_b64": STANDARD.encode(key.verifying_key().to_sec1_point(compressed).as_bytes()), }) .to_string() .into_bytes() @@ -282,7 +282,7 @@ fn a_public_key_that_did_not_sign_is_refused() { let mut v: serde_json::Value = serde_json::from_slice(&answer("ES256", bytes, false)).unwrap(); let other = SigningKey::from_slice(&[0x22u8; 32]).unwrap(); v["public_key_b64"] = STANDARD - .encode(other.verifying_key().to_encoded_point(false).as_bytes()) + .encode(other.verifying_key().to_sec1_point(false).as_bytes()) .into(); let why = verify_answer(v.to_string().as_bytes(), bytes).unwrap_err(); assert!(why.reason.contains("does not verify"), "{why:?}"); diff --git a/crates/cloud/src/audit_sign.rs b/crates/cloud/src/audit_sign.rs index d19a486..6714e14 100644 --- a/crates/cloud/src/audit_sign.rs +++ b/crates/cloud/src/audit_sign.rs @@ -152,7 +152,7 @@ pub fn build_signed_manifest( ) -> AuditManifest { let tip = ChainTip::of(entries); let sig: Signature = key.sign(&tip.signing_bytes(org, now_ms)); - let public_key_b64 = STANDARD.encode(key.verifying_key().to_encoded_point(false).as_bytes()); + let public_key_b64 = STANDARD.encode(key.verifying_key().to_sec1_point(false).as_bytes()); AuditManifest { org: org.to_string(), tip_seq: tip.tip_seq, @@ -273,11 +273,8 @@ mod tests { let key = parse_signing_key(&encoded).expect("parse raw scalar"); // Signs the same as the direct scalar key. assert_eq!( - key.verifying_key().to_encoded_point(false).as_bytes(), - test_key() - .verifying_key() - .to_encoded_point(false) - .as_bytes() + key.verifying_key().to_sec1_point(false).as_bytes(), + test_key().verifying_key().to_sec1_point(false).as_bytes() ); } diff --git a/crates/cloud/src/devices.rs b/crates/cloud/src/devices.rs index 73fcedb..8d75d5f 100644 --- a/crates/cloud/src/devices.rs +++ b/crates/cloud/src/devices.rs @@ -109,7 +109,7 @@ mod tests { fn test_key() -> (SigningKey, String) { let sk = SigningKey::from_slice(&[0x11u8; 32]).expect("valid scalar"); let vk = sk.verifying_key(); - let pubkey_b64 = STANDARD.encode(vk.to_encoded_point(false).as_bytes()); + let pubkey_b64 = STANDARD.encode(vk.to_sec1_point(false).as_bytes()); (sk, pubkey_b64) } diff --git a/crates/cloud/tests/it/audit_manifest_keyryx.rs b/crates/cloud/tests/it/audit_manifest_keyryx.rs index e118128..9501e82 100644 --- a/crates/cloud/tests/it/audit_manifest_keyryx.rs +++ b/crates/cloud/tests/it/audit_manifest_keyryx.rs @@ -120,7 +120,7 @@ async fn sign( "kid": kid, "signature_b64": STANDARD.encode(sig.to_bytes()), "public_key_b64": STANDARD.encode( - stub.key.verifying_key().to_encoded_point(false).as_bytes() + stub.key.verifying_key().to_sec1_point(false).as_bytes() ), })), ) @@ -262,7 +262,7 @@ async fn a_manifest_keyryx_signed_verifies_and_names_its_kid() { assert_eq!(m["kid"], KEYRYX_KID, "the kid keyryx gives its key"); assert_eq!(m["entry_count"], 1); assert!(verifies(&m), "the manifest must verify offline: {m}"); - let keyryx_key = STANDARD.encode(stub.key.verifying_key().to_encoded_point(false).as_bytes()); + let keyryx_key = STANDARD.encode(stub.key.verifying_key().to_sec1_point(false).as_bytes()); assert_eq!(m["public_key_b64"], keyryx_key, "signed by keyryx's key"); let calls = stub.calls.lock().unwrap(); diff --git a/crates/cloud/tests/it/device_tokens.rs b/crates/cloud/tests/it/device_tokens.rs index fd8c0e1..d33b2af 100644 --- a/crates/cloud/tests/it/device_tokens.rs +++ b/crates/cloud/tests/it/device_tokens.rs @@ -52,7 +52,7 @@ fn state_over(store: Arc) -> AppState { fn keypair() -> (SigningKey, String) { let sk = SigningKey::from_slice(&[0x33u8; 32]).expect("valid scalar"); - let pubkey_b64 = STANDARD.encode(sk.verifying_key().to_encoded_point(false).as_bytes()); + let pubkey_b64 = STANDARD.encode(sk.verifying_key().to_sec1_point(false).as_bytes()); (sk, pubkey_b64) } diff --git a/crates/cloud/tests/it/pairing.rs b/crates/cloud/tests/it/pairing.rs index 2146e8a..17d1252 100644 --- a/crates/cloud/tests/it/pairing.rs +++ b/crates/cloud/tests/it/pairing.rs @@ -33,7 +33,7 @@ fn state() -> AppState { fn keypair() -> (SigningKey, String) { let sk = SigningKey::from_slice(&[0x11u8; 32]).expect("valid scalar"); - let pubkey_b64 = STANDARD.encode(sk.verifying_key().to_encoded_point(false).as_bytes()); + let pubkey_b64 = STANDARD.encode(sk.verifying_key().to_sec1_point(false).as_bytes()); (sk, pubkey_b64) } diff --git a/crates/delegation/Cargo.toml b/crates/delegation/Cargo.toml index 559fa87..03c3458 100644 --- a/crates/delegation/Cargo.toml +++ b/crates/delegation/Cargo.toml @@ -16,7 +16,7 @@ description = "Offline verification of an RFC 8693 delegation token, sender-cons [dependencies] tokenfuse-dpop.workspace = true base64 = { workspace = true, optional = true } -p256 = { version = "0.13", features = ["ecdsa"], optional = true } +p256 = { version = "0.14", features = ["ecdsa"], optional = true } jsonwebtoken.workspace = true serde.workspace = true serde_json.workspace = true @@ -32,4 +32,4 @@ sha2.workspace = true # The tests mint real ES256 tokens and verify them as an auditor would, rather # than trusting this crate to check its own output. Already in the lockfile via # `cloud`'s dev-dependencies, so nothing new enters it. -p256 = { version = "0.13", features = ["ecdsa"] } +p256 = { version = "0.14", features = ["ecdsa"] } diff --git a/crates/delegation/src/testing.rs b/crates/delegation/src/testing.rs index 3a70691..7e0898b 100644 --- a/crates/delegation/src/testing.rs +++ b/crates/delegation/src/testing.rs @@ -36,11 +36,11 @@ impl Default for Key { impl Key { pub fn new() -> Self { Key { - signing: p256::ecdsa::SigningKey::random(&mut p256::elliptic_curve::rand_core::OsRng), + signing: ::generate(), } } pub fn jwk_value(&self, kid: Option<&str>) -> serde_json::Value { - let point = self.signing.verifying_key().to_encoded_point(false); + let point = self.signing.verifying_key().to_sec1_point(false); let mut v = serde_json::json!({ "kty": "EC", "crv": "P-256", "x": b64(point.x().unwrap()), "y": b64(point.y().unwrap()), diff --git a/crates/dpop/Cargo.toml b/crates/dpop/Cargo.toml index b4734b0..0ac5e0b 100644 --- a/crates/dpop/Cargo.toml +++ b/crates/dpop/Cargo.toml @@ -24,4 +24,4 @@ serde_json.workspace = true # These tests mint real proofs and verify them through the real code path, # rather than asserting against a stub of the wire. Same key type and same # crate the Cloud's delegation tests already use. -p256 = { version = "0.13", features = ["ecdsa"] } +p256 = { version = "0.14", features = ["ecdsa"] } diff --git a/crates/dpop/src/lib.rs b/crates/dpop/src/lib.rs index b1eb801..8fbe013 100644 --- a/crates/dpop/src/lib.rs +++ b/crates/dpop/src/lib.rs @@ -505,13 +505,11 @@ yyuNSwNVs/LTnw7nI9Ius8M= impl Key { fn new() -> Self { Key { - signing: p256::ecdsa::SigningKey::random( - &mut p256::elliptic_curve::rand_core::OsRng, - ), + signing: ::generate(), } } fn jwk_value(&self) -> serde_json::Value { - let point = self.signing.verifying_key().to_encoded_point(false); + let point = self.signing.verifying_key().to_sec1_point(false); serde_json::json!({ "kty": "EC", "crv": "P-256", "x": b64(point.x().unwrap()), "y": b64(point.y().unwrap()), diff --git a/crates/gateway/Cargo.toml b/crates/gateway/Cargo.toml index a3c2b33..d06c54d 100644 --- a/crates/gateway/Cargo.toml +++ b/crates/gateway/Cargo.toml @@ -25,9 +25,11 @@ thiserror.workspace = true # built. subtle = "2.6" # The MCP broker binds a session id it relays to the upstream and the -# credential that opened it (crate::mcpsession, invariant 77). All three are -# already in Cargo.lock through crates/dpop, crates/delegation and crates/cloud; -# naming them here adds direct edges, not crates. +# credential that opened it (crate::mcpsession, invariant 77). sha2 and +# getrandom are already in Cargo.lock through crates/dpop and crates/cloud. +# hmac 0.12 arrived through p256 0.13's rfc6979 until p256 0.14 (which uses +# hmac 0.13); since then this line is the only edge that keeps hmac 0.12 in +# the lockfile. hmac = "0.12" sha2.workspace = true getrandom = "0.2" @@ -36,7 +38,7 @@ getrandom = "0.2" # keyryx's proof of possession. Both were dev-dependencies for the MCP door's # tests and are in Cargo.lock through crates/cloud and crates/delegation, so # nothing new enters the tree. -p256 = { version = "0.13", features = ["ecdsa"] } +p256 = { version = "0.14", features = ["ecdsa"] } base64 = "0.22" tokio = { workspace = true, features = ["io-std", "io-util"] } axum.workspace = true diff --git a/crates/gateway/src/checkpoints.rs b/crates/gateway/src/checkpoints.rs index 88059a2..4513490 100644 --- a/crates/gateway/src/checkpoints.rs +++ b/crates/gateway/src/checkpoints.rs @@ -250,7 +250,7 @@ impl HeldKey { } fn from_signing_key(sk: SigningKey) -> Result { - let point = sk.verifying_key().to_encoded_point(false); + let point = sk.verifying_key().to_sec1_point(false); let (Some(x), Some(y)) = (point.x(), point.y()) else { return Err("the public key has no coordinates".into()); }; diff --git a/crates/gateway/tests/it/mcp_broker.rs b/crates/gateway/tests/it/mcp_broker.rs index a0ee155..c3c9df7 100644 --- a/crates/gateway/tests/it/mcp_broker.rs +++ b/crates/gateway/tests/it/mcp_broker.rs @@ -1627,11 +1627,11 @@ struct ProofKey { impl ProofKey { fn new() -> Self { ProofKey { - signing: p256::ecdsa::SigningKey::random(&mut p256::elliptic_curve::rand_core::OsRng), + signing: ::generate(), } } fn jwk(&self) -> Value { - let point = self.signing.verifying_key().to_encoded_point(false); + let point = self.signing.verifying_key().to_sec1_point(false); json!({"kty": "EC", "crv": "P-256", "x": b64(point.x().unwrap()), "y": b64(point.y().unwrap())}) } /// A proof for `POST {origin}/`, which is where these tests post. diff --git a/crates/gateway/tests/it/mcp_door.rs b/crates/gateway/tests/it/mcp_door.rs index df41059..e36efef 100644 --- a/crates/gateway/tests/it/mcp_door.rs +++ b/crates/gateway/tests/it/mcp_door.rs @@ -31,12 +31,12 @@ struct Key { impl Key { fn new() -> Self { Key { - signing: p256::ecdsa::SigningKey::random(&mut p256::elliptic_curve::rand_core::OsRng), + signing: ::generate(), } } fn jwk(&self) -> serde_json::Value { - let point = self.signing.verifying_key().to_encoded_point(false); + let point = self.signing.verifying_key().to_sec1_point(false); serde_json::json!({ "kty": "EC", "crv": "P-256", "x": b64(point.x().unwrap()), "y": b64(point.y().unwrap()), From b7218da7ab482dcf586628bb45eba8cf0c17a5f2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 03:22:55 +0000 Subject: [PATCH 3/4] test(crypto): pin the P-256 wire bytes to RFC 6979's published vector The external P-256 values the suite already checked were three keyryx thumbprints (measured from keyryx's Go code) and agent-passport's checkpoint vectors, which it verifies but does not produce. Nothing held a signature this code produces, a SEC1 public key, or a PKCS#8 / SEC1 private key parse to an external value, so a curve library that changed one of those could pass the suite as long as it agreed with itself. Three tests now hold the bytes to RFC 6979 appendix A.2.5 (a P-256 key, its point, its deterministic SHA-256 signatures), with the base64 forms, the RFC 7638 thumbprint and the PKCS#8 / SEC1 encodings written out by Python `cryptography` 50.0.1, never by p256: - gateway::checkpoints: the checkpoint key's JWK x and y, its kid, and the raw r||s over "sample" and "test". - cloud::devices: the RFC signature verifies under the uncompressed and compressed SEC1 key; with its halves swapped, or in DER, it is refused. - cloud::audit_sign: the RFC key loads from PKCS#8 PEM, SEC1 PEM, PKCS#8 DER, SEC1 DER and a raw scalar, and each signs the same empty-chain manifest to the same public key and signature bytes. Each went red against a deliberately broken encoding first (padded standard base64 for x and y; s before r; a DER-only signature parser; a compressed public key and a DER signature in the manifest) and green on the real code. All three also pass unchanged on main with p256 0.13.2, so 0.13 and 0.14 put the same bytes on the wire. Encoding the manifest's public key compressed passed every older test; only the new one caught it. Scenarios: features/the-p256-wire-format-is-unchanged.feature, five, each bound. The suite goes from 2414 to 2417; README.md and PROGRESS.md say so. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HKcmLyFYX5vcY81ze4tHQ1 --- PROGRESS.md | 2 +- README.md | 2 +- crates/cloud/src/audit_sign.rs | 44 +++++++++++++ crates/cloud/src/devices.rs | 26 ++++++++ crates/gateway/src/checkpoints/tests.rs | 57 +++++++++++++++++ .../the-p256-wire-format-is-unchanged.feature | 63 +++++++++++++++++++ 6 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 features/the-p256-wire-format-is-unchanged.feature diff --git a/PROGRESS.md b/PROGRESS.md index 4322066..4385300 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -304,7 +304,7 @@ comparison in #132. **Counts re-measured 2026-10-05**, each by the command named, because the set here once said 100 where the workspace ran 747 and nothing had been watching: -`cargo test --all` runs **2414 passing** (core 469, dpop 29, delegation 75, gateway 1497, cloud 343, umbrella 1, by `cargo test -p `). The door origin lists (invariant 141, 2026-10-19) add 23 to the 2391 below, counted from the diff against main and not measured again here: 6 in `dpop::origin`, 1 in `gateway::chainproof`, and 16 in `tests/it` (8 in `delegation_origins`, 8 in `mcp_door`). @measured `cargo test --all --no-fail-fast` on this branch after merging origin/main (8ed7744, PR #468 merged), 2026-10-19 as root: 2389 passed and 2 failed. The two failures are the tests that need a 0500 directory (`events_export_startup`, `events::tests::an_unwritable_directory_is_named_at_warn_and_the_export_is_off`), which invariant 78 records as root-only; as any other user they pass, so the CI figure is 2391. The breakdown counts the same binaries per crate: core 460 lib and 9 integration, cloud 203 lib and 140 integration, gateway 993 lib and 487 integration (the root-only test counted in the lib figure), dpop 23, delegation 75, umbrella 1. Against main's 2364 the branch adds 27: the static-key allow-list and the agent token to the policy plane (invariants 138 to 140) add 26 to `gateway`, 11 in `gateway::agenttoken` and `wardryx` and 15 in `tests/it` (`static_key_allow_list`, `static_key_allow_list_startup`, `mcp_agent_token`); the identity-source cache key adds 1 in `gateway::wardryx`, and the governed wide-bind agreement of invariant 137 adds 1 in `tests/it/static_key_allow_list_startup`. The governed profile of invariants 136 and 137 is already in main's figure and is not counted here. The governed profile (invariant 136, 2026-10-19) adds 29 to `gateway`: 8 in `defaults`, 3 in `adminkeys` and 18 in `tests/it/governed_profile.rs`. Its broker and no-mode firewall follow-up adds 11 more: 7 in `tests/it/governed_profile_scope.rs`, 3 in `firewall::absent_mode_tests` and 1 in `defaults`. Earlier totals quoted in this section are dated history of how each count grew, not the current figure. The CORS allow-list, the digested device credentials and org keys, and the dashboard's fragment key (invariants 131 to 133) add 28 to `cloud`, all new tests, counted from the diff against main. The CORS allow-list, the digested device credentials and org keys, and the dashboard's +`cargo test --all` runs **2417 passing** (core 469, dpop 29, delegation 75, gateway 1498, cloud 345, umbrella 1, by `cargo test -p `). The p256 0.14 upgrade (2026-10-26) adds 3 RFC 6979 vector tests to the 2414 below: 1 in `gateway::checkpoints` and 2 in `cloud` (`devices`, `audit_sign`). @measured `cargo test --all --no-fail-fast` on that branch, 2026-10-26 as root: 2415 passed and the two tests that need a 0500 directory failed, as invariant 78 records; both passed when their binaries were run again under `setpriv` with `dac_override` and `dac_read_search` dropped. Per binary: core 460 lib and 9 integration, cloud 205 lib and 140 integration, gateway 995 lib and 503 integration, dpop 29, delegation 75, umbrella 1. The door origin lists (invariant 141, 2026-10-19) add 23 to the 2391 below, counted from the diff against main and not measured again here: 6 in `dpop::origin`, 1 in `gateway::chainproof`, and 16 in `tests/it` (8 in `delegation_origins`, 8 in `mcp_door`). @measured `cargo test --all --no-fail-fast` on this branch after merging origin/main (8ed7744, PR #468 merged), 2026-10-19 as root: 2389 passed and 2 failed. The two failures are the tests that need a 0500 directory (`events_export_startup`, `events::tests::an_unwritable_directory_is_named_at_warn_and_the_export_is_off`), which invariant 78 records as root-only; as any other user they pass, so the CI figure is 2391. The breakdown counts the same binaries per crate: core 460 lib and 9 integration, cloud 203 lib and 140 integration, gateway 993 lib and 487 integration (the root-only test counted in the lib figure), dpop 23, delegation 75, umbrella 1. Against main's 2364 the branch adds 27: the static-key allow-list and the agent token to the policy plane (invariants 138 to 140) add 26 to `gateway`, 11 in `gateway::agenttoken` and `wardryx` and 15 in `tests/it` (`static_key_allow_list`, `static_key_allow_list_startup`, `mcp_agent_token`); the identity-source cache key adds 1 in `gateway::wardryx`, and the governed wide-bind agreement of invariant 137 adds 1 in `tests/it/static_key_allow_list_startup`. The governed profile of invariants 136 and 137 is already in main's figure and is not counted here. The governed profile (invariant 136, 2026-10-19) adds 29 to `gateway`: 8 in `defaults`, 3 in `adminkeys` and 18 in `tests/it/governed_profile.rs`. Its broker and no-mode firewall follow-up adds 11 more: 7 in `tests/it/governed_profile_scope.rs`, 3 in `firewall::absent_mode_tests` and 1 in `defaults`. Earlier totals quoted in this section are dated history of how each count grew, not the current figure. The CORS allow-list, the digested device credentials and org keys, and the dashboard's fragment key (invariants 131 to 133) add 28 to `cloud`, all new tests, counted from the diff against main. The CORS allow-list, the digested device credentials and org keys, and the dashboard's fragment key (invariants 131 to 133) add 28 to `cloud`, all of them new tests: 9 in its library and 19 in `tests/it` (8 CORS, 2 startup refusal, 7 device credentials, nonces and the restart second, 2 dashboard), counted from the diff against main (2276, so 2304). @measured `cargo test --all --no-fail-fast` diff --git a/README.md b/README.md index 6bad1a7..9bcb963 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ > The kill-switch isn't a dashboard button you press after the fact - it's an HTTP 402 the gateway returns mid-run, before the provider bills you. ![release](https://img.shields.io/badge/release-v1.20.0-brightgreen) -![tests](https://img.shields.io/badge/tests-2414-brightgreen) +![tests](https://img.shields.io/badge/tests-2417-brightgreen) ![image](https://img.shields.io/badge/ghcr.io-tokenfuse-blue?logo=docker) ![license](https://img.shields.io/badge/license-Apache--2.0-blue) ![core](https://img.shields.io/badge/core-Rust-orange) diff --git a/crates/cloud/src/audit_sign.rs b/crates/cloud/src/audit_sign.rs index 6714e14..889c55f 100644 --- a/crates/cloud/src/audit_sign.rs +++ b/crates/cloud/src/audit_sign.rs @@ -278,6 +278,50 @@ mod tests { ); } + /// RFC 6979 appendix A.2.5's P-256 key in every encoding + /// [`AUDIT_SIGNING_KEY_ENV`] accepts, each one written out by Python + /// `cryptography` 50.0.1 from the RFC's scalar (2026-10-26), never by the + /// curve library under test. Every one must load as that key and sign the + /// empty-chain manifest for `acme` at 42 ms to the same bytes: the public key + /// is SEC1 uncompressed 04||Ux||Uy, the signature raw r||s, both standard + /// base64. The signature was computed by the same library with RFC 6979 + /// deterministic ECDSA over `acme␟0␟␟0␟42`, and that library reproduces the + /// RFC's own "sample" signature for this key. + #[test] + fn the_rfc_6979_key_loads_from_every_accepted_encoding_and_signs_a_known_manifest() { + const PKCS8_PEM: &str = "-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgya+p2EW6dRZrXCFX +Z7HWk05Qw9s26JsSe4piKxIPZyGhRANCAARg/tS6JVqdMclh63TGNW1owEm4kjth ++mzmaWIuYPKftnkD/hAIuLyZpBrp6VYovGTy8bIMLX6fUXejwpTURiKZ +-----END PRIVATE KEY-----"; + const SEC1_PEM: &str = "-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIMmvqdhFunUWa1whV2ex1pNOUMPbNuibEnuKYisSD2choAoGCCqGSM49 +AwEHoUQDQgAEYP7UuiVanTHJYet0xjVtaMBJuJI7Yfps5mliLmDyn7Z5A/4QCLi8 +maQa6elWKLxk8vGyDC1+n1F3o8KU1EYimQ== +-----END EC PRIVATE KEY-----"; + const PKCS8_DER_B64: &str = "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgya+p2EW6dRZrXCFXZ7HWk05Qw9s26JsSe4piKxIPZyGhRANCAARg/tS6JVqdMclh63TGNW1owEm4kjth+mzmaWIuYPKftnkD/hAIuLyZpBrp6VYovGTy8bIMLX6fUXejwpTURiKZ"; + const SEC1_DER_B64: &str = "MHcCAQEEIMmvqdhFunUWa1whV2ex1pNOUMPbNuibEnuKYisSD2choAoGCCqGSM49AwEHoUQDQgAEYP7UuiVanTHJYet0xjVtaMBJuJI7Yfps5mliLmDyn7Z5A/4QCLi8maQa6elWKLxk8vGyDC1+n1F3o8KU1EYimQ=="; + const SCALAR_B64: &str = "ya+p2EW6dRZrXCFXZ7HWk05Qw9s26JsSe4piKxIPZyE="; + const PUBLIC_KEY_B64: &str = + "BGD+1LolWp0xyWHrdMY1bWjASbiSO2H6bOZpYi5g8p+2eQP+EAi4vJmkGunpVii8ZPLxsgwtfp9Rd6PClNRGIpk="; + const SIGNATURE_B64: &str = + "eRt/XRM+xbJI6Y7bXWdfjdO/sNb9wZgb50dUSi+df19EjX3i3Jzmr1iY5Jhkrl9YzC7FLyqLVQBG9Xsxvcaz2g=="; + + for (name, encoded) in [ + ("pkcs8 pem", PKCS8_PEM), + ("sec1 pem", SEC1_PEM), + ("pkcs8 der", PKCS8_DER_B64), + ("sec1 der", SEC1_DER_B64), + ("raw scalar", SCALAR_B64), + ] { + let key = parse_signing_key(encoded).unwrap_or_else(|| panic!("{name} must load")); + let m = build_signed_manifest("acme", &[], &key, 42); + assert_eq!(m.public_key_b64, PUBLIC_KEY_B64, "{name}"); + assert_eq!(m.signature_b64, SIGNATURE_B64, "{name}"); + assert!(manifest_verifies(&m), "{name}"); + } + } + #[test] fn garbage_key_material_is_none() { assert!(parse_signing_key("").is_none()); diff --git a/crates/cloud/src/devices.rs b/crates/cloud/src/devices.rs index 8d75d5f..f9c4d3f 100644 --- a/crates/cloud/src/devices.rs +++ b/crates/cloud/src/devices.rs @@ -147,4 +147,30 @@ mod tests { assert!(!verify_signature("not-base64!!", "x", "y")); assert!(!verify_signature("", "x", "")); } + + /// RFC 6979 appendix A.2.5's P-256 public point and its deterministic + /// SHA-256 signature over "sample", in the encodings a device sends: SEC1 + /// (uncompressed and compressed) and raw r||s, standard base64. Encoded + /// outside this repository with Python `cryptography` 50.0.1 from the RFC's + /// hex (2026-10-26), never by the curve library under test. The same + /// signature with its halves swapped, or in DER, is refused: the wire form is + /// raw r||s and nothing else. + #[test] + fn a_published_rfc_6979_signature_verifies_and_no_other_encoding_of_it_does() { + const UNCOMPRESSED: &str = + "BGD+1LolWp0xyWHrdMY1bWjASbiSO2H6bOZpYi5g8p+2eQP+EAi4vJmkGunpVii8ZPLxsgwtfp9Rd6PClNRGIpk="; + const COMPRESSED: &str = "A2D+1LolWp0xyWHrdMY1bWjASbiSO2H6bOZpYi5g8p+2"; + const RS: &str = + "79SLKqy2qP0RQN2c1F6B1p0sh3tWqvmRw00OqE6vNxb3yxyULWV8QdQ2x6G24p9l8+kA27mv9AZNxKsvhDrNqA=="; + const SR: &str = + "98sclC1lfEHUNsehtuKfZfPpANu5r/QGTcSrL4Q6zajv1IsqrLao/RFA3ZzUXoHWnSyHe1aq+ZHDTQ6oTq83Fg=="; + const DER: &str = + "MEYCIQDv1IsqrLao/RFA3ZzUXoHWnSyHe1aq+ZHDTQ6oTq83FgIhAPfLHJQtZXxB1DbHobbin2Xz6QDbua/0Bk3Eqy+EOs2o"; + + assert!(verify_signature(UNCOMPRESSED, "sample", RS)); + assert!(verify_signature(COMPRESSED, "sample", RS)); + assert!(!verify_signature(UNCOMPRESSED, "test", RS)); + assert!(!verify_signature(UNCOMPRESSED, "sample", SR)); + assert!(!verify_signature(UNCOMPRESSED, "sample", DER)); + } } diff --git a/crates/gateway/src/checkpoints/tests.rs b/crates/gateway/src/checkpoints/tests.rs index 33d983f..8a2606e 100644 --- a/crates/gateway/src/checkpoints/tests.rs +++ b/crates/gateway/src/checkpoints/tests.rs @@ -1032,3 +1032,60 @@ fn hostile_streams_signed_with_a_real_key_verify() { "the sweep signed {signed} records, which measured too little" ); } + +/// RFC 6979 appendix A.2.5's P-256 key: the private scalar, the public point +/// (Ux, Uy) and the deterministic SHA-256 signatures over "sample" and "test", +/// r then s. Written out from the RFC, never computed here, so an expectation +/// cannot come from the curve library under test. +const RFC6979_D: &str = "c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721"; +const RFC6979_UX: &str = "60fed4ba255a9d31c961eb74c6356d68c049b8923b61fa6ce669622e60f29fb6"; +const RFC6979_UY: &str = "7903fe1008b8bc99a41ae9e95628bc64f2f1b20c2d7e9f5177a3c294d4462299"; +const RFC6979_SAMPLE_RS: &str = "efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716\ + f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8"; +const RFC6979_TEST_RS: &str = "f1abb023518351cd71d881567b1ea663ed3efcf6c5132b354f28d3b0b7d38367\ + 019f4113742a2b14bd25926b49c649155f267e60d3814b4c0cc84250e46f0083"; + +fn unhex(s: &str) -> Vec { + let s: String = s.split_whitespace().collect(); + (0..s.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()) + .collect() +} + +/// The bytes this key puts on the wire, pinned against a published vector: the +/// JWK's `x` and `y` (base64url, no padding, of the 32-byte big-endian +/// coordinates), its `kid` (the RFC 7638 thumbprint of that JWK) and the raw +/// r||s signature. The base64url text and the thumbprint were computed outside +/// this repository, with Python `cryptography` 50.0.1 over the RFC's point +/// (2026-10-26), which also verified both RFC signatures under it. A curve +/// library that changes how it encodes a point or a signature fails here, not +/// in a verifier in another repository. +#[test] +fn the_rfc_6979_key_puts_its_published_point_and_signatures_on_the_wire() { + let secret: [u8; 32] = unhex(RFC6979_D).try_into().unwrap(); + let key = HeldKey::from_secret(&secret).unwrap(); + let jwk = key.public_jwk(); + + assert_eq!(jwk["x"], "YP7UuiVanTHJYet0xjVtaMBJuJI7Yfps5mliLmDyn7Y"); + assert_eq!(jwk["y"], "eQP-EAi4vJmkGunpVii8ZPLxsgwtfp9Rd6PClNRGIpk"); + let x = b64url().decode(jwk["x"].as_str().unwrap()).unwrap(); + let y = b64url().decode(jwk["y"].as_str().unwrap()).unwrap(); + assert_eq!((hex(&x), hex(&y)), (RFC6979_UX.into(), RFC6979_UY.into())); + + let kid = "DOvxvJiAdIqVWIkFt5hDtCunXLF0BV4-JGv4f-ALSm0"; + assert_eq!(key.kid(), kid); + assert_eq!(jwk["kid"], kid); + + assert_eq!( + hex(&key.sign_raw(b"sample")), + hex(&unhex(RFC6979_SAMPLE_RS)) + ); + assert_eq!(hex(&key.sign_raw(b"test")), hex(&unhex(RFC6979_TEST_RS))); + assert!(verifying_key(jwk) + .verify( + b"sample", + &Signature::from_slice(&unhex(RFC6979_SAMPLE_RS)).unwrap() + ) + .is_ok()); +} diff --git a/features/the-p256-wire-format-is-unchanged.feature b/features/the-p256-wire-format-is-unchanged.feature new file mode 100644 index 0000000..d86546e --- /dev/null +++ b/features/the-p256-wire-format-is-unchanged.feature @@ -0,0 +1,63 @@ +Feature: The P-256 bytes on the wire are unchanged by a curve library upgrade + + The cloud, the delegation fixture, the DPoP tests and the gateway's + checkpoint signer use the RustCrypto `p256` crate. Moving it from 0.13 to + 0.14 (with `elliptic-curve` 0.14, `ecdsa` 0.17 and `pkcs8` 0.11) removes + `crypto-bigint` 0.5.5 and its advisory, RUSTSEC-2026-0387, from the + lockfile. The upgrade renames APIs (`to_encoded_point` became + `to_sec1_point`, keys are made through the `Generate` trait), and none of + that may change a byte another system reads: a JWK's `x` and `y`, an + RFC 7638 thumbprint, a raw r||s ES256 signature, a SEC1 public key, or the + PKCS#8 and SEC1 private keys an operator already holds. + + @decided 2026-10-26: the advisories are fixed by upgrading, not by an + ignore in `.cargo/audit.toml`. + + The expectations below come from RFC 6979 appendix A.2.5 (a P-256 key, its + public point and its deterministic SHA-256 signatures), with the base64 + forms, the thumbprint and the PEM and DER encodings computed outside this + repository by Python `cryptography` 50.0.1. Each scenario's test passes on + both p256 0.13.2 and p256 0.14.0. + + What this does NOT cover: the APNs provider JWT, which uses the same + signing primitive but mints at the current time and has no fixed-vector + test; and signatures from real Apple Secure Enclave keys, which no test + holds. p256 keeps `NORMALIZE_S = false` in 0.14, so a high-S signature + still verifies, as it did in 0.13; that was read in the crate source, not + run. + + # @test:the_rfc_6979_key_puts_its_published_point_and_signatures_on_the_wire + Scenario: The checkpoint key's JWK and signatures are the published bytes + Given the gateway's checkpoint key built from the RFC 6979 P-256 scalar + When it writes its public JWK and signs "sample" and "test" + Then the JWK's x and y are the RFC's coordinates in base64url without padding + And its kid is the RFC 7638 thumbprint computed outside this repository + And each signature is the RFC's r followed by s, 64 bytes + + # @test:a_published_rfc_6979_signature_verifies_and_no_other_encoding_of_it_does + Scenario: A device signature in the published encoding verifies and no other does + Given the RFC 6979 public point in uncompressed and compressed SEC1 + And the RFC's signature over "sample" as raw r||s + When the cloud verifies a device signature + Then the raw r||s signature verifies under both encodings of the key + And the same signature with its halves swapped is refused + And the same signature in DER is refused + + # @test:the_rfc_6979_key_loads_from_every_accepted_encoding_and_signs_a_known_manifest + Scenario: Every accepted encoding of an audit signing key loads and signs the same manifest + Given the RFC 6979 key as PKCS#8 PEM, SEC1 PEM, PKCS#8 DER, SEC1 DER and a raw scalar + When each is loaded as the audit signing key and signs the same empty-chain manifest + Then the manifest's public key is SEC1 uncompressed and the same for all five + And the manifest's signature is the deterministic one computed outside this repository + + # @test:a_kid_that_is_the_keys_thumbprint_is_taken + Scenario: A keyryx key's thumbprint is the one keyryx's own code computes + Given three P-256 keys whose thumbprints were measured from keyryx's Go code + When the cloud takes the thumbprint of each key keyryx sends + Then each thumbprint is the measured one, whichever SEC1 encoding was sent + + # @test:the_committed_fixture_is_what_the_writer_writes_byte_for_byte + Scenario: The committed checkpoint fixture is reproduced byte for byte + Given the checkpoint fixture written and signed before the upgrade + When the writer writes the same stream with the same key + Then every record and every signature is byte for byte the committed one From fb39712674dce84114b7ff48f17a8ca307617f37 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 26 Oct 2026 04:02:53 +0000 Subject: [PATCH 4/4] test(crypto): allow-list the RFC 6979 test key for the secret scan `secretscan / tree` flagged the PKCS#8 header of the RFC 6979 appendix A.2.5 P-256 key that the audit-signing-key encoding test loads (crates/cloud/src/audit_sign.rs, fingerprint 3021d90eb9437b2d, the same header fingerprint already allowed for the test keys in crates/cloud/tests/it/oidc.rs and crates/dpop/src/lib.rs). The key is a published test vector and signs nothing. The SEC1 header in the same file was already covered by its existing entry. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HKcmLyFYX5vcY81ze4tHQ1 --- .secretscan-allow | 1 + 1 file changed, 1 insertion(+) diff --git a/.secretscan-allow b/.secretscan-allow index 17ba411..1b36eeb 100644 --- a/.secretscan-allow +++ b/.secretscan-allow @@ -1,6 +1,7 @@ # Exceptions for the estate secret scan (estate-gates invariant 23). Each # names a fingerprint and a path, never a value, and says why it is not a secret. 33ea101ddbe7c33b crates/cloud/src/audit_sign.rs # a PEM header around deliberate garbage, in the test that the parser rejects it +3021d90eb9437b2d crates/cloud/src/audit_sign.rs # RFC 6979 appendix A.2.5's published P-256 test key in PKCS#8 form, in the test that every accepted key encoding loads; a public test vector that signs nothing 3021d90eb9437b2d crates/cloud/tests/it/oidc.rs # an RSA key generated for the OIDC integration tests; it signs nothing outside them 743554670c6065b3 crates/core/src/dlp.rs # the DLP detector's own fixture: an AWS key id shape it must catch and redact 743554670c6065b3 crates/core/src/injection.rs # the injection redactor tests feed the DLP fixture shapes