From f05a99351f48d18d505bf913a7b73ee2036d7ab7 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 4 Sep 2024 14:39:03 +0000 Subject: [PATCH 01/14] cargo update --- Cargo.lock | 264 ++++++++++++++++++++++++++--------------------------- 1 file changed, 131 insertions(+), 133 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 41d04cf..f466ff1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "aead" version = "0.5.2" @@ -175,7 +181,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.4", "object", "rustc-demangle", ] @@ -204,6 +210,15 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "basic-toml" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +dependencies = [ + "serde", +] + [[package]] name = "bech32" version = "0.9.1" @@ -267,9 +282,12 @@ checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cc" -version = "1.1.7" +version = "1.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" +checksum = "e9d013ecb737093c0e86b151a7b837993cf9ec6c502946cfb44bedc392421e0b" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -366,15 +384,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" dependencies = [ "libc", ] @@ -529,7 +547,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -619,9 +637,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "ff" @@ -639,17 +657,17 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" dependencies = [ - "toml 0.5.11", + "toml", ] [[package]] name = "flate2" -version = "1.0.31" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.0", ] [[package]] @@ -782,7 +800,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -905,6 +923,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -995,7 +1019,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -1017,15 +1041,15 @@ dependencies = [ [[package]] name = "i18n-config" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9ce3c48cbc21fd5b22b9331f32b5b51f6ad85d969b99e793427332e76e7640" +checksum = "8e88074831c0be5b89181b05e6748c4915f77769ecc9a4c372f88b169a8509c9" dependencies = [ + "basic-toml", "log", "serde", "serde_derive", "thiserror", - "toml 0.8.19", "unic-langid", ] @@ -1068,7 +1092,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.72", + "syn 2.0.77", "unic-langid", ] @@ -1082,7 +1106,7 @@ dependencies = [ "i18n-config", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -1120,9 +1144,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", @@ -1170,11 +1194,11 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "hermit-abi", + "hermit-abi 0.4.0", "libc", "windows-sys 0.52.0", ] @@ -1187,9 +1211,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -1205,9 +1229,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.155" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libm" @@ -1296,14 +1320,24 @@ dependencies = [ ] [[package]] -name = "mio" -version = "0.8.11" +name = "miniz_oxide" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi 0.3.9", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1412,7 +1446,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] @@ -1447,9 +1481,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.2" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "memchr", ] @@ -1498,7 +1532,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -1730,9 +1764,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1921,7 +1955,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.72", + "syn 2.0.77", "walkdir", ] @@ -1958,9 +1992,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" dependencies = [ "bitflags 2.6.0", "errno", @@ -2071,29 +2105,29 @@ checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" [[package]] name = "serde" -version = "1.0.204" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] name = "serde_json" -version = "1.0.122" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" dependencies = [ "itoa", "memchr", @@ -2101,15 +2135,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_spanned" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" -dependencies = [ - "serde", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2150,6 +2175,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signature" version = "2.2.0" @@ -2236,9 +2267,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.72" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -2301,15 +2332,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.11.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", "fastrand", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2335,7 +2366,7 @@ dependencies = [ "quote", "regex", "reqwest", - "syn 2.0.72", + "syn 2.0.77", "sysinfo", "users", "which", @@ -2358,7 +2389,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] @@ -2418,9 +2449,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.1" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", @@ -2428,7 +2459,7 @@ dependencies = [ "mio", "pin-project-lite", "socket2 0.5.7", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2463,45 +2494,11 @@ dependencies = [ "serde", ] -[[package]] -name = "toml" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -2591,9 +2588,9 @@ checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" [[package]] name = "universal-hash" @@ -2680,34 +2677,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -2717,9 +2715,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2727,28 +2725,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -2825,6 +2823,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -2946,15 +2953,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" -dependencies = [ - "memchr", -] - [[package]] name = "winreg" version = "0.50.0" @@ -3045,7 +3043,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.72", + "syn 2.0.77", ] [[package]] From c015dedcf89d30ea430a2f5ddf330bcf88ac6c8a Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 4 Sep 2024 14:39:11 +0000 Subject: [PATCH 02/14] `i18n-embed 0.15` --- Cargo.lock | 19 ++++++++++--------- Cargo.toml | 4 ++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f466ff1..1fa8f7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -455,11 +455,12 @@ dependencies = [ [[package]] name = "dashmap" -version = "5.5.3" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" dependencies = [ "cfg-if", + "crossbeam-utils", "hashbrown", "lock_api", "once_cell", @@ -1055,9 +1056,9 @@ dependencies = [ [[package]] name = "i18n-embed" -version = "0.14.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94205d95764f5bb9db9ea98fa77f89653365ca748e27161f5bbea2ffd50e459c" +checksum = "e901c87176ac0b615033c81dbe927c230f74700abfd60ed953a6f547c87bbe6d" dependencies = [ "arc-swap", "fluent", @@ -1077,9 +1078,9 @@ dependencies = [ [[package]] name = "i18n-embed-fl" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8241a781f49e923415e106fcd1f89c3fab92cc9f699a521c56e95dee273903d3" +checksum = "d73fe51b9655599147183495551696628b335f75b2dbfa225196b16d69d7288e" dependencies = [ "dashmap", "find-crate", @@ -2244,9 +2245,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" @@ -2787,7 +2788,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index afcabec..55e910c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,8 +42,8 @@ x509-parser = "0.14" yubikey = { version = "=0.8.0-pre.0", features = ["untested"] } # Translations -i18n-embed = { version = "0.14", features = ["desktop-requester", "fluent-system"] } -i18n-embed-fl = "0.8" +i18n-embed = { version = "0.15", features = ["desktop-requester", "fluent-system"] } +i18n-embed-fl = "0.9" lazy_static = "1" rust-embed = "8" From e21c504b33983bf44e3853b296cf72d27f660517 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 1 Nov 2024 09:18:58 +0000 Subject: [PATCH 03/14] Migrate to latest `age-plugin` crate commit --- Cargo.lock | 6 ++---- Cargo.toml | 4 ++++ src/main.rs | 6 +----- src/plugin.rs | 23 +++++++++++++++++++++-- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1fa8f7d..72af44e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,8 +36,7 @@ dependencies = [ [[package]] name = "age-core" version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5f11899bc2bbddd135edbc30c36b1924fa59d0746bb45beb5933fafe3fe509b" +source = "git+https://github.com/str4d/rage.git?rev=baf277a749c839e49f93bffb58d36734ac94be83#baf277a749c839e49f93bffb58d36734ac94be83" dependencies = [ "base64 0.21.7", "chacha20poly1305", @@ -54,8 +53,7 @@ dependencies = [ [[package]] name = "age-plugin" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04740d993aac0e06eaf4cfbf8484a8a23a6a7f950cf5d53bdf2d6ea3f429eb9d" +source = "git+https://github.com/str4d/rage.git?rev=baf277a749c839e49f93bffb58d36734ac94be83#baf277a749c839e49f93bffb58d36734ac94be83" dependencies = [ "age-core", "base64 0.21.7", diff --git a/Cargo.toml b/Cargo.toml index 55e910c..bc97f68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,3 +56,7 @@ man = "0.3" tempfile = "3" test-with = "0.11" which = "5" + +[patch.crates-io] +age-core = { git = "https://github.com/str4d/rage.git", rev = "baf277a749c839e49f93bffb58d36734ac94be83" } +age-plugin = { git = "https://github.com/str4d/rage.git", rev = "baf277a749c839e49f93bffb58d36734ac94be83" } diff --git a/src/main.rs b/src/main.rs index 0bbb5f4..aa00930 100644 --- a/src/main.rs +++ b/src/main.rs @@ -327,11 +327,7 @@ fn main() -> Result<(), Error> { } if let Some(state_machine) = opts.age_plugin { - run_state_machine( - &state_machine, - Some(plugin::RecipientPlugin::default), - Some(plugin::IdentityPlugin::default), - )?; + run_state_machine(&state_machine, plugin::Handler)?; Ok(()) } else if opts.version { println!("age-plugin-yubikey {}", env!("CARGO_PKG_VERSION")); diff --git a/src/plugin.rs b/src/plugin.rs index c0cd18b..30d7ca0 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -2,13 +2,28 @@ use age_core::format::{FileKey, Stanza}; use age_plugin::{ identity::{self, IdentityPluginV1}, recipient::{self, RecipientPluginV1}, - Callbacks, + Callbacks, PluginHandler, }; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::io; use crate::{fl, format, key, p256::Recipient, PLUGIN_NAME}; +pub(crate) struct Handler; + +impl PluginHandler for Handler { + type RecipientV1 = RecipientPlugin; + type IdentityV1 = IdentityPlugin; + + fn recipient_v1(self) -> io::Result { + Ok(RecipientPlugin::default()) + } + + fn identity_v1(self) -> io::Result { + Ok(IdentityPlugin::default()) + } +} + #[derive(Debug, Default)] pub(crate) struct RecipientPlugin { recipients: Vec, @@ -58,6 +73,10 @@ impl RecipientPluginV1 for RecipientPlugin { } } + fn labels(&mut self) -> HashSet { + HashSet::new() + } + fn wrap_file_keys( &mut self, file_keys: Vec, From 2f9895229b800a9d91ca00fcc37d3a6d43ce8a13 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 1 Nov 2024 09:51:18 +0000 Subject: [PATCH 04/14] cargo update again --- Cargo.lock | 354 +++++++++++++++++++++++++++-------------------------- 1 file changed, 183 insertions(+), 171 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 72af44e..58c6688 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,19 +4,13 @@ version = 3 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" @@ -165,23 +159,23 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide 0.7.4", + "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -274,15 +268,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cc" -version = "1.1.16" +version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9d013ecb737093c0e86b151a7b837993cf9ec6c502946cfb44bedc392421e0b" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" dependencies = [ "shlex", ] @@ -388,9 +382,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -453,13 +447,13 @@ dependencies = [ [[package]] name = "dashmap" -version = "6.0.1" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", - "hashbrown", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", @@ -546,7 +540,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] @@ -598,9 +592,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -661,12 +655,12 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.33" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", - "miniz_oxide 0.8.0", + "miniz_oxide", ] [[package]] @@ -745,9 +739,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -760,9 +754,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -770,15 +764,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -787,38 +781,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -856,9 +850,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "group" @@ -916,6 +910,12 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -985,9 +985,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -1003,9 +1003,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.30" +version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ "bytes", "futures-channel", @@ -1018,7 +1018,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -1054,9 +1054,9 @@ dependencies = [ [[package]] name = "i18n-embed" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e901c87176ac0b615033c81dbe927c230f74700abfd60ed953a6f547c87bbe6d" +checksum = "a7839d8c7bb8da7bd58c1112d3a1aeb7f178ff3df4ae87783e758ca3bfb750b7" dependencies = [ "arc-swap", "fluent", @@ -1076,9 +1076,9 @@ dependencies = [ [[package]] name = "i18n-embed-fl" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d73fe51b9655599147183495551696628b335f75b2dbfa225196b16d69d7288e" +checksum = "f6e9571c3cba9eba538eaa5ee40031b26debe76f0c7e17bafc97ea57a76cd82e" dependencies = [ "dashmap", "find-crate", @@ -1087,32 +1087,32 @@ dependencies = [ "i18n-config", "i18n-embed", "lazy_static", - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", "strsim", - "syn 2.0.77", + "syn 2.0.86", "unic-langid", ] [[package]] name = "i18n-embed-impl" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81093c4701672f59416582fe3145676126fd23ba5db910acad0793c1108aaa58" +checksum = "0f2cc0e0523d1fe6fc2c6f66e5038624ea8091b3e7748b5e8e0c84b1698db6c2" dependencies = [ "find-crate", "i18n-config", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1143,12 +1143,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.0", ] [[package]] @@ -1187,9 +1187,9 @@ checksum = "4b3f7cef34251886990511df1c61443aa928499d598a9473929ab5a90a527304" [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is-terminal" @@ -1210,9 +1210,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -1228,15 +1228,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.158" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "linux-raw-sys" @@ -1309,15 +1309,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.8.0" @@ -1329,14 +1320,13 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ - "hermit-abi 0.3.9", "libc", "wasi", - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] @@ -1480,9 +1470,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] @@ -1498,9 +1488,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opaque-debug" @@ -1510,9 +1500,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -1531,7 +1521,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] @@ -1542,9 +1532,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -1645,9 +1635,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -1689,9 +1679,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "poly1305" @@ -1753,10 +1743,32 @@ dependencies = [ ] [[package]] -name = "proc-macro2" -version = "1.0.86" +name = "proc-macro-error-attr2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.86", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -1822,18 +1834,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -1843,9 +1855,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -1854,9 +1866,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" @@ -1954,7 +1966,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.77", + "syn 2.0.86", "walkdir", ] @@ -1991,9 +2003,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.35" +version = "0.38.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" dependencies = [ "bitflags 2.6.0", "errno", @@ -2028,11 +2040,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2079,9 +2091,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -2104,29 +2116,29 @@ checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" [[package]] name = "serde" -version = "1.0.209" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", @@ -2266,9 +2278,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "e89275301d38033efb81a6e60e3497e734dfcc62571f2854bf4b16690398824c" dependencies = [ "proc-macro2", "quote", @@ -2331,9 +2343,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", @@ -2365,7 +2377,7 @@ dependencies = [ "quote", "regex", "reqwest", - "syn 2.0.77", + "syn 2.0.86", "sysinfo", "users", "which", @@ -2373,22 +2385,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "5d171f59dbaa811dbbb1aee1e73db92ec2b122911a48e1390dfe327a821ddede" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] @@ -2448,9 +2460,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" dependencies = [ "backtrace", "bytes", @@ -2458,7 +2470,7 @@ dependencies = [ "mio", "pin-project-lite", "socket2 0.5.7", - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] @@ -2560,36 +2572,36 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" @@ -2630,9 +2642,9 @@ checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom", ] @@ -2676,9 +2688,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -2687,24 +2699,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -2714,9 +2726,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2724,28 +2736,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", @@ -3042,7 +3054,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.86", ] [[package]] From 10dacf971111fc00953dbb1cff1a9fb1722ffd13 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Nov 2024 10:39:33 +0000 Subject: [PATCH 05/14] Bump svenstaro/upload-release-action from 2.6.1 to 2.9.0 Bumps [svenstaro/upload-release-action](https://github.com/svenstaro/upload-release-action) from 2.6.1 to 2.9.0. - [Release notes](https://github.com/svenstaro/upload-release-action/releases) - [Changelog](https://github.com/svenstaro/upload-release-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/svenstaro/upload-release-action/compare/2.6.1...2.9.0) --- updated-dependencies: - dependency-name: svenstaro/upload-release-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c0902c8..be0c914 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -86,7 +86,7 @@ jobs: if: matrix.name == 'windows' - name: Upload archive to release - uses: svenstaro/upload-release-action@2.6.1 + uses: svenstaro/upload-release-action@2.9.0 with: file: ${{ matrix.archive_name }} asset_name: age-plugin-yubikey-$tag-${{ matrix.asset_suffix }} @@ -144,7 +144,7 @@ jobs: args: --package age-plugin-yubikey --no-build --target ${{ matrix.target }} - name: Upload Debian package to release - uses: svenstaro/upload-release-action@2.6.1 + uses: svenstaro/upload-release-action@2.9.0 with: file: target/${{ matrix.target }}/debian/*.deb file_glob: true From 68e634c04ee3f7510887b74da906cd7d82ac5668 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 8 Dec 2025 00:10:49 +0000 Subject: [PATCH 06/14] Migrate to `age-plugin 0.6` --- Cargo.lock | 25 ++++++++++++++++++------- Cargo.toml | 8 ++------ src/key.rs | 18 ++++++++++-------- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 58c6688..c94712c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,8 +29,9 @@ dependencies = [ [[package]] name = "age-core" -version = "0.10.0" -source = "git+https://github.com/str4d/rage.git?rev=baf277a749c839e49f93bffb58d36734ac94be83#baf277a749c839e49f93bffb58d36734ac94be83" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2bf6a89c984ca9d850913ece2da39e1d200563b0a94b002b253beee4c5acf99" dependencies = [ "base64 0.21.7", "chacha20poly1305", @@ -39,15 +40,16 @@ dependencies = [ "io_tee", "nom", "rand", - "secrecy", + "secrecy 0.10.3", "sha2", "tempfile", ] [[package]] name = "age-plugin" -version = "0.5.0" -source = "git+https://github.com/str4d/rage.git?rev=baf277a749c839e49f93bffb58d36734ac94be83#baf277a749c839e49f93bffb58d36734ac94be83" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a31f37914cf72cf36a1cd8ea9f24e5df20899e9348dd3d1c8273f4420ce493" dependencies = [ "age-core", "base64 0.21.7", @@ -1018,7 +1020,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -2076,6 +2078,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -3026,7 +3037,7 @@ dependencies = [ "pcsc", "rand_core", "rsa", - "secrecy", + "secrecy 0.8.0", "sha1", "sha2", "subtle", diff --git a/Cargo.toml b/Cargo.toml index bc97f68..1425fb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,8 +22,8 @@ assets = [ ] [dependencies] -age-core = "0.10" -age-plugin = "0.5" +age-core = "0.11" +age-plugin = "0.6" base64 = "0.21" bech32 = "0.9" console = { version = "0.15", default-features = false } @@ -56,7 +56,3 @@ man = "0.3" tempfile = "3" test-with = "0.11" which = "5" - -[patch.crates-io] -age-core = { git = "https://github.com/str4d/rage.git", rev = "baf277a749c839e49f93bffb58d36734ac94be83" } -age-plugin = { git = "https://github.com/str4d/rage.git", rev = "baf277a749c839e49f93bffb58d36734ac94be83" } diff --git a/src/key.rs b/src/key.rs index 71940e8..9dc501e 100644 --- a/src/key.rs +++ b/src/key.rs @@ -3,7 +3,7 @@ use age_core::{ format::{FileKey, FILE_KEY_BYTES}, primitives::{aead_decrypt, hkdf}, - secrecy::{ExposeSecret, SecretString}, + secrecy::{zeroize::Zeroize, ExposeSecret, SecretString}, }; use age_plugin::{identity, Callbacks}; use bech32::{ToBase32, Variant}; @@ -332,7 +332,7 @@ pub(crate) fn manage(yubikey: &mut YubiKey) -> Result<(), Error> { .with_prompt(fl!("mgr-choose-new-pin")) .with_confirmation(fl!("mgr-repeat-new-pin"), fl!("mgr-pin-mismatch")) .interact() - .map(|pin| Result::<_, Infallible>::Ok(SecretString::new(pin))) + .map(|pin| Result::<_, Infallible>::Ok(SecretString::from(pin))) }, yubikey.serial(), )? @@ -747,12 +747,14 @@ impl Connection { // A failure to decrypt is fatal, because we assume that we won't // encounter 32-bit collisions on the key tag embedded in the header. - match aead_decrypt(&enc_key, FILE_KEY_BYTES, &line.encrypted_file_key) { - Ok(pt) => Ok(TryInto::<[u8; FILE_KEY_BYTES]>::try_into(&pt[..]) - .unwrap() - .into()), - Err(_) => Err(()), - } + aead_decrypt(&enc_key, FILE_KEY_BYTES, &line.encrypted_file_key) + .map_err(|_| ()) + .map(|mut pt| { + FileKey::init_with_mut(|file_key| { + file_key.copy_from_slice(&pt); + pt.zeroize(); + }) + }) } /// Close this connection without resetting the YubiKey. From f3f99a0cbcca18dc907e21f0eb3709e9be999340 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 8 Dec 2025 01:17:50 +0000 Subject: [PATCH 07/14] Bump MSRV to 1.70 --- CHANGELOG.md | 3 +++ Cargo.toml | 2 +- README.md | 2 +- rust-toolchain.toml | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4353d97..b268f3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ to 0.3.0 are beta releases. ## [Unreleased] +### Changed +- MSRV is now 1.70.0. + ## [0.5.0] - 2024-08-04 ### Fixed - `age-plugin-yubikey` can now be compiled with Rust 1.80 and above. diff --git a/Cargo.toml b/Cargo.toml index 1425fb4..7a04d58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ keywords = ["age", "cli", "encryption", "yubikey"] categories = ["command-line-utilities", "cryptography"] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.67" # MSRV +rust-version = "1.70" # MSRV [package.metadata.deb] extended-description = """\ diff --git a/README.md b/README.md index daf6f61..3a7a103 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ which enables files to be encrypted to age identities stored on YubiKeys. | Environment | CLI command | |-------------|-------------| -| Cargo (Rust 1.67+) | `cargo install age-plugin-yubikey` | +| Cargo (Rust 1.70+) | `cargo install age-plugin-yubikey` | | Homebrew (macOS or Linux) | `brew install age-plugin-yubikey` | | Arch Linux | `pacman -S age-plugin-yubikey` | | Debian | [Debian package](https://github.com/str4d/age-plugin-yubikey/releases) | diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 4219db3..5299106 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.67.0" +channel = "1.70.0" components = ["clippy", "rustfmt"] From 144d3088b6e81aac3c071589e73f2d7e704bdb2b Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 8 Dec 2025 01:48:39 +0000 Subject: [PATCH 08/14] Refactor `piv-p256`-specific stanza unwrapping onto `RecipientLine` --- src/format.rs | 43 ++++++++++++++++++++++++++++++++++++------- src/key.rs | 37 ++++++++----------------------------- src/main.rs | 1 - src/plugin.rs | 2 +- 4 files changed, 45 insertions(+), 38 deletions(-) diff --git a/src/format.rs b/src/format.rs index fb9ac4c..19f2284 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,7 +1,7 @@ use age_core::{ - format::{FileKey, Stanza}, - primitives::aead_encrypt, - secrecy::ExposeSecret, + format::{FileKey, Stanza, FILE_KEY_BYTES}, + primitives::{aead_decrypt, aead_encrypt, hkdf}, + secrecy::{zeroize::Zeroize, ExposeSecret}, }; use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine}; use p256::{ @@ -11,8 +11,9 @@ use p256::{ use rand::rngs::OsRng; use sha2::Sha256; -use crate::{p256::Recipient, STANZA_TAG}; +use crate::{key::Connection, p256::Recipient}; +const STANZA_TAG: &str = "piv-p256"; pub(crate) const STANZA_KEY_LABEL: &[u8] = b"piv-p256"; const TAG_BYTES: usize = 4; @@ -117,9 +118,7 @@ impl RecipientLine { let shared_secret = esk.diffie_hellman(pk.public_key()); - let mut salt = vec![]; - salt.extend_from_slice(epk_bytes.as_bytes()); - salt.extend_from_slice(pk.to_encoded().as_bytes()); + let salt = salt(&epk_bytes, pk); let enc_key = { let mut okm = [0; 32]; @@ -142,4 +141,34 @@ impl RecipientLine { encrypted_file_key, } } + + pub(crate) fn unwrap_file_key(&self, conn: &mut Connection) -> Result { + assert_eq!(self.tag, conn.recipient().tag()); + + // The YubiKey API for performing scalar multiplication takes the point in its + // uncompressed SEC-1 encoding. + let shared_secret = conn.p256_ecdh(self.epk_bytes.decompress().as_bytes())?; + + let salt = salt(&self.epk_bytes, conn.recipient()); + + let enc_key = hkdf(&salt, STANZA_KEY_LABEL, shared_secret.as_ref()); + + // A failure to decrypt is fatal, because we assume that we won't + // encounter 32-bit collisions on the key tag embedded in the header. + aead_decrypt(&enc_key, FILE_KEY_BYTES, &self.encrypted_file_key) + .map_err(|_| ()) + .map(|mut pt| { + FileKey::init_with_mut(|file_key| { + file_key.copy_from_slice(&pt); + pt.zeroize(); + }) + }) + } +} + +fn salt(epk_bytes: &EphemeralKeyBytes, pk: &Recipient) -> Vec { + let mut salt = vec![]; + salt.extend_from_slice(epk_bytes.as_bytes()); + salt.extend_from_slice(pk.to_encoded().as_bytes()); + salt } diff --git a/src/key.rs b/src/key.rs index 9dc501e..3a09dcf 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1,10 +1,6 @@ //! Structs for handling YubiKeys. -use age_core::{ - format::{FileKey, FILE_KEY_BYTES}, - primitives::{aead_decrypt, hkdf}, - secrecy::{zeroize::Zeroize, ExposeSecret, SecretString}, -}; +use age_core::secrecy::{ExposeSecret, SecretString}; use age_plugin::{identity, Callbacks}; use bech32::{ToBase32, Variant}; use dialoguer::Password; @@ -25,7 +21,7 @@ use yubikey::{ use crate::{ error::Error, fl, - format::{RecipientLine, STANZA_KEY_LABEL}, + format::RecipientLine, p256::{Recipient, TAG_BYTES}, util::{otp_serial_prefix, Metadata}, IDENTITY_PREFIX, @@ -623,7 +619,6 @@ impl Stub { cert, pk, slot: self.slot, - tag: self.tag, identity_index: self.identity_index, cached_metadata: None, last_touch: None, @@ -636,7 +631,6 @@ pub(crate) struct Connection { cert: Certificate, pk: Recipient, slot: RetiredSlotId, - tag: [u8; 4], identity_index: usize, cached_metadata: Option, last_touch: Option, @@ -705,8 +699,10 @@ impl Connection { Ok(Ok(())) } - pub(crate) fn unwrap_file_key(&mut self, line: &RecipientLine) -> Result { - assert_eq!(self.tag, line.tag); + pub(crate) fn p256_ecdh(&mut self, epk_bytes: &[u8]) -> Result { + // The YubiKey API for performing scalar multiplication takes the point in its + // uncompressed SEC-1 encoding. + assert_eq!(epk_bytes.len(), 65); // Check if the touch policy requires a touch. let needs_touch = match ( @@ -718,11 +714,9 @@ impl Connection { _ => false, }; - // The YubiKey API for performing scalar multiplication takes the point in its - // uncompressed SEC-1 encoding. let shared_secret = match decrypt_data( &mut self.yubikey, - line.epk_bytes.decompress().as_bytes(), + epk_bytes, AlgorithmId::EccP256, SlotId::Retired(self.slot), ) { @@ -739,22 +733,7 @@ impl Connection { } } - let mut salt = vec![]; - salt.extend_from_slice(line.epk_bytes.as_bytes()); - salt.extend_from_slice(self.pk.to_encoded().as_bytes()); - - let enc_key = hkdf(&salt, STANZA_KEY_LABEL, shared_secret.as_ref()); - - // A failure to decrypt is fatal, because we assume that we won't - // encounter 32-bit collisions on the key tag embedded in the header. - aead_decrypt(&enc_key, FILE_KEY_BYTES, &line.encrypted_file_key) - .map_err(|_| ()) - .map(|mut pt| { - FileKey::init_with_mut(|file_key| { - file_key.copy_from_slice(&pt); - pt.zeroize(); - }) - }) + Ok(shared_secret) } /// Close this connection without resetting the YubiKey. diff --git a/src/main.rs b/src/main.rs index aa00930..adf4de4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,6 @@ const PLUGIN_NAME: &str = "yubikey"; const BINARY_NAME: &str = "age-plugin-yubikey"; const RECIPIENT_PREFIX: &str = "age1yubikey"; const IDENTITY_PREFIX: &str = "age-plugin-yubikey-"; -const STANZA_TAG: &str = "piv-p256"; const USABLE_SLOTS: [RetiredSlotId; 20] = [ RetiredSlotId::R1, diff --git a/src/plugin.rs b/src/plugin.rs index 30d7ca0..263e2a6 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -252,7 +252,7 @@ impl IdentityPluginV1 for IdentityPlugin { } for (stanza_index, line) in stanzas.iter().enumerate() { - match conn.unwrap_file_key(line) { + match line.unwrap_file_key(&mut conn) { Ok(file_key) => { // We've managed to decrypt this file! file_keys.entry(file_index).or_insert(Ok(file_key)); From 1f1f257ededf2e5af3fbe21c2448bc7bfd63f520 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 8 Dec 2025 01:53:59 +0000 Subject: [PATCH 09/14] Rename `crate::format` to `crate::piv_p256` --- src/key.rs | 4 ++-- src/main.rs | 2 +- src/{format.rs => piv_p256.rs} | 0 src/plugin.rs | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) rename src/{format.rs => piv_p256.rs} (100%) diff --git a/src/key.rs b/src/key.rs index 3a09dcf..684f39d 100644 --- a/src/key.rs +++ b/src/key.rs @@ -21,8 +21,8 @@ use yubikey::{ use crate::{ error::Error, fl, - format::RecipientLine, p256::{Recipient, TAG_BYTES}, + piv_p256, util::{otp_serial_prefix, Metadata}, IDENTITY_PREFIX, }; @@ -476,7 +476,7 @@ impl Stub { bytes } - pub(crate) fn matches(&self, line: &RecipientLine) -> bool { + pub(crate) fn matches(&self, line: &piv_p256::RecipientLine) -> bool { self.tag == line.tag } diff --git a/src/main.rs b/src/main.rs index adf4de4..9dda740 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,9 +16,9 @@ use yubikey::{piv::RetiredSlotId, reader::Context, PinPolicy, Serial, TouchPolic mod builder; mod error; -mod format; mod key; mod p256; +mod piv_p256; mod plugin; mod util; diff --git a/src/format.rs b/src/piv_p256.rs similarity index 100% rename from src/format.rs rename to src/piv_p256.rs diff --git a/src/plugin.rs b/src/plugin.rs index 263e2a6..0b4a2ad 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -7,7 +7,7 @@ use age_plugin::{ use std::collections::{HashMap, HashSet}; use std::io; -use crate::{fl, format, key, p256::Recipient, PLUGIN_NAME}; +use crate::{fl, key, p256::Recipient, piv_p256, PLUGIN_NAME}; pub(crate) struct Handler; @@ -114,7 +114,7 @@ impl RecipientPluginV1 for RecipientPlugin { self.recipients .iter() .chain(yk_recipients.iter()) - .map(|pk| format::RecipientLine::wrap_file_key(&file_key, pk).into()) + .map(|pk| piv_p256::RecipientLine::wrap_file_key(&file_key, pk).into()) .collect() }) .collect()) @@ -159,7 +159,7 @@ impl IdentityPluginV1 for IdentityPlugin { let mut file_keys = HashMap::with_capacity(files.len()); // Filter to files / stanzas for which we have matching YubiKeys - let mut candidate_stanzas: Vec<(&key::Stub, HashMap>)> = + let mut candidate_stanzas: Vec<(&key::Stub, HashMap>)> = self.yubikeys .iter() .map(|stub| (stub, HashMap::new())) @@ -168,7 +168,7 @@ impl IdentityPluginV1 for IdentityPlugin { for (file, stanzas) in files.iter().enumerate() { for (stanza_index, stanza) in stanzas.iter().enumerate() { match ( - format::RecipientLine::from_stanza(stanza).map(|res| { + piv_p256::RecipientLine::from_stanza(stanza).map(|res| { res.map_err(|_| identity::Error::Stanza { file_index: file, stanza_index, From 5b44faec44c13d917cbb14a495ce43cbe66381f2 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 8 Dec 2025 02:34:04 +0000 Subject: [PATCH 10/14] Refactors for reusability across supported recipients --- src/builder.rs | 8 +++-- src/key.rs | 20 +++++------ src/main.rs | 11 +++--- src/piv_p256.rs | 36 +++++++++---------- src/{p256.rs => piv_p256/recipient.rs} | 8 ++--- src/plugin.rs | 50 +++++++++++++++++--------- src/recipient.rs | 50 ++++++++++++++++++++++++++ src/util.rs | 14 +++++++- 8 files changed, 135 insertions(+), 62 deletions(-) rename src/{p256.rs => piv_p256/recipient.rs} (90%) create mode 100644 src/recipient.rs diff --git a/src/builder.rs b/src/builder.rs index 653789e..8ff96ba 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -11,9 +11,9 @@ use crate::{ error::Error, fl, key::{self, Stub}, - p256::Recipient, + piv_p256, util::{Metadata, POLICY_EXTENSION_OID}, - BINARY_NAME, USABLE_SLOTS, + Recipient, BINARY_NAME, USABLE_SLOTS, }; pub(crate) const DEFAULT_PIN_POLICY: PinPolicy = PinPolicy::Once; @@ -104,7 +104,9 @@ impl IdentityBuilder { touch_policy, )?; - let recipient = Recipient::from_spki(&generated).expect("YubiKey generates a valid pubkey"); + let recipient = Recipient::PivP256( + piv_p256::Recipient::from_spki(&generated).expect("YubiKey generates a valid pubkey"), + ); let stub = Stub::new(yubikey.serial(), slot, &recipient); eprintln!(); diff --git a/src/key.rs b/src/key.rs index 684f39d..87167a8 100644 --- a/src/key.rs +++ b/src/key.rs @@ -20,11 +20,10 @@ use yubikey::{ use crate::{ error::Error, - fl, - p256::{Recipient, TAG_BYTES}, - piv_p256, + fl, piv_p256, + recipient::TAG_BYTES, util::{otp_serial_prefix, Metadata}, - IDENTITY_PREFIX, + Recipient, IDENTITY_PREFIX, }; const ONE_SECOND: Duration = Duration::from_secs(1); @@ -394,7 +393,8 @@ pub(crate) fn list_slots( match key.slot() { SlotId::Retired(slot) => { // Only P-256 keys are compatible with us. - let recipient = Recipient::from_certificate(key.certificate()); + let recipient = piv_p256::Recipient::from_certificate(key.certificate()) + .map(Recipient::PivP256); Some((key, slot, recipient)) } _ => None, @@ -449,7 +449,7 @@ impl Stub { Stub { serial, slot, - tag: recipient.tag(), + tag: recipient.static_tag(), identity_index: 0, } } @@ -476,10 +476,6 @@ impl Stub { bytes } - pub(crate) fn matches(&self, line: &piv_p256::RecipientLine) -> bool { - self.tag == line.tag - } - /// Returns: /// - `Ok(Ok(Some(connection)))` if we successfully connected to this YubiKey. /// - `Ok(Ok(None))` if the user told us to skip this YubiKey. @@ -601,9 +597,9 @@ impl Stub { let (cert, pk) = match Certificate::read(&mut yubikey, SlotId::Retired(self.slot)) .ok() .and_then(|cert| { - Recipient::from_certificate(&cert) + piv_p256::Recipient::from_certificate(&cert) .filter(|pk| pk.tag() == self.tag) - .map(|pk| (cert, pk)) + .map(|pk| (cert, Recipient::PivP256(pk))) }) { Some(pk) => pk, None => { diff --git a/src/main.rs b/src/main.rs index 9dda740..a2746ec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,16 +17,17 @@ use yubikey::{piv::RetiredSlotId, reader::Context, PinPolicy, Serial, TouchPolic mod builder; mod error; mod key; -mod p256; mod piv_p256; mod plugin; mod util; +mod recipient; +use recipient::Recipient; + use error::Error; const PLUGIN_NAME: &str = "yubikey"; const BINARY_NAME: &str = "age-plugin-yubikey"; -const RECIPIENT_PREFIX: &str = "age1yubikey"; const IDENTITY_PREFIX: &str = "age-plugin-yubikey-"; const USABLE_SLOTS: [RetiredSlotId; 20] = [ @@ -193,7 +194,7 @@ fn generate(flags: PluginFlags) -> Result<(), Error> { fn print_single( serial: Option, slot: RetiredSlotId, - printer: impl Fn(key::Stub, p256::Recipient, util::Metadata), + printer: impl Fn(key::Stub, Recipient, util::Metadata), ) -> Result<(), Error> { let mut yubikey = key::open(serial)?; @@ -215,7 +216,7 @@ fn print_multiple( kind: &str, serial: Option, all: bool, - printer: impl Fn(key::Stub, p256::Recipient, util::Metadata), + printer: impl Fn(key::Stub, Recipient, util::Metadata), ) -> Result<(), Error> { let mut readers = Context::open()?; @@ -255,7 +256,7 @@ fn print_details( kind: &str, flags: PluginFlags, all: bool, - printer: impl Fn(key::Stub, p256::Recipient, util::Metadata), + printer: impl Fn(key::Stub, Recipient, util::Metadata), ) -> Result<(), Error> { if let Some(slot) = flags.slot { print_single(flags.serial, slot, printer) diff --git a/src/piv_p256.rs b/src/piv_p256.rs index 19f2284..f1b1750 100644 --- a/src/piv_p256.rs +++ b/src/piv_p256.rs @@ -11,12 +11,14 @@ use p256::{ use rand::rngs::OsRng; use sha2::Sha256; -use crate::{key::Connection, p256::Recipient}; +use crate::{key::Connection, recipient::TAG_BYTES, util::base64_arg}; + +mod recipient; +pub(crate) use recipient::Recipient; const STANZA_TAG: &str = "piv-p256"; pub(crate) const STANZA_KEY_LABEL: &[u8] = b"piv-p256"; -const TAG_BYTES: usize = 4; const EPK_BYTES: usize = 33; const ENCRYPTED_FILE_KEY_BYTES: usize = 32; @@ -81,17 +83,6 @@ impl RecipientLine { return None; } - fn base64_arg, B: AsMut<[u8]>>(arg: &A, mut buf: B) -> Option { - if arg.as_ref().len() != ((4 * buf.as_mut().len()) + 2) / 3 { - return None; - } - - BASE64_STANDARD_NO_PAD - .decode_slice_unchecked(arg, buf.as_mut()) - .ok() - .and_then(|len| (len == buf.as_mut().len()).then_some(buf)) - } - let (tag, epk_bytes) = match &s.args[..] { [tag, epk_bytes] => ( base64_arg(tag, [0; TAG_BYTES]), @@ -110,15 +101,17 @@ impl RecipientLine { _ => Err(()), }) } +} - pub(crate) fn wrap_file_key(file_key: &FileKey, pk: &Recipient) -> Self { +impl Recipient { + pub(crate) fn wrap_file_key(&self, file_key: &FileKey) -> RecipientLine { let esk = EphemeralSecret::random(&mut OsRng); let epk = esk.public_key(); let epk_bytes = EphemeralKeyBytes::from_public_key(&epk); - let shared_secret = esk.diffie_hellman(pk.public_key()); + let shared_secret = esk.diffie_hellman(self.public_key()); - let salt = salt(&epk_bytes, pk); + let salt = salt(&epk_bytes, self); let enc_key = { let mut okm = [0; 32]; @@ -136,21 +129,24 @@ impl RecipientLine { }; RecipientLine { - tag: pk.tag(), + tag: self.tag(), epk_bytes, encrypted_file_key, } } +} +impl RecipientLine { pub(crate) fn unwrap_file_key(&self, conn: &mut Connection) -> Result { - assert_eq!(self.tag, conn.recipient().tag()); + let crate::recipient::Recipient::PivP256(recipient) = conn.recipient(); + assert_eq!(self.tag, recipient.tag()); + + let salt = salt(&self.epk_bytes, recipient); // The YubiKey API for performing scalar multiplication takes the point in its // uncompressed SEC-1 encoding. let shared_secret = conn.p256_ecdh(self.epk_bytes.decompress().as_bytes())?; - let salt = salt(&self.epk_bytes, conn.recipient()); - let enc_key = hkdf(&salt, STANZA_KEY_LABEL, shared_secret.as_ref()); // A failure to decrypt is fatal, because we assume that we won't diff --git a/src/p256.rs b/src/piv_p256/recipient.rs similarity index 90% rename from src/p256.rs rename to src/piv_p256/recipient.rs index 1f4c607..67e1df2 100644 --- a/src/p256.rs +++ b/src/piv_p256/recipient.rs @@ -1,13 +1,12 @@ use bech32::{ToBase32, Variant}; use p256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint}; -use sha2::{Digest, Sha256}; use yubikey::{certificate::PublicKeyInfo, Certificate}; use std::fmt; -use crate::RECIPIENT_PREFIX; +use crate::recipient::{static_tag, TAG_BYTES}; -pub(crate) const TAG_BYTES: usize = 4; +const RECIPIENT_PREFIX: &str = "age1yubikey"; /// Wrapper around a compressed secp256r1 curve point. #[derive(Clone)] @@ -69,8 +68,7 @@ impl Recipient { } pub(crate) fn tag(&self) -> [u8; TAG_BYTES] { - let tag = Sha256::digest(self.to_encoded().as_bytes()); - (&tag[0..TAG_BYTES]).try_into().expect("length is correct") + static_tag(self.to_encoded().as_bytes()) } /// Exposes the wrapped public key. diff --git a/src/plugin.rs b/src/plugin.rs index 0b4a2ad..97cdd1c 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -7,7 +7,7 @@ use age_plugin::{ use std::collections::{HashMap, HashSet}; use std::io; -use crate::{fl, key, p256::Recipient, piv_p256, PLUGIN_NAME}; +use crate::{fl, key, piv_p256, Recipient, PLUGIN_NAME}; pub(crate) struct Handler; @@ -37,11 +37,7 @@ impl RecipientPluginV1 for RecipientPlugin { plugin_name: &str, bytes: &[u8], ) -> Result<(), recipient::Error> { - if let Some(pk) = if plugin_name == PLUGIN_NAME { - Recipient::from_bytes(bytes) - } else { - None - } { + if let Some(pk) = Recipient::from_bytes(plugin_name, bytes) { self.recipients.push(pk); Ok(()) } else { @@ -114,7 +110,7 @@ impl RecipientPluginV1 for RecipientPlugin { self.recipients .iter() .chain(yk_recipients.iter()) - .map(|pk| piv_p256::RecipientLine::wrap_file_key(&file_key, pk).into()) + .map(|pk| pk.wrap_file_key(&file_key)) .collect() }) .collect()) @@ -159,16 +155,16 @@ impl IdentityPluginV1 for IdentityPlugin { let mut file_keys = HashMap::with_capacity(files.len()); // Filter to files / stanzas for which we have matching YubiKeys - let mut candidate_stanzas: Vec<(&key::Stub, HashMap>)> = - self.yubikeys - .iter() - .map(|stub| (stub, HashMap::new())) - .collect(); + let mut candidate_stanzas: Vec<(&key::Stub, HashMap>)> = self + .yubikeys + .iter() + .map(|stub| (stub, HashMap::new())) + .collect(); - for (file, stanzas) in files.iter().enumerate() { - for (stanza_index, stanza) in stanzas.iter().enumerate() { + for (file, stanzas) in files.into_iter().enumerate() { + for (stanza_index, stanza) in stanzas.into_iter().enumerate() { match ( - piv_p256::RecipientLine::from_stanza(stanza).map(|res| { + SupportedStanza::parse(stanza).map(|res| { res.map_err(|_| identity::Error::Stanza { file_index: file, stanza_index, @@ -182,7 +178,7 @@ impl IdentityPluginV1 for IdentityPlugin { // A line will match at most one YubiKey. if let Some(files) = candidate_stanzas.iter_mut().find_map(|(stub, files)| { - if stub.matches(&line) { + if line.matches_stub(stub) { Some(files) } else { None @@ -274,3 +270,25 @@ impl IdentityPluginV1 for IdentityPlugin { Ok(file_keys) } } + +enum SupportedStanza { + PivP256(piv_p256::RecipientLine), +} + +impl SupportedStanza { + fn parse(stanza: Stanza) -> Option> { + piv_p256::RecipientLine::from_stanza(&stanza).map(|res| res.map(Self::PivP256)) + } + + pub(crate) fn matches_stub(&self, stub: &key::Stub) -> bool { + match self { + SupportedStanza::PivP256(line) => stub.tag == line.tag, + } + } + + pub(crate) fn unwrap_file_key(&self, conn: &mut key::Connection) -> Result { + match self { + SupportedStanza::PivP256(line) => line.unwrap_file_key(conn), + } + } +} diff --git a/src/recipient.rs b/src/recipient.rs new file mode 100644 index 0000000..f846635 --- /dev/null +++ b/src/recipient.rs @@ -0,0 +1,50 @@ +use std::fmt; + +use age_core::format::{FileKey, Stanza}; +use sha2::{Digest, Sha256}; + +use crate::{piv_p256, PLUGIN_NAME}; + +pub(crate) const TAG_BYTES: usize = 4; + +#[derive(Clone, Debug)] +pub(crate) enum Recipient { + PivP256(piv_p256::Recipient), +} + +impl fmt::Display for Recipient { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Recipient::PivP256(recipient) => recipient.fmt(f), + } + } +} + +impl Recipient { + /// Attempts to parse a supported YubiKey recipient. + pub(crate) fn from_bytes(plugin_name: &str, bytes: &[u8]) -> Option { + match plugin_name { + PLUGIN_NAME => piv_p256::Recipient::from_bytes(bytes).map(Self::PivP256), + _ => None, + } + } + + /// Returns the static tag for this recipient. + pub(crate) fn static_tag(&self) -> [u8; TAG_BYTES] { + match self { + Recipient::PivP256(recipient) => recipient.tag(), + } + } + + pub(crate) fn wrap_file_key(&self, file_key: &FileKey) -> Stanza { + match self { + Recipient::PivP256(recipient) => recipient.wrap_file_key(file_key).into(), + } + } +} + +pub(crate) fn static_tag(pk: &[u8]) -> [u8; TAG_BYTES] { + Sha256::digest(pk)[0..TAG_BYTES] + .try_into() + .expect("length is correct") +} diff --git a/src/util.rs b/src/util.rs index b44d221..36141d7 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,6 +1,7 @@ use std::fmt; use std::iter; +use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine}; use x509_parser::{certificate::X509Certificate, der_parser::oid::Oid}; use yubikey::{ piv::{RetiredSlotId, SlotId}, @@ -8,7 +9,7 @@ use yubikey::{ }; use crate::fl; -use crate::{error::Error, key::Stub, p256::Recipient, BINARY_NAME, USABLE_SLOTS}; +use crate::{error::Error, key::Stub, Recipient, BINARY_NAME, USABLE_SLOTS}; pub(crate) const POLICY_EXTENSION_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 41482, 3, 8]; @@ -219,3 +220,14 @@ pub(crate) fn print_identity(stub: Stub, recipient: Recipient, metadata: Metadat ) ); } + +pub(crate) fn base64_arg, B: AsMut<[u8]>>(arg: &A, mut buf: B) -> Option { + if arg.as_ref().len() != ((4 * buf.as_mut().len()) + 2) / 3 { + return None; + } + + BASE64_STANDARD_NO_PAD + .decode_slice_unchecked(arg, buf.as_mut()) + .ok() + .and_then(|len| (len == buf.as_mut().len()).then_some(buf)) +} From 2a4d12954804ae4f3c282eb2af24b528b0c7012c Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 7 Apr 2026 05:50:59 +0100 Subject: [PATCH 11/14] Migrate to latest revision of `age-core` and `age-plugin` --- Cargo.lock | 44 ++++++++++++++++++++++++++------------- Cargo.toml | 8 +++++-- src/key.rs | 13 ++++-------- src/main.rs | 2 +- src/piv_p256/recipient.rs | 14 +++---------- 5 files changed, 43 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c94712c..fda1308 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,15 +30,15 @@ dependencies = [ [[package]] name = "age-core" version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2bf6a89c984ca9d850913ece2da39e1d200563b0a94b002b253beee4c5acf99" +source = "git+https://github.com/str4d/rage.git?rev=5e530a3a6aad9e189e26903bc8114e2da526b4b5#5e530a3a6aad9e189e26903bc8114e2da526b4b5" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", + "bech32", "chacha20poly1305", "cookie-factory", "hkdf", "io_tee", - "nom", + "nom 8.0.0", "rand", "secrecy 0.10.3", "sha2", @@ -48,11 +48,10 @@ dependencies = [ [[package]] name = "age-plugin" version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a31f37914cf72cf36a1cd8ea9f24e5df20899e9348dd3d1c8273f4420ce493" +source = "git+https://github.com/str4d/rage.git?rev=5e530a3a6aad9e189e26903bc8114e2da526b4b5#5e530a3a6aad9e189e26903bc8114e2da526b4b5" dependencies = [ "age-core", - "base64 0.21.7", + "base64 0.22.1", "bech32", "chrono", ] @@ -63,7 +62,7 @@ version = "0.5.0" dependencies = [ "age-core", "age-plugin", - "base64 0.21.7", + "base64 0.22.1", "bech32", "console", "dialoguer", @@ -129,7 +128,7 @@ dependencies = [ "asn1-rs-derive", "asn1-rs-impl", "displaydoc", - "nom", + "nom 7.1.3", "num-traits", "rusticata-macros", "thiserror", @@ -198,6 +197,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -215,9 +220,9 @@ dependencies = [ [[package]] name = "bech32" -version = "0.9.1" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" [[package]] name = "bitflags" @@ -486,7 +491,7 @@ checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" dependencies = [ "asn1-rs", "displaydoc", - "nom", + "nom 7.1.3", "num-bigint", "num-traits", "rusticata-macros", @@ -1358,6 +1363,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + [[package]] name = "ntapi" version = "0.4.1" @@ -2000,7 +2014,7 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" dependencies = [ - "nom", + "nom 7.1.3", ] [[package]] @@ -3006,7 +3020,7 @@ dependencies = [ "data-encoding", "der-parser", "lazy_static", - "nom", + "nom 7.1.3", "oid-registry", "rusticata-macros", "thiserror", @@ -3027,7 +3041,7 @@ dependencies = [ "elliptic-curve", "hmac", "log", - "nom", + "nom 7.1.3", "num-bigint-dig", "num-integer", "num-traits", diff --git a/Cargo.toml b/Cargo.toml index 7a04d58..23599e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,8 +24,8 @@ assets = [ [dependencies] age-core = "0.11" age-plugin = "0.6" -base64 = "0.21" -bech32 = "0.9" +base64 = "0.22" +bech32 = "0.11" console = { version = "0.15", default-features = false } dialoguer = { version = "0.11", default-features = false, features = ["password"] } env_logger = "0.10" @@ -56,3 +56,7 @@ man = "0.3" tempfile = "3" test-with = "0.11" which = "5" + +[patch.crates-io] +age-core = { git = "https://github.com/str4d/rage.git", rev = "5e530a3a6aad9e189e26903bc8114e2da526b4b5" } +age-plugin = { git = "https://github.com/str4d/rage.git", rev = "5e530a3a6aad9e189e26903bc8114e2da526b4b5" } diff --git a/src/key.rs b/src/key.rs index 87167a8..db716f8 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1,8 +1,8 @@ //! Structs for handling YubiKeys. +use age_core::primitives::bech32_encode; use age_core::secrecy::{ExposeSecret, SecretString}; use age_plugin::{identity, Callbacks}; -use bech32::{ToBase32, Variant}; use dialoguer::Password; use log::{debug, error, warn}; use std::convert::Infallible; @@ -422,14 +422,9 @@ pub struct Stub { impl fmt::Display for Stub { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str( - bech32::encode( - IDENTITY_PREFIX, - self.to_bytes().to_base32(), - Variant::Bech32, - ) - .expect("HRP is valid") - .to_uppercase() - .as_str(), + bech32_encode(IDENTITY_PREFIX, &self.to_bytes()) + .to_uppercase() + .as_str(), ) } } diff --git a/src/main.rs b/src/main.rs index a2746ec..69f2840 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,7 @@ use error::Error; const PLUGIN_NAME: &str = "yubikey"; const BINARY_NAME: &str = "age-plugin-yubikey"; -const IDENTITY_PREFIX: &str = "age-plugin-yubikey-"; +const IDENTITY_PREFIX: bech32::Hrp = bech32::Hrp::parse_unchecked("AGE-PLUGIN-YUBIKEY-"); const USABLE_SLOTS: [RetiredSlotId; 20] = [ RetiredSlotId::R1, diff --git a/src/piv_p256/recipient.rs b/src/piv_p256/recipient.rs index 67e1df2..a0a661b 100644 --- a/src/piv_p256/recipient.rs +++ b/src/piv_p256/recipient.rs @@ -1,4 +1,4 @@ -use bech32::{ToBase32, Variant}; +use age_core::primitives::bech32_encode_to_fmt; use p256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint}; use yubikey::{certificate::PublicKeyInfo, Certificate}; @@ -6,7 +6,7 @@ use std::fmt; use crate::recipient::{static_tag, TAG_BYTES}; -const RECIPIENT_PREFIX: &str = "age1yubikey"; +const RECIPIENT_PREFIX: bech32::Hrp = bech32::Hrp::parse_unchecked("age1yubikey"); /// Wrapper around a compressed secp256r1 curve point. #[derive(Clone)] @@ -20,15 +20,7 @@ impl fmt::Debug for Recipient { impl fmt::Display for Recipient { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str( - bech32::encode( - RECIPIENT_PREFIX, - self.to_encoded().as_bytes().to_base32(), - Variant::Bech32, - ) - .expect("HRP is valid") - .as_str(), - ) + bech32_encode_to_fmt(f, RECIPIENT_PREFIX, self.to_encoded().as_bytes()) } } From 0057a1825ed3b4fd7b9fc383aa352ea653eec5e5 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 8 Dec 2025 03:45:43 +0000 Subject: [PATCH 12/14] Add support for `p256tag` --- CHANGELOG.md | 6 + Cargo.lock | 98 +++++++++++++++- Cargo.toml | 6 +- src/key.rs | 4 + src/main.rs | 1 + src/native.rs | 59 ++++++++++ src/native/p256tag.rs | 264 ++++++++++++++++++++++++++++++++++++++++++ src/piv_p256.rs | 5 +- src/plugin.rs | 11 +- src/recipient.rs | 7 +- 10 files changed, 453 insertions(+), 8 deletions(-) create mode 100644 src/native.rs create mode 100644 src/native/p256tag.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b268f3d..c2979f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ to 0.3.0 are beta releases. ## [Unreleased] +### Added +- Support for the native non-hybrid tagged recipient type (`age1tag1..`). + - Encryption requires making the `age-plugin-yubikey` binary available on the + `PATH` as `age-plugin-tag`, or upgrading to a client version that builds in + support for this new native recipient type. + ### Changed - MSRV is now 1.70.0. diff --git a/Cargo.lock b/Cargo.lock index fda1308..41d8438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,16 +27,42 @@ dependencies = [ "generic-array", ] +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "age-core" version = "0.11.0" -source = "git+https://github.com/str4d/rage.git?rev=5e530a3a6aad9e189e26903bc8114e2da526b4b5#5e530a3a6aad9e189e26903bc8114e2da526b4b5" +source = "git+https://github.com/str4d/rage.git?rev=e08c450aa5d7b1cc5706094080c0042ddd60aaf7#e08c450aa5d7b1cc5706094080c0042ddd60aaf7" dependencies = [ "base64 0.22.1", "bech32", "chacha20poly1305", "cookie-factory", "hkdf", + "hpke", "io_tee", "nom 8.0.0", "rand", @@ -48,7 +74,7 @@ dependencies = [ [[package]] name = "age-plugin" version = "0.6.1" -source = "git+https://github.com/str4d/rage.git?rev=5e530a3a6aad9e189e26903bc8114e2da526b4b5#5e530a3a6aad9e189e26903bc8114e2da526b4b5" +source = "git+https://github.com/str4d/rage.git?rev=e08c450aa5d7b1cc5706094080c0042ddd60aaf7#e08c450aa5d7b1cc5706094080c0042ddd60aaf7" dependencies = [ "age-core", "base64 0.22.1", @@ -70,6 +96,8 @@ dependencies = [ "flate2", "gumdrop", "hex", + "hkdf", + "hpke", "i18n-embed", "i18n-embed-fl", "lazy_static", @@ -449,9 +477,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "dashmap" version = "6.1.0" @@ -855,6 +893,16 @@ dependencies = [ "wasi", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.31.1" @@ -968,6 +1016,26 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "hpke" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4917627a14198c3603282c5158b815ad5534795451d3c074b53cf3cee0960b11" +dependencies = [ + "aead", + "aes-gcm", + "chacha20poly1305", + "digest", + "generic-array", + "hkdf", + "hmac", + "p256", + "rand_core", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "http" version = "0.2.12" @@ -1710,6 +1778,18 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -3087,3 +3167,17 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.86", +] diff --git a/Cargo.toml b/Cargo.toml index 23599e5..141b4fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,8 @@ dialoguer = { version = "0.11", default-features = false, features = ["password" env_logger = "0.10" gumdrop = "0.8" hex = "0.4" +hkdf = "0.12" +hpke = { version = "0.12", default-features = false, features = ["alloc", "p256"] } log = "0.4" p256 = { version = "0.13", features = ["ecdh"] } pcsc = "2.4" @@ -58,5 +60,5 @@ test-with = "0.11" which = "5" [patch.crates-io] -age-core = { git = "https://github.com/str4d/rage.git", rev = "5e530a3a6aad9e189e26903bc8114e2da526b4b5" } -age-plugin = { git = "https://github.com/str4d/rage.git", rev = "5e530a3a6aad9e189e26903bc8114e2da526b4b5" } +age-core = { git = "https://github.com/str4d/rage.git", rev = "e08c450aa5d7b1cc5706094080c0042ddd60aaf7" } +age-plugin = { git = "https://github.com/str4d/rage.git", rev = "e08c450aa5d7b1cc5706094080c0042ddd60aaf7" } diff --git a/src/key.rs b/src/key.rs index db716f8..4e1375e 100644 --- a/src/key.rs +++ b/src/key.rs @@ -632,6 +632,10 @@ impl Connection { &self.pk } + pub(crate) fn stub(&self) -> Stub { + Stub::new(self.yubikey.serial(), self.slot, &self.pk) + } + pub(crate) fn request_pin_if_necessary( &mut self, callbacks: &mut dyn Callbacks, diff --git a/src/main.rs b/src/main.rs index 69f2840..5492493 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ use yubikey::{piv::RetiredSlotId, reader::Context, PinPolicy, Serial, TouchPolic mod builder; mod error; mod key; +mod native; mod piv_p256; mod plugin; mod util; diff --git a/src/native.rs b/src/native.rs new file mode 100644 index 0000000..f7484a2 --- /dev/null +++ b/src/native.rs @@ -0,0 +1,59 @@ +use std::marker::PhantomData; +use std::rc::Rc; +use std::sync::RwLock; + +use hkdf::Hkdf; +use sha2::Sha256; + +use crate::key::Connection; + +pub(crate) mod p256tag; + +/// Derives a tag for the tagged age recipient formats. +fn stanza_tag(ikm: &[u8], salt: &str) -> [u8; 4] { + let (tag, _) = Hkdf::::extract(Some(salt.as_bytes()), ikm); + tag[..4].try_into().expect("correct length") +} + +/// Pretend that a YubiKey connection is a KEM private key. +struct YubiKeyKemPrivateKey<'a, Kem> { + conn: Rc>, + _kem: PhantomData, +} + +impl<'a, Kem> YubiKeyKemPrivateKey<'a, Kem> { + fn new(conn: &'a mut Connection) -> Self { + Self { + conn: Rc::new(RwLock::new(conn)), + _kem: PhantomData::default(), + } + } +} + +impl<'a, Kem> Clone for YubiKeyKemPrivateKey<'a, Kem> { + fn clone(&self) -> Self { + Self { + conn: self.conn.clone(), + _kem: PhantomData::default(), + } + } +} + +impl<'a, Kem> PartialEq for YubiKeyKemPrivateKey<'a, Kem> { + fn eq(&self, other: &Self) -> bool { + self.conn.read().unwrap().stub() == other.conn.read().unwrap().stub() + } +} +impl<'a, Kem> Eq for YubiKeyKemPrivateKey<'a, Kem> {} + +impl<'a, Kem: hpke::Kem> hpke::Serializable for YubiKeyKemPrivateKey<'a, Kem> { + type OutputSize = ::OutputSize; + fn write_exact(&self, _: &mut [u8]) { + unreachable!("Never called") + } +} +impl<'a, Kem: hpke::Kem> hpke::Deserializable for YubiKeyKemPrivateKey<'a, Kem> { + fn from_bytes(_: &[u8]) -> Result { + unreachable!("Never called") + } +} diff --git a/src/native/p256tag.rs b/src/native/p256tag.rs new file mode 100644 index 0000000..beb3cde --- /dev/null +++ b/src/native/p256tag.rs @@ -0,0 +1,264 @@ +use std::fmt; +use std::marker::PhantomData; + +use age_core::{ + format::{FileKey, Stanza}, + primitives::{bech32_encode_to_fmt, hpke_open, hpke_seal}, + secrecy::{zeroize::Zeroize, ExposeSecret}, +}; +use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine}; +use hpke::{Deserializable, Serializable}; +use p256::{ + elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint}, + EncodedPoint, +}; +use rand::rngs::OsRng; + +use super::{stanza_tag, YubiKeyKemPrivateKey}; +use crate::{ + key::{self, Connection}, + recipient::static_tag, + util::base64_arg, +}; + +pub(crate) const PLUGIN_NAME: &str = "tag"; +const RECIPIENT_PREFIX: bech32::Hrp = bech32::Hrp::parse_unchecked("age1tag"); + +const P256TAG_RECIPIENT_TAG: &str = "p256tag"; +const P256TAG_SALT: &str = "age-encryption.org/p256tag"; + +const TAG_BYTES: usize = 4; +/// Per [RFC 9180 section 7.1.1]: +/// > For P-256, P-384, and P-521, the `SerializePublicKey()` function of the KEM performs +/// > the uncompressed Elliptic-Curve-Point-to-Octet-String conversion according to [SECG]. +/// +/// [RFC 9180 section 7.1.1]: https://www.rfc-editor.org/rfc/rfc9180.html#section-7.1.1 +/// [SECG]: https://secg.org/sec1-v2.pdf +const ENC_BYTES: usize = 65; + +type Kem = hpke::kem::DhP256HkdfSha256; + +/// The non-hybrid tagged age recipient type, designed for hardware keys where decryption +/// potentially requires user presence. +/// +/// With knowledge of the recipient, it is possible to check if a stanza was addressed to +/// a specific recipient before attempting decryption. This offers less privacy than the +/// untagged recipient types. +#[derive(Clone, PartialEq, Eq)] +pub(crate) struct Recipient { + /// Compressed encoding of the recipient public key. + compressed: EncodedPoint, + /// Cached in-memory representation, for HPKE. + pk_recip: ::PublicKey, +} + +impl fmt::Display for Recipient { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + bech32_encode_to_fmt(f, RECIPIENT_PREFIX, self.compressed.as_bytes()) + } +} + +impl fmt::Debug for Recipient { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self) + } +} + +impl Recipient { + /// Attempts to parse a valid p256tag recipient from its compressed SEC-1 byte encoding. + pub(crate) fn from_bytes(bytes: &[u8]) -> Option { + let encoded = p256::EncodedPoint::from_bytes(bytes).ok()?; + if !encoded.is_compressed() { + return None; + } + + let point = p256::PublicKey::from_encoded_point(&encoded).into_option()?; + + let pk_recip = + ::PublicKey::from_bytes(point.to_encoded_point(false).as_bytes()) + .expect("valid"); + + Some(Self { + compressed: encoded, + pk_recip, + }) + } + + pub(crate) fn static_tag(&self) -> [u8; TAG_BYTES] { + static_tag(self.compressed.as_bytes()) + } + + pub(crate) fn wrap_file_key(&self, file_key: &FileKey) -> RecipientLine { + let (enc, ct) = hpke_seal::( + &self.pk_recip, + P256TAG_SALT.as_bytes(), + file_key.expose_secret(), + &mut OsRng, + ); + + RecipientLine { + tag: tag(&enc, self.static_tag()), + enc, + ct, + } + } +} + +fn tag(enc: &::EncappedKey, static_tag: [u8; TAG_BYTES]) -> [u8; TAG_BYTES] { + let ikm = enc + .to_bytes() + .into_iter() + .chain(static_tag) + .collect::>(); + + stanza_tag(&ikm, P256TAG_SALT) +} + +pub(crate) struct RecipientLine { + tag: [u8; TAG_BYTES], + enc: ::EncappedKey, + ct: Vec, +} + +impl From for Stanza { + fn from(r: RecipientLine) -> Self { + Stanza { + tag: P256TAG_RECIPIENT_TAG.to_owned(), + args: vec![ + BASE64_STANDARD_NO_PAD.encode(r.tag), + BASE64_STANDARD_NO_PAD.encode(r.enc.to_bytes()), + ], + body: r.ct, + } + } +} + +impl RecipientLine { + pub(crate) fn from_stanza(s: Stanza) -> Option> { + if s.tag != P256TAG_RECIPIENT_TAG { + return None; + } + + let (tag, enc) = match &s.args[..] { + [encoded_tag, encoded_enc] => ( + base64_arg(encoded_tag, [0; TAG_BYTES]), + base64_arg(encoded_enc, [0; ENC_BYTES]) + .and_then(|bytes| ::EncappedKey::from_bytes(&bytes[..]).ok()), + ), + _ => (None, None), + }; + + Some(match (tag, enc) { + (Some(tag), Some(epk_bytes)) => Ok(RecipientLine { + tag, + enc: epk_bytes, + ct: s.body, + }), + // Anything else indicates a structurally-invalid stanza. + _ => Err(()), + }) + } + + pub(crate) fn matches_stub(&self, stub: &key::Stub) -> bool { + self.tag == tag(&self.enc, stub.tag) + } + + pub(crate) fn unwrap_file_key(&self, conn: &mut Connection) -> Result { + // > The identity implementation [...] MUST check that the body length is exactly + // > 32 bytes before attempting to decrypt it, to mitigate partitioning oracle + // > attacks. + if self.ct.len() != 32 { + return Err(()); + } + + let sk_recip = YubiKeyKemPrivateKey::new(conn); + + // A failure to decrypt is fatal, because we assume that we won't + // encounter 32-bit collisions on the key tag embedded in the header. + hpke_open::( + &self.enc, + &sk_recip, + P256TAG_SALT.as_bytes(), + &self.ct, + ) + .map_err(|_| ()) + .map(|mut pt| { + FileKey::init_with_mut(|file_key| { + file_key.copy_from_slice(&pt); + pt.zeroize(); + }) + }) + } +} + +/// A decap-only version of [`Kem`] where the private key is stored on a YubiKey. +struct YubiKeyDhP256HkdfSha256<'a>(PhantomData<&'a ()>); + +impl<'a> hpke::Kem for YubiKeyDhP256HkdfSha256<'a> { + type PublicKey = ::PublicKey; + type PrivateKey = YubiKeyKemPrivateKey<'a, Kem>; + + fn sk_to_pk(_: &Self::PrivateKey) -> Self::PublicKey { + unreachable!("Never called") + } + + type EncappedKey = ::EncappedKey; + type NSecret = ::NSecret; + const KEM_ID: u16 = ::KEM_ID; + + fn derive_keypair(_: &[u8]) -> (Self::PrivateKey, Self::PublicKey) { + unreachable!("Never called") + } + + fn decap( + sk_recip: &Self::PrivateKey, + pk_sender_id: Option<&Self::PublicKey>, + encapped_key: &Self::EncappedKey, + ) -> Result, hpke::HpkeError> { + let mut sk_recip = sk_recip.conn.write().unwrap(); + + // Put together the binding context used for all KDF operations + let suite_id = b"KEM\x00\x10"; + + // Compute the shared secret from the ephemeral inputs + let kex_res_eph = sk_recip + .p256_ecdh(&encapped_key.to_bytes()) + .map_err(|_| hpke::HpkeError::DecapError)?; + + // Compute the sender's pubkey from their privkey + let pk_recip = match sk_recip.recipient() { + crate::recipient::Recipient::P256Tag(recipient) => &recipient.pk_recip, + _ => panic!("should have been filtered out earlier"), + }; + + assert!(pk_sender_id.is_none()); + + // kem_context = encapped_key || pk_recip || pk_sender_id + let kem_context = [encapped_key.to_bytes(), pk_recip.to_bytes()] + .into_iter() + .flatten() + .collect::>(); + + // The "unauthed shared secret" is derived from just the KEX of the ephemeral + // input with the recipient pubkey. The HKDF-Expand call only errors if the + // output values are 255x the digest size of the hash function. Since these + // values are fixed at compile time, we don't worry about it. + let mut shared_secret = as Default>::default(); + hpke::kdf::extract_and_expand::( + &kex_res_eph, + suite_id, + &kem_context, + &mut shared_secret.0, + ) + .expect("shared secret is way too big"); + Ok(shared_secret) + } + + fn encap( + _: &Self::PublicKey, + _: Option<(&Self::PrivateKey, &Self::PublicKey)>, + _: &mut R, + ) -> Result<(hpke::kem::SharedSecret, Self::EncappedKey), hpke::HpkeError> { + unreachable!("Never called") + } +} diff --git a/src/piv_p256.rs b/src/piv_p256.rs index f1b1750..17e05c8 100644 --- a/src/piv_p256.rs +++ b/src/piv_p256.rs @@ -138,7 +138,10 @@ impl Recipient { impl RecipientLine { pub(crate) fn unwrap_file_key(&self, conn: &mut Connection) -> Result { - let crate::recipient::Recipient::PivP256(recipient) = conn.recipient(); + let recipient = match conn.recipient() { + crate::recipient::Recipient::PivP256(recipient) => recipient, + _ => panic!("should have been filtered out earlier"), + }; assert_eq!(self.tag, recipient.tag()); let salt = salt(&self.epk_bytes, recipient); diff --git a/src/plugin.rs b/src/plugin.rs index 97cdd1c..910a554 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -7,7 +7,7 @@ use age_plugin::{ use std::collections::{HashMap, HashSet}; use std::io; -use crate::{fl, key, piv_p256, Recipient, PLUGIN_NAME}; +use crate::{fl, key, native::p256tag, piv_p256, Recipient, PLUGIN_NAME}; pub(crate) struct Handler; @@ -273,22 +273,29 @@ impl IdentityPluginV1 for IdentityPlugin { enum SupportedStanza { PivP256(piv_p256::RecipientLine), + P256Tag(p256tag::RecipientLine), } impl SupportedStanza { fn parse(stanza: Stanza) -> Option> { - piv_p256::RecipientLine::from_stanza(&stanza).map(|res| res.map(Self::PivP256)) + piv_p256::RecipientLine::from_stanza(&stanza) + .map(|res| res.map(Self::PivP256)) + .or_else(|| { + p256tag::RecipientLine::from_stanza(stanza).map(|res| res.map(Self::P256Tag)) + }) } pub(crate) fn matches_stub(&self, stub: &key::Stub) -> bool { match self { SupportedStanza::PivP256(line) => stub.tag == line.tag, + SupportedStanza::P256Tag(line) => line.matches_stub(stub), } } pub(crate) fn unwrap_file_key(&self, conn: &mut key::Connection) -> Result { match self { SupportedStanza::PivP256(line) => line.unwrap_file_key(conn), + SupportedStanza::P256Tag(line) => line.unwrap_file_key(conn), } } } diff --git a/src/recipient.rs b/src/recipient.rs index f846635..03a2223 100644 --- a/src/recipient.rs +++ b/src/recipient.rs @@ -3,19 +3,21 @@ use std::fmt; use age_core::format::{FileKey, Stanza}; use sha2::{Digest, Sha256}; -use crate::{piv_p256, PLUGIN_NAME}; +use crate::{native::p256tag, piv_p256, PLUGIN_NAME}; pub(crate) const TAG_BYTES: usize = 4; #[derive(Clone, Debug)] pub(crate) enum Recipient { PivP256(piv_p256::Recipient), + P256Tag(p256tag::Recipient), } impl fmt::Display for Recipient { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Recipient::PivP256(recipient) => recipient.fmt(f), + Recipient::P256Tag(recipient) => recipient.fmt(f), } } } @@ -25,6 +27,7 @@ impl Recipient { pub(crate) fn from_bytes(plugin_name: &str, bytes: &[u8]) -> Option { match plugin_name { PLUGIN_NAME => piv_p256::Recipient::from_bytes(bytes).map(Self::PivP256), + p256tag::PLUGIN_NAME => p256tag::Recipient::from_bytes(bytes).map(Self::P256Tag), _ => None, } } @@ -33,12 +36,14 @@ impl Recipient { pub(crate) fn static_tag(&self) -> [u8; TAG_BYTES] { match self { Recipient::PivP256(recipient) => recipient.tag(), + Recipient::P256Tag(recipient) => recipient.static_tag(), } } pub(crate) fn wrap_file_key(&self, file_key: &FileKey) -> Stanza { match self { Recipient::PivP256(recipient) => recipient.wrap_file_key(file_key).into(), + Recipient::P256Tag(recipient) => recipient.wrap_file_key(file_key).into(), } } } From 971d63957c181781f142e9dff6a4f0c2eb58e3ec Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sun, 21 Dec 2025 12:21:49 +0000 Subject: [PATCH 13/14] Change recipient type for identity encryption to `p256tag` Encrypting to an identity requires the plugin binary, and there is a reasonable expectation that the same (or a later) plugin binary version will be used to decrypt, so we can assume support for the preferred recipient type. --- CHANGELOG.md | 2 ++ src/key.rs | 12 ++++++++---- src/native/p256tag.rs | 28 ++++++++++++++++++++++++++++ src/piv_p256.rs | 21 +++++++++++++-------- 4 files changed, 51 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2979f2..4728cfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ to 0.3.0 are beta releases. ### Changed - MSRV is now 1.70.0. +- Encryption to an identity now uses the preferred recipient type supported for + that identity. ## [0.5.0] - 2024-08-04 ### Fixed diff --git a/src/key.rs b/src/key.rs index 4e1375e..1714532 100644 --- a/src/key.rs +++ b/src/key.rs @@ -20,7 +20,9 @@ use yubikey::{ use crate::{ error::Error, - fl, piv_p256, + fl, + native::p256tag, + piv_p256, recipient::TAG_BYTES, util::{otp_serial_prefix, Metadata}, Recipient, IDENTITY_PREFIX, @@ -592,9 +594,10 @@ impl Stub { let (cert, pk) = match Certificate::read(&mut yubikey, SlotId::Retired(self.slot)) .ok() .and_then(|cert| { - piv_p256::Recipient::from_certificate(&cert) - .filter(|pk| pk.tag() == self.tag) - .map(|pk| (cert, Recipient::PivP256(pk))) + // Parse as the preferred recipient for each identity type. + p256tag::Recipient::from_certificate(&cert) + .filter(|pk| pk.static_tag() == self.tag) + .map(|pk| (cert, Recipient::P256Tag(pk))) }) { Some(pk) => pk, None => { @@ -628,6 +631,7 @@ pub(crate) struct Connection { } impl Connection { + /// Returns the preferred recipient for encrypting to this identity. pub(crate) fn recipient(&self) -> &Recipient { &self.pk } diff --git a/src/native/p256tag.rs b/src/native/p256tag.rs index beb3cde..ad94675 100644 --- a/src/native/p256tag.rs +++ b/src/native/p256tag.rs @@ -13,6 +13,7 @@ use p256::{ EncodedPoint, }; use rand::rngs::OsRng; +use yubikey::{certificate::PublicKeyInfo, Certificate}; use super::{stanza_tag, YubiKeyKemPrivateKey}; use crate::{ @@ -84,6 +85,33 @@ impl Recipient { }) } + pub(crate) fn from_certificate(cert: &Certificate) -> Option { + Self::from_spki(cert.subject_pki()) + } + + pub(crate) fn from_spki(spki: &PublicKeyInfo) -> Option { + let encoded = match spki { + PublicKeyInfo::EcP256(pubkey) => Some(pubkey), + _ => None, + }?; + + // Check that the certificate encoding is uncompressed. + let pk_recip = ::PublicKey::from_bytes(encoded.as_bytes()).ok()?; + + let point = p256::PublicKey::from_encoded_point(encoded).into_option()?; + let compressed = point.to_encoded_point(true); + + Some(Self { + compressed, + pk_recip, + }) + } + + /// Returns the compressed SEC-1 encoding of this recipient. + pub(crate) fn to_compressed(&self) -> p256::EncodedPoint { + self.compressed + } + pub(crate) fn static_tag(&self) -> [u8; TAG_BYTES] { static_tag(self.compressed.as_bytes()) } diff --git a/src/piv_p256.rs b/src/piv_p256.rs index 17e05c8..26873d4 100644 --- a/src/piv_p256.rs +++ b/src/piv_p256.rs @@ -111,7 +111,7 @@ impl Recipient { let shared_secret = esk.diffie_hellman(self.public_key()); - let salt = salt(&epk_bytes, self); + let salt = salt(&epk_bytes, self.to_encoded()); let enc_key = { let mut okm = [0; 32]; @@ -138,13 +138,17 @@ impl Recipient { impl RecipientLine { pub(crate) fn unwrap_file_key(&self, conn: &mut Connection) -> Result { - let recipient = match conn.recipient() { - crate::recipient::Recipient::PivP256(recipient) => recipient, - _ => panic!("should have been filtered out earlier"), + let (static_tag, pk) = match conn.recipient() { + crate::recipient::Recipient::PivP256(recipient) => { + (recipient.tag(), recipient.to_encoded()) + } + crate::recipient::Recipient::P256Tag(recipient) => { + (recipient.static_tag(), recipient.to_compressed()) + } }; - assert_eq!(self.tag, recipient.tag()); + assert_eq!(self.tag, static_tag); - let salt = salt(&self.epk_bytes, recipient); + let salt = salt(&self.epk_bytes, pk); // The YubiKey API for performing scalar multiplication takes the point in its // uncompressed SEC-1 encoding. @@ -165,9 +169,10 @@ impl RecipientLine { } } -fn salt(epk_bytes: &EphemeralKeyBytes, pk: &Recipient) -> Vec { +fn salt(epk_bytes: &EphemeralKeyBytes, pk: p256::EncodedPoint) -> Vec { + assert!(pk.is_compressed()); let mut salt = vec![]; salt.extend_from_slice(epk_bytes.as_bytes()); - salt.extend_from_slice(pk.to_encoded().as_bytes()); + salt.extend_from_slice(pk.as_bytes()); salt } From 0068b1f3435fdc1af03032ccff2c06fc9a773780 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sun, 21 Dec 2025 13:44:19 +0000 Subject: [PATCH 14/14] Change default recipient type to `p256tag` Identities generated with older versions of `age-plugin-yubikey` show their legacy recipient in comments; newer identities only show the new recipient. --- CHANGELOG.md | 4 ++ README.md | 12 +++-- i18n/en-US/age_plugin_yubikey.ftl | 2 + src/builder.rs | 6 +-- src/key.rs | 5 +- src/main.rs | 19 +++++++- src/piv_p256/recipient.rs | 12 ----- src/recipient.rs | 17 ++++++- src/util.rs | 78 ++++++++++++++++++++++--------- 9 files changed, 107 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4728cfc..0be8da1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,10 @@ to 0.3.0 are beta releases. - MSRV is now 1.70.0. - Encryption to an identity now uses the preferred recipient type supported for that identity. +- `age-plugin-yubikey` now prints `age1tag1..` recipients in its CLI and + identity files instead of `age1yubikey1..` recipients. The latter is now only + shown in comments for identities generated with `age-plugin-yubikey 0.5.0` or + earlier. ## [0.5.0] - 2024-08-04 ### Fixed diff --git a/README.md b/README.md index 3a7a103..5235051 100644 --- a/README.md +++ b/README.md @@ -108,15 +108,17 @@ standard output: $ age-plugin-yubikey --list ``` -To encrypt files to these YubiKey recipients, ensure that `age-plugin-yubikey` -is accessible in your `PATH`, and then use the recipients with an age client as -normal (e.g. `rage -r age1yubikey1...`). +To encrypt files to these YubiKey recipients, ensure you have a recent version +of an age client, and then use the recipients with it as normal (e.g. +`rage -r age1tag1...`). If this does not work, make `age-plugin-yubikey` +accessible in your `PATH` with the name `age-plugin-tag` and try again. The output of the `--list` command can also be used directly to encrypt files to all recipients (e.g. `age -R filename.txt`). -To decrypt files encrypted to a YubiKey identity, pass the identity file to the -age client as normal (e.g. `rage -d -i yubikey-identity.txt`). +To decrypt files encrypted to a YubiKey identity, ensure that +`age-plugin-yubikey` is accessible in your `PATH`, and then pass the identity +file to the age client as normal (e.g. `rage -d -i yubikey-identity.txt`). ## Advanced topics diff --git a/i18n/en-US/age_plugin_yubikey.ftl b/i18n/en-US/age_plugin_yubikey.ftl index 2174b6c..0baf801 100644 --- a/i18n/en-US/age_plugin_yubikey.ftl +++ b/i18n/en-US/age_plugin_yubikey.ftl @@ -43,6 +43,8 @@ yubikey-metadata = # Created: {$created} # PIN policy: {$pin_policy} # Touch policy: {$touch_policy} +yubikey-legacy-recipient = + # Legacy recipient: {$recipient} yubikey-identity = {$yubikey_metadata} # Recipient: {$recipient} diff --git a/src/builder.rs b/src/builder.rs index 8ff96ba..aa4a534 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -11,7 +11,7 @@ use crate::{ error::Error, fl, key::{self, Stub}, - piv_p256, + native::p256tag, util::{Metadata, POLICY_EXTENSION_OID}, Recipient, BINARY_NAME, USABLE_SLOTS, }; @@ -104,8 +104,8 @@ impl IdentityBuilder { touch_policy, )?; - let recipient = Recipient::PivP256( - piv_p256::Recipient::from_spki(&generated).expect("YubiKey generates a valid pubkey"), + let recipient = Recipient::P256Tag( + p256tag::Recipient::from_spki(&generated).expect("YubiKey generates a valid pubkey"), ); let stub = Stub::new(yubikey.serial(), slot, &recipient); diff --git a/src/key.rs b/src/key.rs index 1714532..e1b6277 100644 --- a/src/key.rs +++ b/src/key.rs @@ -22,7 +22,6 @@ use crate::{ error::Error, fl, native::p256tag, - piv_p256, recipient::TAG_BYTES, util::{otp_serial_prefix, Metadata}, Recipient, IDENTITY_PREFIX, @@ -395,8 +394,8 @@ pub(crate) fn list_slots( match key.slot() { SlotId::Retired(slot) => { // Only P-256 keys are compatible with us. - let recipient = piv_p256::Recipient::from_certificate(key.certificate()) - .map(Recipient::PivP256); + let recipient = + p256tag::Recipient::from_certificate(key.certificate()).map(Recipient::P256Tag); Some((key, slot, recipient)) } _ => None, diff --git a/src/main.rs b/src/main.rs index 5492493..24d7c0e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -298,6 +298,12 @@ fn list(flags: PluginFlags, all: bool) -> Result<(), Error> { all, |_, recipient, metadata| { println!("{metadata}"); + if let Some(legacy_recipient) = recipient.legacy_recipient(&metadata) { + println!( + "{}", + fl!("yubikey-legacy-recipient", recipient = legacy_recipient) + ); + } println!("{recipient}"); }, ) @@ -403,7 +409,7 @@ fn main() -> Result<(), Error> { let (_, cert) = x509_parser::parse_x509_certificate(key.certificate().as_ref()) .unwrap(); - let (name, _) = util::extract_name(&cert, true).unwrap(); + let (name, _) = util::extract_name_and_version(&cert, true).unwrap(); let created = cert .validity() .not_before @@ -613,6 +619,15 @@ fn main() -> Result<(), Error> { Err(e) => return Err(e.into()), }; + let identity = if let Some(legacy_recipient) = recipient.legacy_recipient(&metadata) { + format!( + "{}\n{stub}", + fl!("yubikey-legacy-recipient", recipient = legacy_recipient), + ) + } else { + stub.to_string() + }; + writeln!( file, "{}", @@ -620,7 +635,7 @@ fn main() -> Result<(), Error> { "yubikey-identity", yubikey_metadata = metadata.to_string(), recipient = recipient.to_string(), - identity = stub.to_string(), + identity = identity, ) )?; file.sync_data()?; diff --git a/src/piv_p256/recipient.rs b/src/piv_p256/recipient.rs index a0a661b..0b41efe 100644 --- a/src/piv_p256/recipient.rs +++ b/src/piv_p256/recipient.rs @@ -1,6 +1,5 @@ use age_core::primitives::bech32_encode_to_fmt; use p256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint}; -use yubikey::{certificate::PublicKeyInfo, Certificate}; use std::fmt; @@ -35,17 +34,6 @@ impl Recipient { } } - pub(crate) fn from_certificate(cert: &Certificate) -> Option { - Self::from_spki(cert.subject_pki()) - } - - pub(crate) fn from_spki(spki: &PublicKeyInfo) -> Option { - match spki { - PublicKeyInfo::EcP256(pubkey) => Self::from_encoded(pubkey), - _ => None, - } - } - /// Attempts to parse a valid YubiKey recipient from its SEC-1 encoding. /// /// This accepts both compressed (as used by the plugin) and uncompressed (as used in diff --git a/src/recipient.rs b/src/recipient.rs index 03a2223..2333ddc 100644 --- a/src/recipient.rs +++ b/src/recipient.rs @@ -3,7 +3,7 @@ use std::fmt; use age_core::format::{FileKey, Stanza}; use sha2::{Digest, Sha256}; -use crate::{native::p256tag, piv_p256, PLUGIN_NAME}; +use crate::{native::p256tag, piv_p256, util::Metadata, PLUGIN_NAME}; pub(crate) const TAG_BYTES: usize = 4; @@ -32,6 +32,21 @@ impl Recipient { } } + /// Helper for returning the legacy encoding of this recipient, if any. + pub(crate) fn legacy_recipient(&self, metadata: &Metadata) -> Option { + metadata + .is_pre_p256tag() + .then(|| match self { + Recipient::P256Tag(recipient) => Some( + piv_p256::Recipient::from_bytes(recipient.to_compressed().as_bytes()) + .expect("valid") + .to_string(), + ), + _ => None, + }) + .flatten() + } + /// Returns the static tag for this recipient. pub(crate) fn static_tag(&self) -> [u8; TAG_BYTES] { match self { diff --git a/src/util.rs b/src/util.rs index 36141d7..b723ce9 100644 --- a/src/util.rs +++ b/src/util.rs @@ -71,7 +71,10 @@ pub(crate) fn otp_serial_prefix(serial: Serial) -> String { .collect() } -pub(crate) fn extract_name(cert: &X509Certificate, all: bool) -> Option<(String, bool)> { +pub(crate) fn extract_name_and_version( + cert: &X509Certificate, + all: bool, +) -> Option<(String, Option)> { // Look at Subject Organization to determine if we created this. match cert.subject().iter_organization().next() { Some(org) if org.as_str() == Ok(BINARY_NAME) => { @@ -84,7 +87,16 @@ pub(crate) fn extract_name(cert: &X509Certificate, all: bool) -> Option<(String, .map(|s| s.to_owned()) .unwrap_or_default(); // TODO: This should always be present. - Some((name, true)) + // We store the binary version as an Organizational Unit attribute. + let version = cert + .subject() + .iter_organizational_unit() + .next() + .and_then(|cn| cn.as_str().ok()) + .map(|s| s.to_owned()) + .unwrap_or_default(); // TODO: This should always be present. + + Some((name, Some(version))) } _ => { // Not one of ours, but we've already filtered for compatibility. @@ -95,7 +107,7 @@ pub(crate) fn extract_name(cert: &X509Certificate, all: bool) -> Option<(String, // Display the entire subject. let name = cert.subject().to_string(); - Some((name, false)) + Some((name, None)) } } } @@ -104,6 +116,7 @@ pub(crate) struct Metadata { serial: Serial, slot: RetiredSlotId, name: String, + version: Option, created: String, pub(crate) pin_policy: Option, pub(crate) touch_policy: Option, @@ -149,31 +162,29 @@ impl Metadata { .unwrap_or((None, None)) }; - extract_name(&cert, all) - .map(|(name, ours)| { - if ours { - let (pin_policy, touch_policy) = policies(&cert); - (name, pin_policy, touch_policy) + extract_name_and_version(&cert, all) + .map(|(name, version)| { + let (pin_policy, touch_policy) = if version.is_some() { + policies(&cert) } else { // We can extract the PIN and touch policies via an attestation. This // is slow, but the user has asked for all compatible keys, so... - let (pin_policy, touch_policy) = - yubikey::piv::attest(yubikey, SlotId::Retired(slot)) - .ok() - .and_then(|buf| { - x509_parser::parse_x509_certificate(&buf) - .map(|(_, c)| policies(&c)) - .ok() - }) - .unwrap_or((None, None)); - - (name, pin_policy, touch_policy) - } + yubikey::piv::attest(yubikey, SlotId::Retired(slot)) + .ok() + .and_then(|buf| { + x509_parser::parse_x509_certificate(&buf) + .map(|(_, c)| policies(&c)) + .ok() + }) + .unwrap_or((None, None)) + }; + (name, version, pin_policy, touch_policy) }) - .map(|(name, pin_policy, touch_policy)| Metadata { + .map(|(name, version, pin_policy, touch_policy)| Metadata { serial: yubikey.serial(), slot, name, + version, created: cert .validity() .not_before @@ -183,6 +194,19 @@ impl Metadata { touch_policy, }) } + + /// Returns `true` if this identity was generated with an `age-plugin-yubikey` version + /// before `p256tag` was added (and became the default). + pub(crate) fn is_pre_p256tag(&self) -> bool { + self.version + .as_ref() + .and_then(|version| version.split_once('.')) + .and_then(|(major, rest)| rest.split_once('.').map(|(minor, _)| (major, minor))) + .is_some_and(|(major, minor)| { + // `p256tag` added in v0.6.0 + major == "0" && minor.parse::().is_ok_and(|minor| minor < 6) + }) + } } impl fmt::Display for Metadata { @@ -204,19 +228,29 @@ impl fmt::Display for Metadata { } pub(crate) fn print_identity(stub: Stub, recipient: Recipient, metadata: Metadata) { + let legacy_recipient = recipient.legacy_recipient(&metadata); let recipient = recipient.to_string(); if !console::user_attended() { let recipient = recipient.as_str(); eprintln!("{}", fl!("print-recipient", recipient = recipient)); } + let identity = if let Some(legacy_recipient) = legacy_recipient { + format!( + "{}\n{stub}", + fl!("yubikey-legacy-recipient", recipient = legacy_recipient), + ) + } else { + stub.to_string() + }; + println!( "{}", fl!( "yubikey-identity", yubikey_metadata = metadata.to_string(), recipient = recipient, - identity = stub.to_string(), + identity = identity, ) ); }