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 diff --git a/Cargo.lock b/Cargo.lock index f585755..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" @@ -1138,27 +1159,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 +1187,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 +1198,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", @@ -1200,7 +1221,7 @@ dependencies = [ "rustc-hash", "serde", "serde_derive", - "sha2", + "sha2 0.10.9", "smallvec", "target-lexicon", "wasmtime-internal-core", @@ -1208,9 +1229,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 +1242,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 +1269,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 +1282,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 +1299,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" @@ -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.52.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" @@ -3024,7 +3102,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.8", + "webpki-roots 1.0.11", ] [[package]] @@ -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]] @@ -4545,9 +4642,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 +4654,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,17 +5186,17 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.8", + "webpki-roots 1.0.11", ] [[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.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5239,9 +5336,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", @@ -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.52.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", @@ -6731,9 +6858,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 +6896,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", @@ -6788,7 +6915,7 @@ dependencies = [ "semver", "serde", "serde_derive", - "sha2", + "sha2 0.10.9", "smallvec", "target-lexicon", "wasm-encoder 0.258.3", @@ -6800,15 +6927,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 +6944,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 +6970,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 +6984,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 +6994,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 +7005,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 +7017,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 +7074,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", ] @@ -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.52.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/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/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..889c55f 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,14 +273,55 @@ 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() ); } + /// 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 73fcedb..f9c4d3f 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) } @@ -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/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/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", ] 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/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/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()), 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