From 3ecd1fd71833d038c0876559ce39012beee24764 Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Mon, 8 Dec 2025 19:43:23 +0200 Subject: [PATCH 01/15] Add working implement 2FA Signed-off-by: Cristian Andrei --- Cargo.lock | 1847 +++++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 4 + src/auth.rs | 133 ++++ src/main.rs | 1 + src/run.rs | 156 ++--- 5 files changed, 2017 insertions(+), 124 deletions(-) create mode 100644 src/auth.rs diff --git a/Cargo.lock b/Cargo.lock index 8d2d0a9e..e1dede57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,6 +43,15 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anes" version = "0.1.6" @@ -362,6 +371,24 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -464,7 +491,7 @@ version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38aa5c627cd7706490e5b003d685f8b9d69bc343b1a00b9fdd01e75fdf6827cf" dependencies = [ - "darling", + "darling 0.20.10", "ident_case", "prettyplease", "proc-macro2", @@ -515,6 +542,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "0.1.10" @@ -533,6 +566,20 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -616,6 +663,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -625,6 +682,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -641,6 +704,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -741,6 +814,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -761,14 +846,51 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "darling" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.10", + "darling_macro 0.20.10", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] @@ -785,17 +907,53 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.90", +] + [[package]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ - "darling_core", + "darling_core 0.20.10", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", "quote", "syn 2.0.90", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.11" @@ -803,6 +961,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", + "serde", ] [[package]] @@ -823,16 +982,102 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "enumflags2" version = "0.7.10" @@ -934,6 +1179,22 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "flume" version = "0.11.0" @@ -958,6 +1219,15 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fuse3" version = "0.8.1" @@ -1077,6 +1347,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1098,6 +1369,36 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.7.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.4.1" @@ -1108,6 +1409,12 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.15.2" @@ -1171,71 +1478,377 @@ dependencies = [ ] [[package]] -name = "ident_case" -version = "1.0.1" +name = "http" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] [[package]] -name = "indexmap" -version = "2.7.0" +name = "http" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ - "equivalent", - "hashbrown", + "bytes", + "itoa", ] [[package]] -name = "inout" -version = "0.1.3" +name = "http-body" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ - "block-padding", - "generic-array", + "bytes", + "http 0.2.12", + "pin-project-lite", ] [[package]] -name = "instant" -version = "0.1.13" +name = "http-body" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ - "cfg-if 1.0.0", + "bytes", + "http 1.4.0", ] [[package]] -name = "io-lifetimes" -version = "1.0.11" +name = "http-body-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", + "bytes", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "pin-project-lite", ] [[package]] -name = "is-terminal" -version = "0.4.13" +name = "httparse" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" -dependencies = [ - "hermit-abi 0.4.0", - "libc", - "windows-sys 0.52.0", -] +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] -name = "is_terminal_polyfill" -version = "1.70.1" +name = "httpdate" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] -name = "itertools" -version = "0.10.5" +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.7", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.4.0", + "hyper 1.8.1", + "hyper-util", + "rustls 0.23.35", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower-service", + "webpki-roots 1.0.4", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.8.1", + "pin-project-lite", + "socket2 0.5.7", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", + "serde", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ @@ -1248,6 +1861,28 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if 1.0.0", + "combine", + "jni-sys", + "log", + "thiserror 1.0.64", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "js-sys" version = "0.3.72" @@ -1276,6 +1911,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -1283,6 +1921,12 @@ version = "0.2.160" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0b21006cd1874ae9e650973c565615676dc4a274c965bb0a73796dac838ce4f" +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "linux-keyutils" version = "0.2.4" @@ -1305,6 +1949,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + [[package]] name = "lock_api" version = "0.4.12" @@ -1327,7 +1977,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -1363,6 +2013,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.8.0" @@ -1393,6 +2049,12 @@ dependencies = [ "getrandom", ] +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "nix" version = "0.26.4" @@ -1452,6 +2114,22 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -1515,6 +2193,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", +] + +[[package]] +name = "oauth2" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" +dependencies = [ + "base64 0.13.1", + "chrono", + "getrandom", + "http 0.2.12", + "rand", + "reqwest 0.11.27", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror 1.0.64", + "url", +] + +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.6.0", + "objc2", ] [[package]] @@ -1549,6 +2273,47 @@ version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +[[package]] +name = "openidconnect" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47e80a9cfae4462dd29c41e987edd228971d6565553fbc14b8a11e666d91590" +dependencies = [ + "base64 0.13.1", + "chrono", + "dyn-clone", + "ed25519-dalek", + "hmac", + "http 0.2.12", + "itertools", + "log", + "oauth2", + "p256", + "p384", + "rand", + "rsa", + "serde", + "serde-value", + "serde_derive", + "serde_json", + "serde_path_to_error", + "serde_plain", + "serde_with", + "sha2", + "subtle", + "thiserror 1.0.64", + "url", +] + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -1565,6 +2330,30 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking" version = "2.2.1" @@ -1605,6 +2394,21 @@ dependencies = [ "subtle", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1628,6 +2432,27 @@ dependencies = [ "futures-io", ] +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "plotters" version = "0.3.7" @@ -1687,6 +2512,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1712,6 +2546,15 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -1731,6 +2574,58 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.35", + "socket2 0.5.7", + "thiserror 2.0.6", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +dependencies = [ + "bytes", + "getrandom", + "rand", + "ring", + "rustc-hash", + "rustls 0.23.35", + "rustls-pki-types", + "slab", + "thiserror 2.0.6", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.5.7", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "quote" version = "1.0.37" @@ -1799,6 +2694,26 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "regex" version = "1.11.0" @@ -1851,7 +2766,7 @@ dependencies = [ "argon2", "async-trait", "atomic-write-file", - "base64", + "base64 0.22.1", "bincode", "blake3", "bon", @@ -1867,9 +2782,11 @@ dependencies = [ "lru", "num-format", "okaywal", + "openidconnect", "rand", "rand_chacha", "rand_core", + "reqwest 0.12.12", "retainer", "ring", "rpassword", @@ -1887,6 +2804,92 @@ dependencies = [ "tracing-appender", "tracing-subscriber", "tracing-test", + "url", + "webbrowser", +] + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-rustls 0.24.2", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", + "tokio", + "tokio-rustls 0.24.1", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.25.4", + "winreg", +] + +[[package]] +name = "reqwest" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls 0.27.7", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.35", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tokio-rustls 0.26.4", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.26.11", + "windows-registry", ] [[package]] @@ -1901,6 +2904,16 @@ dependencies = [ "rand", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.12" @@ -1926,6 +2939,26 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rsa" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rtoolbox" version = "0.0.2" @@ -1942,6 +2975,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -1978,6 +3017,81 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.103.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.18" @@ -1999,12 +3113,60 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "secret-service" version = "3.1.0" @@ -2031,7 +3193,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", - "core-foundation", + "core-foundation 0.9.4", "core-foundation-sys", "libc", "security-framework-sys", @@ -2055,18 +3217,38 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2075,14 +3257,35 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_plain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" +dependencies = [ + "serde", ] [[package]] @@ -2096,6 +3299,49 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.7.0", + "schemars 0.9.0", + "schemars 1.1.0", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2154,6 +3400,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "slab" version = "0.4.9" @@ -2198,6 +3454,22 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "static_assertions" version = "1.1.0" @@ -2257,6 +3529,53 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.13.0" @@ -2351,6 +3670,16 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -2361,6 +3690,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.40.0" @@ -2390,6 +3734,26 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls 0.23.35", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.16" @@ -2401,6 +3765,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml_datetime" version = "0.6.8" @@ -2413,11 +3790,38 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap", + "indexmap 2.7.0", "toml_datetime", "winnow", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 1.0.2", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.40" @@ -2523,6 +3927,12 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.17.0" @@ -2552,6 +3962,24 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -2586,6 +4014,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2618,6 +4055,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.95" @@ -2657,6 +4106,56 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97" +dependencies = [ + "core-foundation 0.10.1", + "jni", + "log", + "ndk-context", + "objc2", + "objc2-foundation", + "url", + "web-sys", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.4", +] + +[[package]] +name = "webpki-roots" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" version = "6.0.3" @@ -2700,6 +4199,104 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -2727,6 +4324,21 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -2758,6 +4370,12 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -2770,6 +4388,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -2782,6 +4406,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -2800,6 +4430,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -2812,6 +4448,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -2824,6 +4466,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -2836,6 +4484,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -2857,12 +4511,28 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if 1.0.0", + "windows-sys 0.48.0", +] + [[package]] name = "winsafe" version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + [[package]] name = "xdg-home" version = "1.3.0" @@ -2873,6 +4543,29 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "synstructure", +] + [[package]] name = "zbus" version = "3.15.2" @@ -2960,12 +4653,66 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "zvariant" version = "3.15.2" diff --git a/Cargo.toml b/Cargo.toml index 8484ba24..2fb59e72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,10 @@ subtle = "2.6.1" bon = "3.3.0" shush-rs = "0.1.10" criterion = { version = "0.5.1", features = ["html_reports"] } +openidconnect = { version = "3.5", features = ["reqwest"] } +reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false } +url = "2.5" +webbrowser = "1.0" [target.'cfg(target_os = "linux")'.dependencies] fuse3 = { version = "0.8.1", features = ["tokio-runtime", "unprivileged"] } diff --git a/src/auth.rs b/src/auth.rs new file mode 100644 index 00000000..e26eb37b --- /dev/null +++ b/src/auth.rs @@ -0,0 +1,133 @@ +use anyhow::{Context, Result}; +use openidconnect::core::{CoreClient, CoreProviderMetadata, CoreResponseType}; +use openidconnect::reqwest::async_http_client; +// UPDATED IMPORT: Added OAuth2TokenResponse based on compiler hint +use openidconnect::{ + AuthenticationFlow, ClientId, ClientSecret, CsrfToken, IssuerUrl, Nonce, + RedirectUrl, Scope, RefreshToken, OAuth2TokenResponse, +}; +use std::io::{BufRead, BufReader, Write}; +use std::net::TcpListener; +use url::Url; +use webbrowser; + +/// Authenticates with Google. +/// +/// If `stored_refresh_token` is provided, it tries to use it to silently refresh the session. +/// If that fails or no token is provided, it triggers the full browser flow. +/// +/// Returns the valid Refresh Token string to be saved. +pub async fn authenticate_google( + client_id: String, + client_secret: String, + stored_refresh_token: Option +) -> Result { + + // 1. Setup Client + let google_issuer = IssuerUrl::new("https://accounts.google.com".to_string())?; + + let provider_metadata = CoreProviderMetadata::discover_async(google_issuer, async_http_client) + .await + .context("Failed to discover Google OIDC configuration")?; + + let client = CoreClient::from_provider_metadata( + provider_metadata, + ClientId::new(client_id), + Some(ClientSecret::new(client_secret)), + ) + .set_redirect_uri(RedirectUrl::new("http://localhost:8080".to_string())?); + + // 2. Try Silent Auth (Refresh Token) if available + if let Some(token_str) = stored_refresh_token { + println!("Validating cached Google session..."); + let refresh_token = RefreshToken::new(token_str.clone()); + + let response = client + .exchange_refresh_token(&refresh_token) + .request_async(async_http_client) + .await; + + match response { + Ok(_) => { + println!("Google session is valid. Skipping browser."); + return Ok(token_str); // Return the existing token as it is still valid + }, + Err(_) => { + println!("Cached session expired or invalid. Re-authenticating..."); + // Fall through to browser login + } + } + } + + // 3. Browser Login Flow + println!("Initiating Google 2FA..."); + + let (authorize_url, csrf_state, _nonce) = client + .authorize_url( + AuthenticationFlow::::AuthorizationCode, + CsrfToken::new_random, + Nonce::new_random, + ) + .add_scope(Scope::new("email".to_string())) + .add_scope(Scope::new("profile".to_string())) + // CRITICAL: Request offline access to get a Refresh Token + .add_extra_param("access_type", "offline") + // Force consent to ensure we get a refresh token every time we do the full flow + .add_extra_param("prompt", "consent") + .url(); + + println!("Opening browser for authentication..."); + if webbrowser::open(authorize_url.as_str()).is_err() { + println!("Please open this URL in your browser:\n{}", authorize_url); + } + + let listener = TcpListener::bind("127.0.0.1:8080")?; + + if let Some(stream) = listener.incoming().next() { + let mut stream = stream?; + let code; + let state; + + { + let mut reader = BufReader::new(&stream); + let mut request_line = String::new(); + reader.read_line(&mut request_line)?; + + let redirect_url = request_line.split_whitespace().nth(1).unwrap_or("/"); + let url = Url::parse(&("http://localhost".to_string() + redirect_url))?; + + let code_pair = url.query_pairs().find(|(key, _)| key == "code"); + let state_pair = url.query_pairs().find(|(key, _)| key == "state"); + + if let (Some((_, c)), Some((_, s))) = (code_pair, state_pair) { + code = c.into_owned(); + state = s.into_owned(); + } else { + return Err(anyhow::anyhow!("Failed to retrieve auth code")); + } + } + + let response = "HTTP/1.1 200 OK\r\n\r\nGoogle Auth Successful! You can close this window."; + stream.write_all(response.as_bytes())?; + + if state != *csrf_state.secret() { + return Err(anyhow::anyhow!("CSRF state mismatch")); + } + + let token_response = client + .exchange_code(openidconnect::AuthorizationCode::new(code)) + .request_async(async_http_client) + .await + .context("Failed to exchange auth code for token")?; + + println!("Google Authentication successful."); + + // Extract the Refresh Token to save it + let refresh_token = token_response.refresh_token() + .ok_or_else(|| anyhow::anyhow!("Google did not return a refresh token (check access_type=offline)"))?; + + Ok(refresh_token.secret().clone()) + } else { + Err(anyhow::anyhow!("Failed to receive connection from browser")) + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index d8986e0d..0360d5ce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use anyhow::Result; mod keyring; +mod auth; #[cfg(target_os = "linux")] mod run; diff --git a/src/run.rs b/src/run.rs index 8a71ff10..47f4a44a 100644 --- a/src/run.rs +++ b/src/run.rs @@ -268,49 +268,73 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { let data_dir: String = matches.get_one::("data-dir").unwrap().to_string(); - // when running from IDE we can't read from stdin with rpassword, get it from env var - let mut password = SecretString::from_str( - env::var("RENCFS_PASSWORD") - .unwrap_or_else(|_| String::new()) - .as_str(), - ) - .unwrap(); - if password.expose_secret().is_empty() { - // read password from stdin - print!("Enter password: "); + // --- SECURE CREDENTIAL LOADING --- + let raw_client_id = env::var("GOOGLE_CLIENT_ID").unwrap_or_default(); + let raw_client_secret = env::var("GOOGLE_CLIENT_SECRET").unwrap_or_default(); + + // Sanitize: Remove whitespace AND strictly remove surrounding quotes if present + let google_client_id = raw_client_id.trim().trim_matches('"').trim_matches('\'').to_string(); + let google_client_secret = raw_client_secret.trim().trim_matches('"').trim_matches('\'').to_string(); + + // ------------------------------------------------------------- + // PHASE 1: GOOGLE AUTH (Token Caching) + // ------------------------------------------------------------- + // We check for a stored REFRESH TOKEN for Google, not the master password. + // If the token exists and works, we skip the browser. + + if !google_client_id.is_empty() { + // Try to load Google Refresh Token from Keyring + // FIX: expose_secret() and convert to String, because authenticate_google expects Option + let stored_token = keyring::get("google_refresh_token") + .ok() + .map(|s| s.expose_secret().to_string()); + + // Authenticate (Silent Refresh OR Browser Flow) + let valid_refresh_token = crate::auth::authenticate_google( + google_client_id.clone(), + google_client_secret.clone(), + stored_token + ).await?; + + // Save the valid token back to keyring (in case it was new) + // FIX: Wrap String in SecretString before saving + let secret_token = SecretString::new(Box::new(valid_refresh_token)); + let _ = keyring::save(&secret_token, "google_refresh_token").map_err(|e| { + warn!("Failed to save Google token to keyring: {}", e); + }); + } + + // ------------------------------------------------------------- + // PHASE 2: MASTER PASSWORD (Always Manual) + // ------------------------------------------------------------- + // We do NOT check keyring for this. We always ask the user. + + print!("Enter password: "); + io::stdout().flush().unwrap(); + let p = SecretString::new(Box::new(read_password()?)); + + if !PathBuf::new().join(data_dir.clone()).is_dir() + || fs::read_dir(&data_dir) + .await + .unwrap() + .next_entry() + .await + .unwrap() + .is_none() + { + // first run / empty dir logic: confirm password + print!("Confirm password: "); io::stdout().flush().unwrap(); - password = SecretString::new(Box::new(read_password()?)); - - if !PathBuf::new().join(data_dir.clone()).is_dir() - || fs::read_dir(&data_dir) - .await - .unwrap() - .next_entry() - .await - .unwrap() - .is_none() - { - // first run, ask to confirm password - print!("Confirm password: "); - io::stdout().flush().unwrap(); - let confirm_password = SecretString::new(Box::new(read_password()?)); - if password.expose_secret() != confirm_password.expose_secret() { - error!("Passwords do not match"); - return Err(ExitStatusError::Failure(1).into()); - } + let confirm_password = SecretString::new(Box::new(read_password()?)); + if p.expose_secret() != confirm_password.expose_secret() { + error!("Passwords do not match"); + return Err(ExitStatusError::Failure(1).into()); } } - // save password in keyring - info!("Save password in keyring"); - let res = keyring::save(&password, "password").map_err(|err| { - warn!(err = %err); - }); - if res.is_err() { - // maybe we don't have a security manager, keep it in mem - unsafe { - warn!("Cannot save password in keyring, keep it in memory"); - PASS = Some(password.clone()); - } + + // Keep password in memory for the mount lifecycle + unsafe { + PASS = Some(p.clone()); } if matches.get_flag("umount-on-start") { @@ -320,50 +344,40 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { }); } - struct PasswordProviderImpl {} - #[allow(clippy::items_after_statements)] - #[allow(static_mut_refs)] + struct PasswordProviderImpl { + pass: SecretString, + } impl PasswordProvider for PasswordProviderImpl { fn get_password(&self) -> Option { - unsafe { - if PASS.is_some() { - info!("Get password from memory"); - PASS.clone() - } else { - info!("Get password from keyring"); - keyring::get("password") - .map_err(|err| { - error!(err = %err, "cannot get password from keyring"); - err - }) - .ok() - } - } + Some(self.pass.clone()) } } + let mount_point = mount::create_mount_point( Path::new(&mountpoint), Path::new(&data_dir), - Box::new(PasswordProviderImpl {}), + Box::new(PasswordProviderImpl { pass: p }), cipher, matches.get_flag("allow-root"), matches.get_flag("allow-other"), matches.get_flag("read-only"), ); + let mount_handle = mount_point.mount().await.map_err(|err| { error!(err = %err); ExitStatusError::Failure(1) })?; let mount_handle = Arc::new(Mutex::new(Some(Some(mount_handle)))); let mount_handle_clone = mount_handle.clone(); - // cleanup on process kill + set_handler(move || { - // can't use tracing methods here as guard cannot be dropper to flush content before we exit eprintln!("Received signal to exit"); let mut status: Option = None; - remove_pass(); + + // Cleanup memory on exit + remove_pass(); + eprintln!("Unmounting {mountpoint}"); - // create new tokio runtime let rt = tokio::runtime::Builder::new_current_thread() .enable_all() .build() @@ -406,18 +420,12 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { } #[allow(static_mut_refs)] +#[allow(dead_code)] fn remove_pass() { unsafe { - if PASS.is_none() { - info!("Delete password from keyring"); - keyring::remove("password") - .map_err(|err| { - error!(err = %err); - }) - .ok(); - } else { - info!("Remove password from memory"); - PASS = None; - } + // We only clear memory now. We do NOT touch the keyring + // because we never saved the password there in the first place. + info!("Remove password from memory"); + PASS = None; } -} +} \ No newline at end of file From 63e8699e44b05382af97e96208053f71bdcbd8c0 Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Tue, 9 Dec 2025 13:39:27 +0200 Subject: [PATCH 02/15] Refine implementation Signed-off-by: Cristian Andrei --- src/run.rs | 137 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 79 insertions(+), 58 deletions(-) diff --git a/src/run.rs b/src/run.rs index 47f4a44a..bdab47d9 100644 --- a/src/run.rs +++ b/src/run.rs @@ -16,6 +16,7 @@ use tokio::{fs, task}; use tracing::{error, info, warn, Level}; use crate::keyring; +// use crate::auth; // Accessed via crate::auth::... below use rencfs::crypto::Cipher; use rencfs::encryptedfs::{EncryptedFs, FsError, PasswordProvider}; use rencfs::mount::MountPoint; @@ -268,73 +269,77 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { let data_dir: String = matches.get_one::("data-dir").unwrap().to_string(); - // --- SECURE CREDENTIAL LOADING --- + // retrieve credentials let raw_client_id = env::var("GOOGLE_CLIENT_ID").unwrap_or_default(); let raw_client_secret = env::var("GOOGLE_CLIENT_SECRET").unwrap_or_default(); - // Sanitize: Remove whitespace AND strictly remove surrounding quotes if present + // refine credentials let google_client_id = raw_client_id.trim().trim_matches('"').trim_matches('\'').to_string(); let google_client_secret = raw_client_secret.trim().trim_matches('"').trim_matches('\'').to_string(); - // ------------------------------------------------------------- - // PHASE 1: GOOGLE AUTH (Token Caching) - // ------------------------------------------------------------- - // We check for a stored REFRESH TOKEN for Google, not the master password. - // If the token exists and works, we skip the browser. - if !google_client_id.is_empty() { - // Try to load Google Refresh Token from Keyring - // FIX: expose_secret() and convert to String, because authenticate_google expects Option + // try to load token from keyring let stored_token = keyring::get("google_refresh_token") .ok() .map(|s| s.expose_secret().to_string()); - // Authenticate (Silent Refresh OR Browser Flow) + // authenticate with Google let valid_refresh_token = crate::auth::authenticate_google( google_client_id.clone(), google_client_secret.clone(), stored_token ).await?; - // Save the valid token back to keyring (in case it was new) - // FIX: Wrap String in SecretString before saving + // save the valid token back to keyring let secret_token = SecretString::new(Box::new(valid_refresh_token)); let _ = keyring::save(&secret_token, "google_refresh_token").map_err(|e| { warn!("Failed to save Google token to keyring: {}", e); }); } - // ------------------------------------------------------------- - // PHASE 2: MASTER PASSWORD (Always Manual) - // ------------------------------------------------------------- - // We do NOT check keyring for this. We always ask the user. - - print!("Enter password: "); - io::stdout().flush().unwrap(); - let p = SecretString::new(Box::new(read_password()?)); - - if !PathBuf::new().join(data_dir.clone()).is_dir() - || fs::read_dir(&data_dir) - .await - .unwrap() - .next_entry() - .await - .unwrap() - .is_none() - { - // first run / empty dir logic: confirm password - print!("Confirm password: "); + // when running from IDE we can't read from stdin with rpassword, get it from env var + let mut password = SecretString::from_str( + env::var("RENCFS_PASSWORD") + .unwrap_or_else(|_| String::new()) + .as_str(), + ) + .unwrap(); + if password.expose_secret().is_empty() { + // read password from stdin + print!("Enter password: "); io::stdout().flush().unwrap(); - let confirm_password = SecretString::new(Box::new(read_password()?)); - if p.expose_secret() != confirm_password.expose_secret() { - error!("Passwords do not match"); - return Err(ExitStatusError::Failure(1).into()); + password = SecretString::new(Box::new(read_password()?)); + + if !PathBuf::new().join(data_dir.clone()).is_dir() + || fs::read_dir(&data_dir) + .await + .unwrap() + .next_entry() + .await + .unwrap() + .is_none() + { + // first run, ask to confirm password + print!("Confirm password: "); + io::stdout().flush().unwrap(); + let confirm_password = SecretString::new(Box::new(read_password()?)); + if password.expose_secret() != confirm_password.expose_secret() { + error!("Passwords do not match"); + return Err(ExitStatusError::Failure(1).into()); + } } } - - // Keep password in memory for the mount lifecycle - unsafe { - PASS = Some(p.clone()); + // save password in keyring + info!("Save password in keyring"); + let res = keyring::save(&password, "password").map_err(|err| { + warn!(err = %err); + }); + if res.is_err() { + // maybe we don't have a security manager, keep it in mem + unsafe { + warn!("Cannot save password in keyring, keep it in memory"); + PASS = Some(password.clone()); + } } if matches.get_flag("umount-on-start") { @@ -344,40 +349,50 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { }); } - struct PasswordProviderImpl { - pass: SecretString, - } + struct PasswordProviderImpl {} + #[allow(clippy::items_after_statements)] + #[allow(static_mut_refs)] impl PasswordProvider for PasswordProviderImpl { fn get_password(&self) -> Option { - Some(self.pass.clone()) + unsafe { + if PASS.is_some() { + info!("Get password from memory"); + PASS.clone() + } else { + info!("Get password from keyring"); + keyring::get("password") + .map_err(|err| { + error!(err = %err, "cannot get password from keyring"); + err + }) + .ok() + } + } } } - let mount_point = mount::create_mount_point( Path::new(&mountpoint), Path::new(&data_dir), - Box::new(PasswordProviderImpl { pass: p }), + Box::new(PasswordProviderImpl {}), cipher, matches.get_flag("allow-root"), matches.get_flag("allow-other"), matches.get_flag("read-only"), ); - let mount_handle = mount_point.mount().await.map_err(|err| { error!(err = %err); ExitStatusError::Failure(1) })?; let mount_handle = Arc::new(Mutex::new(Some(Some(mount_handle)))); let mount_handle_clone = mount_handle.clone(); - + // cleanup on process kill set_handler(move || { + // can't use tracing methods here as guard cannot be dropper to flush content before we exit eprintln!("Received signal to exit"); let mut status: Option = None; - - // Cleanup memory on exit - remove_pass(); - + remove_pass(); eprintln!("Unmounting {mountpoint}"); + // create new tokio runtime let rt = tokio::runtime::Builder::new_current_thread() .enable_all() .build() @@ -420,12 +435,18 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { } #[allow(static_mut_refs)] -#[allow(dead_code)] fn remove_pass() { unsafe { - // We only clear memory now. We do NOT touch the keyring - // because we never saved the password there in the first place. - info!("Remove password from memory"); - PASS = None; + if PASS.is_none() { + info!("Delete password from keyring"); + keyring::remove("password") + .map_err(|err| { + error!(err = %err); + }) + .ok(); + } else { + info!("Remove password from memory"); + PASS = None; + } } } \ No newline at end of file From 425bc7ed43eed9a03eb8236eed2bf51ad8c1c560 Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Fri, 12 Dec 2025 17:04:54 +0200 Subject: [PATCH 03/15] Add email recognition --- src/auth.rs | 79 +++++++++++++++++++++++++++-------------------------- src/run.rs | 21 +++++++++----- 2 files changed, 54 insertions(+), 46 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index e26eb37b..1ebff062 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,10 +1,11 @@ +// src/auth.rs use anyhow::{Context, Result}; use openidconnect::core::{CoreClient, CoreProviderMetadata, CoreResponseType}; use openidconnect::reqwest::async_http_client; -// UPDATED IMPORT: Added OAuth2TokenResponse based on compiler hint +// UPDATED IMPORT: Added TokenResponse (for id_token() method) use openidconnect::{ AuthenticationFlow, ClientId, ClientSecret, CsrfToken, IssuerUrl, Nonce, - RedirectUrl, Scope, RefreshToken, OAuth2TokenResponse, + RedirectUrl, Scope, RefreshToken, OAuth2TokenResponse, TokenResponse, }; use std::io::{BufRead, BufReader, Write}; use std::net::TcpListener; @@ -13,15 +14,18 @@ use webbrowser; /// Authenticates with Google. /// -/// If `stored_refresh_token` is provided, it tries to use it to silently refresh the session. -/// If that fails or no token is provided, it triggers the full browser flow. +/// Triggers a browser flow to authenticate the user. +/// Enforces strict user presence (Passkey/2FA) via `max_age=0`. /// -/// Returns the valid Refresh Token string to be saved. +/// If `expected_sub` is provided, it verifies that the authenticated user's +/// Subject ID matches the expected one. +/// +/// Returns a tuple: (Refresh Token, Subject ID) pub async fn authenticate_google( client_id: String, client_secret: String, - stored_refresh_token: Option -) -> Result { + expected_sub: Option +) -> Result<(String, String)> { // 1. Setup Client let google_issuer = IssuerUrl::new("https://accounts.google.com".to_string())?; @@ -37,32 +41,11 @@ pub async fn authenticate_google( ) .set_redirect_uri(RedirectUrl::new("http://localhost:8080".to_string())?); - // 2. Try Silent Auth (Refresh Token) if available - if let Some(token_str) = stored_refresh_token { - println!("Validating cached Google session..."); - let refresh_token = RefreshToken::new(token_str.clone()); - - let response = client - .exchange_refresh_token(&refresh_token) - .request_async(async_http_client) - .await; - - match response { - Ok(_) => { - println!("Google session is valid. Skipping browser."); - return Ok(token_str); // Return the existing token as it is still valid - }, - Err(_) => { - println!("Cached session expired or invalid. Re-authenticating..."); - // Fall through to browser login - } - } - } - - // 3. Browser Login Flow - println!("Initiating Google 2FA..."); + // 2. Browser Login Flow + println!("Initiating Google 2FA (Passkey Check)..."); - let (authorize_url, csrf_state, _nonce) = client + // We keep the `nonce` to verify the ID token later + let (authorize_url, csrf_state, nonce) = client .authorize_url( AuthenticationFlow::::AuthorizationCode, CsrfToken::new_random, @@ -70,10 +53,9 @@ pub async fn authenticate_google( ) .add_scope(Scope::new("email".to_string())) .add_scope(Scope::new("profile".to_string())) - // CRITICAL: Request offline access to get a Refresh Token .add_extra_param("access_type", "offline") - // Force consent to ensure we get a refresh token every time we do the full flow - .add_extra_param("prompt", "consent") + .add_extra_param("prompt", "login") // Force login UI + .add_extra_param("max_age", "0") // Force fresh authentication (Passkey) .url(); println!("Opening browser for authentication..."); @@ -120,13 +102,32 @@ pub async fn authenticate_google( .await .context("Failed to exchange auth code for token")?; - println!("Google Authentication successful."); + // 3. Verify ID Token and Check Subject + let id_token = token_response.id_token() + .ok_or_else(|| anyhow::anyhow!("Google did not return an ID token"))?; + + let claims = id_token.claims(&client.id_token_verifier(), &nonce) + .context("Failed to verify ID token claims")?; + + let subject = claims.subject().to_string(); + + if let Some(expected) = &expected_sub { + if *expected != subject { + return Err(anyhow::anyhow!( + "Security Alert: Authenticated user ({}) does not match the owner ({})!", + subject, expected + )); + } + } else { + println!("First run detected. Binding this file system to user ID: {}", subject); + } + + println!("Google Authentication and Identity Verification successful."); - // Extract the Refresh Token to save it let refresh_token = token_response.refresh_token() - .ok_or_else(|| anyhow::anyhow!("Google did not return a refresh token (check access_type=offline)"))?; + .ok_or_else(|| anyhow::anyhow!("Google did not return a refresh token"))?; - Ok(refresh_token.secret().clone()) + Ok((refresh_token.secret().clone(), subject)) } else { Err(anyhow::anyhow!("Failed to receive connection from browser")) } diff --git a/src/run.rs b/src/run.rs index bdab47d9..1b957f20 100644 --- a/src/run.rs +++ b/src/run.rs @@ -273,24 +273,31 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { let raw_client_id = env::var("GOOGLE_CLIENT_ID").unwrap_or_default(); let raw_client_secret = env::var("GOOGLE_CLIENT_SECRET").unwrap_or_default(); - // refine credentials let google_client_id = raw_client_id.trim().trim_matches('"').trim_matches('\'').to_string(); let google_client_secret = raw_client_secret.trim().trim_matches('"').trim_matches('\'').to_string(); if !google_client_id.is_empty() { - // try to load token from keyring - let stored_token = keyring::get("google_refresh_token") + // Try to load the known Owner ID (sub) from keyring + let stored_sub = keyring::get("google_owner_sub") .ok() .map(|s| s.expose_secret().to_string()); - // authenticate with Google - let valid_refresh_token = crate::auth::authenticate_google( + // Authenticate with Google (Passkey + Identity Check) + let (valid_refresh_token, subject) = crate::auth::authenticate_google( google_client_id.clone(), google_client_secret.clone(), - stored_token + stored_sub.clone() // Pass the expected sub (or None if first run) ).await?; - // save the valid token back to keyring + // If this was the first run (no stored sub), save the verified sub as the owner + if stored_sub.is_none() { + let secret_sub = SecretString::new(Box::new(subject)); + let _ = keyring::save(&secret_sub, "google_owner_sub").map_err(|e| { + warn!("Failed to save Google Owner ID to keyring: {}", e); + }); + } + + // Save the valid refresh token back to keyring let secret_token = SecretString::new(Box::new(valid_refresh_token)); let _ = keyring::save(&secret_token, "google_refresh_token").map_err(|e| { warn!("Failed to save Google token to keyring: {}", e); From 0617404e273b086d04b55a83700c2eb8a7e66bf7 Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Fri, 12 Dec 2025 18:52:18 +0200 Subject: [PATCH 04/15] Add implementation for new param Signed-off-by: Cristian Andrei --- src/run.rs | 66 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/src/run.rs b/src/run.rs index 1b957f20..2eb3f33a 100644 --- a/src/run.rs +++ b/src/run.rs @@ -153,6 +153,14 @@ fn get_cli_args() -> ArgMatches { .requires("data-dir") .help("If we should try to umount the mountpoint before starting the FUSE server. This can be useful when the previous run crashed or was forced kll and the mountpoint is still mounted."), ) + .arg( + Arg::new("init-2fa") + .long("init-2fa") + .action(ArgAction::SetTrue) + .requires("mount-point") + .requires("data-dir") + .help("Initialize Google 2FA for this filesystem. Required only for the first run to bind the user."), + ) .arg( Arg::new("allow-root") .long("allow-root") @@ -269,39 +277,47 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { let data_dir: String = matches.get_one::("data-dir").unwrap().to_string(); - // retrieve credentials - let raw_client_id = env::var("GOOGLE_CLIENT_ID").unwrap_or_default(); - let raw_client_secret = env::var("GOOGLE_CLIENT_SECRET").unwrap_or_default(); + // 1. Check Keyring for existing 2FA registration + // We attempt to fetch the ID first. If it exists, we MUST enforce 2FA. + let stored_sub = match keyring::get("google_owner_sub") { + Ok(s) => Some(s.expose_secret().to_string()), + Err(_) => None, // Entry not found or keyring unavailable + }; - let google_client_id = raw_client_id.trim().trim_matches('"').trim_matches('\'').to_string(); - let google_client_secret = raw_client_secret.trim().trim_matches('"').trim_matches('\'').to_string(); + let init_2fa = matches.get_flag("init-2fa"); - if !google_client_id.is_empty() { - // Try to load the known Owner ID (sub) from keyring - let stored_sub = keyring::get("google_owner_sub") - .ok() - .map(|s| s.expose_secret().to_string()); - - // Authenticate with Google (Passkey + Identity Check) - let (valid_refresh_token, subject) = crate::auth::authenticate_google( - google_client_id.clone(), - google_client_secret.clone(), - stored_sub.clone() // Pass the expected sub (or None if first run) + // 2. Decide if we need to Authenticate + if stored_sub.is_some() || init_2fa { + // Retrieve credentials + let raw_client_id = env::var("GOOGLE_CLIENT_ID").unwrap_or_default(); + let raw_client_secret = env::var("GOOGLE_CLIENT_SECRET").unwrap_or_default(); + let google_client_id = raw_client_id.trim().trim_matches('"').trim_matches('\'').to_string(); + let google_client_secret = raw_client_secret.trim().trim_matches('"').trim_matches('\'').to_string(); + + // 3. Fail if credentials are missing but 2FA is required + if google_client_id.is_empty() || google_client_secret.is_empty() { + return Err(anyhow::anyhow!( + "CRITICAL: This filesystem (or your user profile) is bound to Google 2FA, but GOOGLE_CLIENT_ID/SECRET are missing.\n\ + You must provide these environment variables to authenticate." + )); + } + + // 4. Authenticate + let (_valid_refresh_token, subject) = crate::auth::authenticate_google( + google_client_id, + google_client_secret, + stored_sub.clone() ).await?; - // If this was the first run (no stored sub), save the verified sub as the owner + // 5. Persist the ID if this was the first run if stored_sub.is_none() { let secret_sub = SecretString::new(Box::new(subject)); - let _ = keyring::save(&secret_sub, "google_owner_sub").map_err(|e| { + if let Err(e) = keyring::save(&secret_sub, "google_owner_sub") { warn!("Failed to save Google Owner ID to keyring: {}", e); - }); + } else { + info!("2FA Identity saved. Future mounts will strictly require Google Auth."); + } } - - // Save the valid refresh token back to keyring - let secret_token = SecretString::new(Box::new(valid_refresh_token)); - let _ = keyring::save(&secret_token, "google_refresh_token").map_err(|e| { - warn!("Failed to save Google token to keyring: {}", e); - }); } // when running from IDE we can't read from stdin with rpassword, get it from env var From 62a681ff16703609477c12d4991fa978fe590922 Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Fri, 12 Dec 2025 19:58:16 +0200 Subject: [PATCH 05/15] Add encrypted storage of Subject ID Signed-off-by: Cristian Andrei --- src/auth.rs | 18 ++++----- src/encryptedfs.rs | 79 +++++++++++++++++++++++++++++++++++++++ src/run.rs | 93 ++++++++++++++++++++++------------------------ 3 files changed, 131 insertions(+), 59 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index 1ebff062..ab420e89 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -15,10 +15,9 @@ use webbrowser; /// Authenticates with Google. /// /// Triggers a browser flow to authenticate the user. -/// Enforces strict user presence (Passkey/2FA) via `max_age=0`. /// /// If `expected_sub` is provided, it verifies that the authenticated user's -/// Subject ID matches the expected one. +/// subject ID matches the expected one. /// /// Returns a tuple: (Refresh Token, Subject ID) pub async fn authenticate_google( @@ -27,7 +26,6 @@ pub async fn authenticate_google( expected_sub: Option ) -> Result<(String, String)> { - // 1. Setup Client let google_issuer = IssuerUrl::new("https://accounts.google.com".to_string())?; let provider_metadata = CoreProviderMetadata::discover_async(google_issuer, async_http_client) @@ -41,10 +39,9 @@ pub async fn authenticate_google( ) .set_redirect_uri(RedirectUrl::new("http://localhost:8080".to_string())?); - // 2. Browser Login Flow println!("Initiating Google 2FA (Passkey Check)..."); - // We keep the `nonce` to verify the ID token later + // keep the `nonce` to verify the ID token later let (authorize_url, csrf_state, nonce) = client .authorize_url( AuthenticationFlow::::AuthorizationCode, @@ -102,7 +99,7 @@ pub async fn authenticate_google( .await .context("Failed to exchange auth code for token")?; - // 3. Verify ID Token and Check Subject + // verify ID token and check subject let id_token = token_response.id_token() .ok_or_else(|| anyhow::anyhow!("Google did not return an ID token"))?; @@ -119,15 +116,16 @@ pub async fn authenticate_google( )); } } else { - println!("First run detected. Binding this file system to user ID: {}", subject); + println!("Authenticated as user ID: {}", subject); } println!("Google Authentication and Identity Verification successful."); - let refresh_token = token_response.refresh_token() - .ok_or_else(|| anyhow::anyhow!("Google did not return a refresh token"))?; + let refresh_token = token_response.refresh_token() + .map(|t| t.secret().clone()) + .unwrap_or_else(String::new); - Ok((refresh_token.secret().clone(), subject)) + Ok((refresh_token, subject)) } else { Err(anyhow::anyhow!("Failed to receive connection from browser")) } diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index 7c2da5a4..9c5d8545 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -41,6 +41,7 @@ pub(crate) const CONTENTS_DIR: &str = "contents"; pub(crate) const SECURITY_DIR: &str = "security"; pub(crate) const KEY_ENC_FILENAME: &str = "key.enc"; pub(crate) const KEY_SALT_FILENAME: &str = "key.salt"; +pub(crate) const IDENTITY_FILENAME: &str = "identity.enc"; pub(crate) const LS_DIR: &str = "ls"; pub(crate) const HASH_DIR: &str = "hash"; @@ -640,6 +641,84 @@ impl EncryptedFs { Ok(arc) } + // checks if the filesystem at the given path is bound to a specific identity. + pub fn is_identity_bound(data_dir: &Path) -> bool { + data_dir.join(SECURITY_DIR).join(IDENTITY_FILENAME).exists() + } + + // decrypts and returns the bound identity (subject ID) if it exists. + pub fn get_bound_identity( + data_dir: &Path, + password: &SecretString, + cipher: Cipher, + ) -> FsResult> { + let identity_path = data_dir.join(SECURITY_DIR).join(IDENTITY_FILENAME); + if !identity_path.exists() { + return Ok(None); + } + + let key_path = data_dir.join(SECURITY_DIR).join(KEY_ENC_FILENAME); + let salt_path = data_dir.join(SECURITY_DIR).join(KEY_SALT_FILENAME); + + let key = read_or_create_key(&key_path, &salt_path, password, cipher)?; + + let reader = crypto::create_read(File::open(&identity_path)?, cipher, &key); + let stored_sub: String = bincode::deserialize_from(reader) + .map_err(|_| FsError::Other("Failed to decrypt identity. Wrong password?"))?; + + Ok(Some(stored_sub)) + } + + // Verifies the identity of the user against the bound Google Account ID (sub). + // If no identity is bound, it encrypts and stores the provided `subject` to bind the vault. + pub async fn verify_or_bind_identity( + data_dir: &Path, + password: &SecretString, + cipher: Cipher, + subject: &str, + ) -> FsResult<()> { + // Ensure the directory structure exists + ensure_structure_created(&data_dir.to_path_buf()).await?; + + let key_path = data_dir.join(SECURITY_DIR).join(KEY_ENC_FILENAME); + let salt_path = data_dir.join(SECURITY_DIR).join(KEY_SALT_FILENAME); + let identity_path = data_dir.join(SECURITY_DIR).join(IDENTITY_FILENAME); + + // Derive/Load the master key using the provided password + let key = read_or_create_key(&key_path, &salt_path, password, cipher)?; + + if identity_path.exists() { + info!("Identity file found. Verifying 2FA Identity..."); + + // Decrypt the stored ID + let reader = crypto::create_read(File::open(&identity_path)?, cipher, &key); + let stored_sub: String = bincode::deserialize_from(reader) + .map_err(|e| { + error!("Failed to deserialize identity: {}", e); + FsError::Other("Failed to read identity file. Password might be wrong or file corrupted.") + })?; + + if stored_sub != subject { + error!("CRITICAL: Identity mismatch! Vault is bound to user '{}', but current user is '{}'", stored_sub, subject); + return Err(FsError::Other("Access Denied: The Google Account used for 2FA does not match the vault owner.")); + } + info!("Identity verified successfully."); + } else { + info!("No identity file found. Binding vault to Google Account ID: {}", subject); + + // Encrypt and save the ID + crypto::atomic_serialize_encrypt_into( + &identity_path, + &subject.to_string(), + cipher, + &key + )?; + info!("Vault successfully bound to user."); + } + + Ok(()) + } + pub fn exists(&self, ino: u64) -> bool { self.ino_file(ino).is_file() } diff --git a/src/run.rs b/src/run.rs index 2eb3f33a..d2e7dbd3 100644 --- a/src/run.rs +++ b/src/run.rs @@ -16,7 +16,7 @@ use tokio::{fs, task}; use tracing::{error, info, warn, Level}; use crate::keyring; -// use crate::auth; // Accessed via crate::auth::... below + use rencfs::crypto::Cipher; use rencfs::encryptedfs::{EncryptedFs, FsError, PasswordProvider}; use rencfs::mount::MountPoint; @@ -24,6 +24,7 @@ use rencfs::{log, mount}; static mut PASS: Option = None; + #[derive(Debug, Error)] enum ExitStatusError { #[error("exit with status {0}")] @@ -53,6 +54,7 @@ pub(super) async fn run() -> Result<()> { }) }) .await; + match res { Ok(Ok(Ok(()))) => Ok(()), Ok(Ok(Err(err))) => { @@ -276,63 +278,21 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { .to_string(); let data_dir: String = matches.get_one::("data-dir").unwrap().to_string(); - - // 1. Check Keyring for existing 2FA registration - // We attempt to fetch the ID first. If it exists, we MUST enforce 2FA. - let stored_sub = match keyring::get("google_owner_sub") { - Ok(s) => Some(s.expose_secret().to_string()), - Err(_) => None, // Entry not found or keyring unavailable - }; - - let init_2fa = matches.get_flag("init-2fa"); - - // 2. Decide if we need to Authenticate - if stored_sub.is_some() || init_2fa { - // Retrieve credentials - let raw_client_id = env::var("GOOGLE_CLIENT_ID").unwrap_or_default(); - let raw_client_secret = env::var("GOOGLE_CLIENT_SECRET").unwrap_or_default(); - let google_client_id = raw_client_id.trim().trim_matches('"').trim_matches('\'').to_string(); - let google_client_secret = raw_client_secret.trim().trim_matches('"').trim_matches('\'').to_string(); - - // 3. Fail if credentials are missing but 2FA is required - if google_client_id.is_empty() || google_client_secret.is_empty() { - return Err(anyhow::anyhow!( - "CRITICAL: This filesystem (or your user profile) is bound to Google 2FA, but GOOGLE_CLIENT_ID/SECRET are missing.\n\ - You must provide these environment variables to authenticate." - )); - } - - // 4. Authenticate - let (_valid_refresh_token, subject) = crate::auth::authenticate_google( - google_client_id, - google_client_secret, - stored_sub.clone() - ).await?; - - // 5. Persist the ID if this was the first run - if stored_sub.is_none() { - let secret_sub = SecretString::new(Box::new(subject)); - if let Err(e) = keyring::save(&secret_sub, "google_owner_sub") { - warn!("Failed to save Google Owner ID to keyring: {}", e); - } else { - info!("2FA Identity saved. Future mounts will strictly require Google Auth."); - } - } - } - - // when running from IDE we can't read from stdin with rpassword, get it from env var + let data_path = Path::new(&data_dir); + let mut password = SecretString::from_str( env::var("RENCFS_PASSWORD") .unwrap_or_else(|_| String::new()) .as_str(), ) .unwrap(); + if password.expose_secret().is_empty() { - // read password from stdin print!("Enter password: "); io::stdout().flush().unwrap(); password = SecretString::new(Box::new(read_password()?)); + // confirm password logic for new directories if !PathBuf::new().join(data_dir.clone()).is_dir() || fs::read_dir(&data_dir) .await @@ -342,7 +302,6 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { .unwrap() .is_none() { - // first run, ask to confirm password print!("Confirm password: "); io::stdout().flush().unwrap(); let confirm_password = SecretString::new(Box::new(read_password()?)); @@ -352,19 +311,55 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { } } } + // save password in keyring info!("Save password in keyring"); let res = keyring::save(&password, "password").map_err(|err| { warn!(err = %err); }); if res.is_err() { - // maybe we don't have a security manager, keep it in mem unsafe { warn!("Cannot save password in keyring, keep it in memory"); PASS = Some(password.clone()); } } + // check 2FA Requirement + let is_bound = EncryptedFs::is_identity_bound(data_path); + let init_2fa = matches.get_flag("init-2fa"); + + if is_bound || init_2fa { + info!("2FA is required. Checking identity..."); + + let raw_client_id = env::var("GOOGLE_CLIENT_ID").unwrap_or_default(); + let raw_client_secret = env::var("GOOGLE_CLIENT_SECRET").unwrap_or_default(); + let google_client_id = raw_client_id.trim().trim_matches('"').trim_matches('\'').to_string(); + let google_client_secret = raw_client_secret.trim().trim_matches('"').trim_matches('\'').to_string(); + + if google_client_id.is_empty() || google_client_secret.is_empty() { + return Err(anyhow::anyhow!("CRITICAL: GOOGLE_CLIENT_ID/SECRET missing.")); + } + + // determine expected User + let expected_sub = if is_bound { + EncryptedFs::get_bound_identity(data_path, &password, cipher)? + } else { + None + }; + + // authenticate + let (_refresh_token, subject) = crate::auth::authenticate_google( + google_client_id, + google_client_secret, + expected_sub + ).await?; + + // bind if this was a new initialization + if !is_bound { + EncryptedFs::verify_or_bind_identity(data_path, &password, cipher, &subject).await?; + } + } + if matches.get_flag("umount-on-start") { let _ = mount::umount(mountpoint.as_str()).map_err(|err| { warn!("Cannot umount, maybe it was not mounted: {err}"); From a87e02d514bf99042121f1ab14bdd16370f1638a Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Fri, 12 Dec 2025 20:57:45 +0200 Subject: [PATCH 06/15] Add 2fa with authenticator --- Cargo.lock | 265 ++++++++++++++++++++++++++++++++++++++++----- Cargo.toml | 1 + src/encryptedfs.rs | 46 ++++++++ src/run.rs | 113 +++++++++++++------ 4 files changed, 363 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1dede57..fb168917 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -347,7 +347,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23e32862ecc63d580f4a5e1436a685f51e0629caeb7a7933e4f017d5e2099e13" dependencies = [ "nix 0.29.0", - "rand", + "rand 0.8.5", ] [[package]] @@ -377,6 +377,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base32" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" + [[package]] name = "base64" version = "0.13.1" @@ -441,7 +447,7 @@ dependencies = [ "arrayvec 0.5.2", "cc", "cfg-if 0.1.10", - "constant_time_eq", + "constant_time_eq 0.1.5", ] [[package]] @@ -506,12 +512,24 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.7.2" @@ -694,6 +712,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "core-foundation" version = "0.9.4" @@ -738,6 +762,15 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "criterion" version = "0.5.1" @@ -821,7 +854,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -1063,7 +1096,7 @@ dependencies = [ "hkdf", "pem-rfc7468", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -1179,13 +1212,22 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + [[package]] name = "ff" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1195,6 +1237,16 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "flume" version = "0.11.0" @@ -1363,6 +1415,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "gimli" version = "0.31.1" @@ -1376,7 +1440,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1771,6 +1835,19 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.25.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -2021,11 +2098,12 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -2040,13 +2118,23 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "moxcms" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80986bbbcf925ebd3be54c26613d861255284584501595cf418320c078945608" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "nanorand" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] @@ -2125,7 +2213,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", "smallvec", "zeroize", ] @@ -2204,9 +2292,9 @@ checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" dependencies = [ "base64 0.13.1", "chrono", - "getrandom", + "getrandom 0.2.15", "http 0.2.12", - "rand", + "rand 0.8.5", "reqwest 0.11.27", "serde", "serde_json", @@ -2290,7 +2378,7 @@ dependencies = [ "oauth2", "p256", "p384", - "rand", + "rand 0.8.5", "rsa", "serde", "serde-value", @@ -2390,7 +2478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -2481,6 +2569,19 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "png" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +dependencies = [ + "bitflags 2.6.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "2.8.0" @@ -2574,6 +2675,32 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pxfm" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" +dependencies = [ + "num-traits", +] + +[[package]] +name = "qrcodegen" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4339fc7a1021c9c1621d87f5e3505f2805c8c105420ba2f2a4df86814590c142" + +[[package]] +name = "qrcodegen-image" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "221b7eace1aef8c95d65dbe09fb7a1a43d006045394a89afba6997721fcb7708" +dependencies = [ + "base64 0.22.1", + "image", + "qrcodegen", +] + [[package]] name = "quinn" version = "0.11.6" @@ -2599,8 +2726,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ "bytes", - "getrandom", - "rand", + "getrandom 0.2.15", + "rand 0.8.5", "ring", "rustc-hash", "rustls 0.23.35", @@ -2635,6 +2762,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -2642,8 +2775,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -2653,7 +2796,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -2662,7 +2815,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", ] [[package]] @@ -2783,9 +2945,9 @@ dependencies = [ "num-format", "okaywal", "openidconnect", - "rand", - "rand_chacha", - "rand_core", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", "reqwest 0.12.12", "retainer", "ring", @@ -2800,6 +2962,7 @@ dependencies = [ "thread_local", "tokio", "tokio-stream", + "totp-rs", "tracing", "tracing-appender", "tracing-subscriber", @@ -2901,7 +3064,7 @@ dependencies = [ "async-lock 2.8.0", "async-timer", "log", - "rand", + "rand 0.8.5", ] [[package]] @@ -2922,7 +3085,7 @@ checksum = "ed9b823fa29b721a59671b41d6b06e66b29e0628e207e8b1c3ceeda701ec928d" dependencies = [ "cc", "cfg-if 1.0.0", - "getrandom", + "getrandom 0.2.15", "libc", "untrusted", "windows-sys 0.52.0", @@ -2952,7 +3115,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", "signature", "spki", "subtle", @@ -3180,7 +3343,7 @@ dependencies = [ "hkdf", "num", "once_cell", - "rand", + "rand 0.8.5", "serde", "sha2", "zbus", @@ -3407,9 +3570,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "slab" version = "0.4.9" @@ -3795,6 +3964,23 @@ dependencies = [ "winnow", ] +[[package]] +name = "totp-rs" +version = "5.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f124352108f58ef88299e909f6e9470f1cdc8d2a1397963901b4a6366206bf72" +dependencies = [ + "base32", + "constant_time_eq 0.3.1", + "hmac", + "qrcodegen-image", + "rand 0.9.2", + "sha1", + "sha2", + "url", + "urlencoding", +] + [[package]] name = "tower" version = "0.5.2" @@ -3974,6 +4160,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -4029,6 +4221,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.95" @@ -4527,6 +4728,12 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + [[package]] name = "writeable" version = "0.6.2" @@ -4593,7 +4800,7 @@ dependencies = [ "nix 0.26.4", "once_cell", "ordered-stream", - "rand", + "rand 0.8.5", "serde", "serde_repr", "sha1", diff --git a/Cargo.toml b/Cargo.toml index 2fb59e72..782427aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,7 @@ openidconnect = { version = "3.5", features = ["reqwest"] } reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false } url = "2.5" webbrowser = "1.0" +totp-rs = { version = "5.0", features = ["qr", "gen_secret"] } [target.'cfg(target_os = "linux")'.dependencies] fuse3 = { version = "0.8.1", features = ["tokio-runtime", "unprivileged"] } diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index 9c5d8545..b57d8ba2 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -646,6 +646,52 @@ impl EncryptedFs { data_dir.join(SECURITY_DIR).join(IDENTITY_FILENAME).exists() } + // Encrypts and saves the TOTP secret to the vault. + pub async fn bind_totp_secret( + data_dir: &Path, + password: &SecretString, + cipher: Cipher, + secret: &str, + ) -> FsResult<()> { + ensure_structure_created(&data_dir.to_path_buf()).await?; + + let key_path = data_dir.join(SECURITY_DIR).join(KEY_ENC_FILENAME); + let salt_path = data_dir.join(SECURITY_DIR).join(KEY_SALT_FILENAME); + let identity_path = data_dir.join(SECURITY_DIR).join(IDENTITY_FILENAME); + + // Derive key from password to encrypt the secret + let key = read_or_create_key(&key_path, &salt_path, password, cipher)?; + + crypto::atomic_serialize_encrypt_into( + &identity_path, + &secret.to_string(), + cipher, + &key + )?; + Ok(()) + } + + // Decrypts and retrieves the TOTP secret from the vault. + pub fn get_totp_secret( + data_dir: &Path, + password: &SecretString, + cipher: Cipher, + ) -> FsResult { + let identity_path = data_dir.join(SECURITY_DIR).join(IDENTITY_FILENAME); + let key_path = data_dir.join(SECURITY_DIR).join(KEY_ENC_FILENAME); + let salt_path = data_dir.join(SECURITY_DIR).join(KEY_SALT_FILENAME); + + let key = read_or_create_key(&key_path, &salt_path, password, cipher)?; + let reader = crypto::create_read(File::open(&identity_path)?, cipher, &key); + + let secret: String = bincode::deserialize_from(reader) + .map_err(|e| { + error!("Failed to decrypt identity: {}", e); + FsError::Other("Failed to decrypt 2FA secret. Wrong password?") + })?; + Ok(secret) + } + // decrypts and returns the bound identity (subject ID) if it exists. pub fn get_bound_identity( data_dir: &Path, diff --git a/src/run.rs b/src/run.rs index d2e7dbd3..4d808782 100644 --- a/src/run.rs +++ b/src/run.rs @@ -16,15 +16,14 @@ use tokio::{fs, task}; use tracing::{error, info, warn, Level}; use crate::keyring; - use rencfs::crypto::Cipher; use rencfs::encryptedfs::{EncryptedFs, FsError, PasswordProvider}; use rencfs::mount::MountPoint; use rencfs::{log, mount}; +use totp_rs::{Algorithm, Secret, TOTP}; static mut PASS: Option = None; - #[derive(Debug, Error)] enum ExitStatusError { #[error("exit with status {0}")] @@ -54,7 +53,6 @@ pub(super) async fn run() -> Result<()> { }) }) .await; - match res { Ok(Ok(Ok(()))) => Ok(()), Ok(Ok(Err(err))) => { @@ -279,7 +277,8 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { let data_dir: String = matches.get_one::("data-dir").unwrap().to_string(); let data_path = Path::new(&data_dir); - + + // 1. Get Password FIRST let mut password = SecretString::from_str( env::var("RENCFS_PASSWORD") .unwrap_or_else(|_| String::new()) @@ -292,7 +291,7 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { io::stdout().flush().unwrap(); password = SecretString::new(Box::new(read_password()?)); - // confirm password logic for new directories + // Logic for new directory confirmation if !PathBuf::new().join(data_dir.clone()).is_dir() || fs::read_dir(&data_dir) .await @@ -312,7 +311,7 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { } } - // save password in keyring + // Save password to keyring info!("Save password in keyring"); let res = keyring::save(&password, "password").map_err(|err| { warn!(err = %err); @@ -324,40 +323,88 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { } } - // check 2FA Requirement + // 2. TOTP 2FA Logic let is_bound = EncryptedFs::is_identity_bound(data_path); let init_2fa = matches.get_flag("init-2fa"); - - if is_bound || init_2fa { - info!("2FA is required. Checking identity..."); - - let raw_client_id = env::var("GOOGLE_CLIENT_ID").unwrap_or_default(); - let raw_client_secret = env::var("GOOGLE_CLIENT_SECRET").unwrap_or_default(); - let google_client_id = raw_client_id.trim().trim_matches('"').trim_matches('\'').to_string(); - let google_client_secret = raw_client_secret.trim().trim_matches('"').trim_matches('\'').to_string(); - if google_client_id.is_empty() || google_client_secret.is_empty() { - return Err(anyhow::anyhow!("CRITICAL: GOOGLE_CLIENT_ID/SECRET missing.")); + if init_2fa { + // --- SETUP MODE --- + if is_bound { + error!("2FA is already initialized for this vault."); + return Err(ExitStatusError::Failure(1).into()); } - // determine expected User - let expected_sub = if is_bound { - EncryptedFs::get_bound_identity(data_path, &password, cipher)? + info!("Initializing TOTP 2FA..."); + + // Generate a new Secret + let secret = Secret::generate_secret(); + let secret_str = secret.to_encoded().to_string(); + + let totp = TOTP::new( + Algorithm::SHA1, + 6, + 1, + 30, + secret.to_bytes().unwrap(), + Some("Rencfs".to_string()), + "MyVault".to_string(), + ).unwrap(); + + // FIX: Map the String error to an anyhow::Error + let qr_base64 = totp.get_qr_base64() + .map_err(|e| anyhow::anyhow!("QR Code generation error: {}", e))?; + + println!("\n=== 2FA SETUP REQUIRED ==="); + println!("1. Open Google Authenticator (or similar app)."); + println!("2. Scan this QR Code (copy data URI to browser if terminal doesn't support it):"); + println!("data:image/png;base64,{}", qr_base64); + println!("\nOR enter this secret manually: {}\n", secret_str); + + print!("Enter the 6-digit code to verify and save: "); + io::stdout().flush().unwrap(); + let mut code = String::new(); + io::stdin().read_line(&mut code)?; + + if totp.check_current(code.trim()).unwrap_or(false) { + info!("Code verified. Saving encrypted 2FA secret..."); + EncryptedFs::bind_totp_secret(data_path, &password, cipher, &secret_str).await?; + println!("2FA enabled successfully."); } else { - None - }; - - // authenticate - let (_refresh_token, subject) = crate::auth::authenticate_google( - google_client_id, - google_client_secret, - expected_sub - ).await?; - - // bind if this was a new initialization - if !is_bound { - EncryptedFs::verify_or_bind_identity(data_path, &password, cipher, &subject).await?; + error!("Invalid code. Setup aborted."); + return Err(ExitStatusError::Failure(1).into()); + } + + } else if is_bound { + // --- VERIFICATION MODE --- + info!("Locked by 2FA."); + + let secret_str = EncryptedFs::get_totp_secret(data_path, &password, cipher) + .map_err(|_| { + error!("Failed to unlock 2FA. Password might be incorrect."); + ExitStatusError::Failure(1) + })?; + + let secret = Secret::Encoded(secret_str); + let totp = TOTP::new( + Algorithm::SHA1, + 6, + 1, + 30, + secret.to_bytes().unwrap(), + None, + String::new(), + ).unwrap(); + + print!("Enter 2FA Code: "); + io::stdout().flush().unwrap(); + let mut code = String::new(); + io::stdin().read_line(&mut code)?; + + if !totp.check_current(code.trim()).unwrap_or(false) { + error!("Invalid 2FA Code. Access Denied."); + return Err(ExitStatusError::Failure(1).into()); } + info!("2FA Validated. Mounting..."); } if matches.get_flag("umount-on-start") { From edddd873579a1b8fa0e20f4c4ecf6332a01e93e4 Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Fri, 12 Dec 2025 21:12:01 +0200 Subject: [PATCH 07/15] Add automatic page for QR image --- src/run.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/src/run.rs b/src/run.rs index 4d808782..b948ec9c 100644 --- a/src/run.rs +++ b/src/run.rs @@ -21,6 +21,7 @@ use rencfs::encryptedfs::{EncryptedFs, FsError, PasswordProvider}; use rencfs::mount::MountPoint; use rencfs::{log, mount}; use totp_rs::{Algorithm, Secret, TOTP}; +use webbrowser; static mut PASS: Option = None; @@ -354,10 +355,56 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { let qr_base64 = totp.get_qr_base64() .map_err(|e| anyhow::anyhow!("QR Code generation error: {}", e))?; + // --- NEW: Generate HTML and Open Browser --- + let html_content = format!( + r#" + + + + + + Rencfs 2FA Setup + + + +
+

Setup 2FA

+

Scan this QR code with your Authenticator App:

+ QR Code +

Or enter this secret manually:

+

{}

+
+ + + "#, + qr_base64, secret_str + ); + + let setup_file_path = std::env::current_dir()?.join("rencfs_2fa_setup.html"); + + // FIX 3: Use a scope block to force the file to close/flush immediately + { + let mut file = std::fs::File::create(&setup_file_path)?; + file.write_all(html_content.as_bytes())?; + file.flush()?; + } + + info!("Opening QR code in default browser..."); + + // Pass the absolute path to the browser + if webbrowser::open(setup_file_path.to_str().unwrap_or("")).is_err() { + warn!("Could not open browser automatically."); + println!("Please open this file manually: {}", setup_file_path.display()); + } + println!("\n=== 2FA SETUP REQUIRED ==="); println!("1. Open Google Authenticator (or similar app)."); - println!("2. Scan this QR Code (copy data URI to browser if terminal doesn't support it):"); - println!("data:image/png;base64,{}", qr_base64); + println!("2. Scan this QR Code from browser:"); println!("\nOR enter this secret manually: {}\n", secret_str); print!("Enter the 6-digit code to verify and save: "); From 367a7beb766e80f6cc872b2cf1bf5db76520ef29 Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Sat, 13 Dec 2025 10:36:18 +0200 Subject: [PATCH 08/15] Refine comments --- src/run.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/run.rs b/src/run.rs index b948ec9c..a92072e7 100644 --- a/src/run.rs +++ b/src/run.rs @@ -279,7 +279,6 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { let data_dir: String = matches.get_one::("data-dir").unwrap().to_string(); let data_path = Path::new(&data_dir); - // 1. Get Password FIRST let mut password = SecretString::from_str( env::var("RENCFS_PASSWORD") .unwrap_or_else(|_| String::new()) @@ -292,7 +291,7 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { io::stdout().flush().unwrap(); password = SecretString::new(Box::new(read_password()?)); - // Logic for new directory confirmation + // logic for new directory confirmation if !PathBuf::new().join(data_dir.clone()).is_dir() || fs::read_dir(&data_dir) .await @@ -324,12 +323,11 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { } } - // 2. TOTP 2FA Logic + // TOTP 2FA Logic let is_bound = EncryptedFs::is_identity_bound(data_path); let init_2fa = matches.get_flag("init-2fa"); if init_2fa { - // --- SETUP MODE --- if is_bound { error!("2FA is already initialized for this vault."); return Err(ExitStatusError::Failure(1).into()); @@ -337,10 +335,9 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { info!("Initializing TOTP 2FA..."); - // Generate a new Secret let secret = Secret::generate_secret(); let secret_str = secret.to_encoded().to_string(); - + let totp = TOTP::new( Algorithm::SHA1, 6, @@ -350,12 +347,11 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { Some("Rencfs".to_string()), "MyVault".to_string(), ).unwrap(); - - // FIX: Map the String error to an anyhow::Error + let qr_base64 = totp.get_qr_base64() .map_err(|e| anyhow::anyhow!("QR Code generation error: {}", e))?; - // --- NEW: Generate HTML and Open Browser --- + // generate HTML site with embedded QR code let html_content = format!( r#" @@ -387,7 +383,7 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { let setup_file_path = std::env::current_dir()?.join("rencfs_2fa_setup.html"); - // FIX 3: Use a scope block to force the file to close/flush immediately + // scope block to force the file to close/flush immediately { let mut file = std::fs::File::create(&setup_file_path)?; file.write_all(html_content.as_bytes())?; @@ -396,7 +392,6 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { info!("Opening QR code in default browser..."); - // Pass the absolute path to the browser if webbrowser::open(setup_file_path.to_str().unwrap_or("")).is_err() { warn!("Could not open browser automatically."); println!("Please open this file manually: {}", setup_file_path.display()); @@ -422,7 +417,6 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { } } else if is_bound { - // --- VERIFICATION MODE --- info!("Locked by 2FA."); let secret_str = EncryptedFs::get_totp_secret(data_path, &password, cipher) From 5195cec45b5b8b6f6c8ec38bbde2a66738ac633a Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Sat, 13 Dec 2025 10:54:42 +0200 Subject: [PATCH 09/15] Removed unnecessary files --- Cargo.lock | 1468 +++------------------------------------------------ Cargo.toml | 3 +- src/auth.rs | 132 ----- src/main.rs | 1 - 4 files changed, 84 insertions(+), 1520 deletions(-) delete mode 100644 src/auth.rs diff --git a/Cargo.lock b/Cargo.lock index fb168917..9bc78195 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,15 +43,6 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anes" version = "0.1.6" @@ -371,30 +362,12 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - [[package]] name = "base32" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -497,7 +470,7 @@ version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38aa5c627cd7706490e5b003d685f8b9d69bc343b1a00b9fdd01e75fdf6827cf" dependencies = [ - "darling 0.20.10", + "darling", "ident_case", "prettyplease", "proc-macro2", @@ -584,20 +557,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "chrono" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-link", -] - [[package]] name = "ciborium" version = "0.2.2" @@ -700,12 +659,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "constant_time_eq" version = "0.1.5" @@ -847,18 +800,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - [[package]] name = "crypto-common" version = "0.1.6" @@ -879,51 +820,14 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "curve25519-dalek" -version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "curve25519-dalek-derive", - "digest", - "fiat-crypto", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "darling" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "darling_core 0.20.10", - "darling_macro 0.20.10", -] - -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", + "darling_core", + "darling_macro", ] [[package]] @@ -940,53 +844,17 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.90", -] - [[package]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ - "darling_core 0.20.10", - "quote", - "syn 2.0.90", -] - -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core 0.21.3", + "darling_core", "quote", "syn 2.0.90", ] -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - [[package]] name = "deranged" version = "0.3.11" @@ -994,7 +862,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", - "serde", ] [[package]] @@ -1015,7 +882,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", - "const-oid", "crypto-common", "subtle", ] @@ -1031,86 +897,12 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" -dependencies = [ - "curve25519-dalek", - "ed25519", - "serde", - "sha2", - "subtle", - "zeroize", -] - [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", - "hkdf", - "pem-rfc7468", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if 1.0.0", -] - [[package]] name = "enumflags2" version = "0.7.10" @@ -1221,22 +1013,6 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "ff" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - [[package]] name = "flate2" version = "1.1.5" @@ -1399,7 +1175,6 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] @@ -1433,36 +1208,6 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "h2" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.7.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "half" version = "2.4.1" @@ -1473,12 +1218,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.15.2" @@ -1542,294 +1281,108 @@ dependencies = [ ] [[package]] -name = "http" -version = "0.2.12" +name = "icu_collections" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ - "bytes", - "fnv", - "itoa", + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "http" -version = "1.4.0" +name = "icu_locale_core" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ - "bytes", - "itoa", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "http-body" -version = "0.4.6" +name = "icu_normalizer" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", ] [[package]] -name = "http-body" -version = "1.0.1" +name = "icu_normalizer_data" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" dependencies = [ - "bytes", - "http 1.4.0", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", ] [[package]] -name = "http-body-util" -version = "0.1.3" +name = "icu_properties_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" + +[[package]] +name = "icu_provider" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ - "bytes", - "futures-core", - "http 1.4.0", - "http-body 1.0.1", - "pin-project-lite", + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", ] [[package]] -name = "httparse" -version = "1.10.1" +name = "ident_case" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] -name = "httpdate" -version = "1.0.3" +name = "idna" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] [[package]] -name = "hyper" -version = "0.14.32" +name = "idna_adapter" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.5.7", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http 1.4.0", - "http-body 1.0.1", - "httparse", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.32", - "rustls 0.21.12", - "tokio", - "tokio-rustls 0.24.1", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http 1.4.0", - "hyper 1.8.1", - "hyper-util", - "rustls 0.23.35", - "rustls-pki-types", - "tokio", - "tokio-rustls 0.26.4", - "tower-service", - "webpki-roots 1.0.4", -] - -[[package]] -name = "hyper-util" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http 1.4.0", - "http-body 1.0.1", - "hyper 1.8.1", - "pin-project-lite", - "socket2 0.5.7", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" - -[[package]] -name = "icu_properties" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" - -[[package]] -name = "icu_provider" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -1848,17 +1401,6 @@ dependencies = [ "png", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - [[package]] name = "indexmap" version = "2.7.0" @@ -1866,8 +1408,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.15.2", - "serde", + "hashbrown", ] [[package]] @@ -1900,12 +1441,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - [[package]] name = "is-terminal" version = "0.4.13" @@ -1988,9 +1523,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "libc" @@ -1998,12 +1530,6 @@ version = "0.2.160" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0b21006cd1874ae9e650973c565615676dc4a274c965bb0a73796dac838ce4f" -[[package]] -name = "libm" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" - [[package]] name = "linux-keyutils" version = "0.2.4" @@ -2054,7 +1580,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.2", + "hashbrown", ] [[package]] @@ -2090,12 +1616,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2202,22 +1722,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.5", - "smallvec", - "zeroize", -] - [[package]] name = "num-complex" version = "0.4.6" @@ -2281,27 +1785,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", -] - -[[package]] -name = "oauth2" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" -dependencies = [ - "base64 0.13.1", - "chrono", - "getrandom 0.2.15", - "http 0.2.12", - "rand 0.8.5", - "reqwest 0.11.27", - "serde", - "serde_json", - "serde_path_to_error", - "sha2", - "thiserror 1.0.64", - "url", ] [[package]] @@ -2361,47 +1844,6 @@ version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" -[[package]] -name = "openidconnect" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f47e80a9cfae4462dd29c41e987edd228971d6565553fbc14b8a11e666d91590" -dependencies = [ - "base64 0.13.1", - "chrono", - "dyn-clone", - "ed25519-dalek", - "hmac", - "http 0.2.12", - "itertools", - "log", - "oauth2", - "p256", - "p384", - "rand 0.8.5", - "rsa", - "serde", - "serde-value", - "serde_derive", - "serde_json", - "serde_path_to_error", - "serde_plain", - "serde_with", - "sha2", - "subtle", - "thiserror 1.0.64", - "url", -] - -[[package]] -name = "ordered-float" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" -dependencies = [ - "num-traits", -] - [[package]] name = "ordered-stream" version = "0.2.0" @@ -2418,30 +1860,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - -[[package]] -name = "p384" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - [[package]] name = "parking" version = "2.2.1" @@ -2482,15 +1900,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -2520,27 +1929,6 @@ dependencies = [ "futures-io", ] -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "plotters" version = "0.3.7" @@ -2647,15 +2035,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", -] - [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -2696,63 +2075,11 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "221b7eace1aef8c95d65dbe09fb7a1a43d006045394a89afba6997721fcb7708" dependencies = [ - "base64 0.22.1", + "base64", "image", "qrcodegen", ] -[[package]] -name = "quinn" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" -dependencies = [ - "bytes", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls 0.23.35", - "socket2 0.5.7", - "thiserror 2.0.6", - "tokio", - "tracing", -] - -[[package]] -name = "quinn-proto" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" -dependencies = [ - "bytes", - "getrandom 0.2.15", - "rand 0.8.5", - "ring", - "rustc-hash", - "rustls 0.23.35", - "rustls-pki-types", - "slab", - "thiserror 2.0.6", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2 0.5.7", - "tracing", - "windows-sys 0.59.0", -] - [[package]] name = "quote" version = "1.0.37" @@ -2856,26 +2183,6 @@ dependencies = [ "bitflags 2.6.0", ] -[[package]] -name = "ref-cast" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "regex" version = "1.11.0" @@ -2928,7 +2235,7 @@ dependencies = [ "argon2", "async-trait", "atomic-write-file", - "base64 0.22.1", + "base64", "bincode", "blake3", "bon", @@ -2944,11 +2251,9 @@ dependencies = [ "lru", "num-format", "okaywal", - "openidconnect", "rand 0.8.5", "rand_chacha 0.3.1", "rand_core 0.6.4", - "reqwest 0.12.12", "retainer", "ring", "rpassword", @@ -2971,90 +2276,6 @@ dependencies = [ "webbrowser", ] -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "hyper-rustls 0.24.2", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls 0.21.12", - "rustls-pemfile 1.0.4", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration", - "tokio", - "tokio-rustls 0.24.1", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots 0.25.4", - "winreg", -] - -[[package]] -name = "reqwest" -version = "0.12.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "futures-util", - "http 1.4.0", - "http-body 1.0.1", - "http-body-util", - "hyper 1.8.1", - "hyper-rustls 0.27.7", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls 0.23.35", - "rustls-pemfile 2.2.0", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 1.0.2", - "tokio", - "tokio-rustls 0.26.4", - "tower", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots 0.26.11", - "windows-registry", -] - [[package]] name = "retainer" version = "0.3.0" @@ -3067,16 +2288,6 @@ dependencies = [ "rand 0.8.5", ] -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - [[package]] name = "ring" version = "0.17.12" @@ -3102,26 +2313,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "rsa" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - [[package]] name = "rtoolbox" version = "0.0.2" @@ -3138,12 +2329,6 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - [[package]] name = "rustc_version" version = "0.4.1" @@ -3180,81 +2365,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - -[[package]] -name = "rustls" -version = "0.23.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki 0.103.8", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - [[package]] name = "rustversion" version = "1.0.18" @@ -3273,62 +2383,14 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ - "winapi-util", -] - -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", + "winapi-util", ] [[package]] -name = "sec1" -version = "0.7.3" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "secret-service" @@ -3388,16 +2450,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-value" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" -dependencies = [ - "ordered-float", - "serde", -] - [[package]] name = "serde_core" version = "1.0.228" @@ -3431,26 +2483,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "serde_path_to_error" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" -dependencies = [ - "itoa", - "serde", - "serde_core", -] - -[[package]] -name = "serde_plain" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" -dependencies = [ - "serde", -] - [[package]] name = "serde_repr" version = "0.1.19" @@ -3462,49 +2494,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "3.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.7.0", - "schemars 0.9.0", - "schemars 1.1.0", - "serde_core", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" -dependencies = [ - "darling 0.21.3", - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "sha1" version = "0.10.6" @@ -3563,16 +2552,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - [[package]] name = "simd-adler32" version = "0.3.8" @@ -3623,16 +2602,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -3698,21 +2667,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - [[package]] name = "synstructure" version = "0.13.2" @@ -3724,27 +2678,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "tempfile" version = "3.13.0" @@ -3859,21 +2792,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "tinyvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" version = "1.40.0" @@ -3903,26 +2821,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls 0.23.35", - "tokio", -] - [[package]] name = "tokio-stream" version = "0.1.16" @@ -3934,19 +2832,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-util" -version = "0.7.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - [[package]] name = "toml_datetime" version = "0.6.8" @@ -3959,7 +2844,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.7.0", + "indexmap", "toml_datetime", "winnow", ] @@ -3981,33 +2866,6 @@ dependencies = [ "urlencoding", ] -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper 1.0.2", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - [[package]] name = "tracing" version = "0.1.40" @@ -4113,12 +2971,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - [[package]] name = "typenum" version = "1.17.0" @@ -4206,15 +3058,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -4256,18 +3099,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" -dependencies = [ - "cfg-if 1.0.0", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.95" @@ -4307,16 +3138,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "webbrowser" version = "1.0.6" @@ -4333,30 +3154,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.4", -] - -[[package]] -name = "webpki-roots" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "which" version = "6.0.3" @@ -4400,95 +3197,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-registry" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" -dependencies = [ - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-sys" version = "0.45.0" @@ -4712,16 +3420,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if 1.0.0", - "windows-sys 0.48.0", -] - [[package]] name = "winsafe" version = "0.0.19" diff --git a/Cargo.toml b/Cargo.toml index 782427aa..40a20838 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,8 +58,7 @@ subtle = "2.6.1" bon = "3.3.0" shush-rs = "0.1.10" criterion = { version = "0.5.1", features = ["html_reports"] } -openidconnect = { version = "3.5", features = ["reqwest"] } -reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false } + url = "2.5" webbrowser = "1.0" totp-rs = { version = "5.0", features = ["qr", "gen_secret"] } diff --git a/src/auth.rs b/src/auth.rs deleted file mode 100644 index ab420e89..00000000 --- a/src/auth.rs +++ /dev/null @@ -1,132 +0,0 @@ -// src/auth.rs -use anyhow::{Context, Result}; -use openidconnect::core::{CoreClient, CoreProviderMetadata, CoreResponseType}; -use openidconnect::reqwest::async_http_client; -// UPDATED IMPORT: Added TokenResponse (for id_token() method) -use openidconnect::{ - AuthenticationFlow, ClientId, ClientSecret, CsrfToken, IssuerUrl, Nonce, - RedirectUrl, Scope, RefreshToken, OAuth2TokenResponse, TokenResponse, -}; -use std::io::{BufRead, BufReader, Write}; -use std::net::TcpListener; -use url::Url; -use webbrowser; - -/// Authenticates with Google. -/// -/// Triggers a browser flow to authenticate the user. -/// -/// If `expected_sub` is provided, it verifies that the authenticated user's -/// subject ID matches the expected one. -/// -/// Returns a tuple: (Refresh Token, Subject ID) -pub async fn authenticate_google( - client_id: String, - client_secret: String, - expected_sub: Option -) -> Result<(String, String)> { - - let google_issuer = IssuerUrl::new("https://accounts.google.com".to_string())?; - - let provider_metadata = CoreProviderMetadata::discover_async(google_issuer, async_http_client) - .await - .context("Failed to discover Google OIDC configuration")?; - - let client = CoreClient::from_provider_metadata( - provider_metadata, - ClientId::new(client_id), - Some(ClientSecret::new(client_secret)), - ) - .set_redirect_uri(RedirectUrl::new("http://localhost:8080".to_string())?); - - println!("Initiating Google 2FA (Passkey Check)..."); - - // keep the `nonce` to verify the ID token later - let (authorize_url, csrf_state, nonce) = client - .authorize_url( - AuthenticationFlow::::AuthorizationCode, - CsrfToken::new_random, - Nonce::new_random, - ) - .add_scope(Scope::new("email".to_string())) - .add_scope(Scope::new("profile".to_string())) - .add_extra_param("access_type", "offline") - .add_extra_param("prompt", "login") // Force login UI - .add_extra_param("max_age", "0") // Force fresh authentication (Passkey) - .url(); - - println!("Opening browser for authentication..."); - if webbrowser::open(authorize_url.as_str()).is_err() { - println!("Please open this URL in your browser:\n{}", authorize_url); - } - - let listener = TcpListener::bind("127.0.0.1:8080")?; - - if let Some(stream) = listener.incoming().next() { - let mut stream = stream?; - let code; - let state; - - { - let mut reader = BufReader::new(&stream); - let mut request_line = String::new(); - reader.read_line(&mut request_line)?; - - let redirect_url = request_line.split_whitespace().nth(1).unwrap_or("/"); - let url = Url::parse(&("http://localhost".to_string() + redirect_url))?; - - let code_pair = url.query_pairs().find(|(key, _)| key == "code"); - let state_pair = url.query_pairs().find(|(key, _)| key == "state"); - - if let (Some((_, c)), Some((_, s))) = (code_pair, state_pair) { - code = c.into_owned(); - state = s.into_owned(); - } else { - return Err(anyhow::anyhow!("Failed to retrieve auth code")); - } - } - - let response = "HTTP/1.1 200 OK\r\n\r\nGoogle Auth Successful! You can close this window."; - stream.write_all(response.as_bytes())?; - - if state != *csrf_state.secret() { - return Err(anyhow::anyhow!("CSRF state mismatch")); - } - - let token_response = client - .exchange_code(openidconnect::AuthorizationCode::new(code)) - .request_async(async_http_client) - .await - .context("Failed to exchange auth code for token")?; - - // verify ID token and check subject - let id_token = token_response.id_token() - .ok_or_else(|| anyhow::anyhow!("Google did not return an ID token"))?; - - let claims = id_token.claims(&client.id_token_verifier(), &nonce) - .context("Failed to verify ID token claims")?; - - let subject = claims.subject().to_string(); - - if let Some(expected) = &expected_sub { - if *expected != subject { - return Err(anyhow::anyhow!( - "Security Alert: Authenticated user ({}) does not match the owner ({})!", - subject, expected - )); - } - } else { - println!("Authenticated as user ID: {}", subject); - } - - println!("Google Authentication and Identity Verification successful."); - - let refresh_token = token_response.refresh_token() - .map(|t| t.secret().clone()) - .unwrap_or_else(String::new); - - Ok((refresh_token, subject)) - } else { - Err(anyhow::anyhow!("Failed to receive connection from browser")) - } -} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 0360d5ce..d8986e0d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ use anyhow::Result; mod keyring; -mod auth; #[cfg(target_os = "linux")] mod run; From 95068c6b79ffabe66936a4598372ea6a496d4020 Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Sat, 13 Dec 2025 11:01:56 +0200 Subject: [PATCH 10/15] Auto remove HTML file --- src/run.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/run.rs b/src/run.rs index a92072e7..92550fa8 100644 --- a/src/run.rs +++ b/src/run.rs @@ -351,6 +351,11 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { let qr_base64 = totp.get_qr_base64() .map_err(|e| anyhow::anyhow!("QR Code generation error: {}", e))?; + + let setup_file_path = std::env::current_dir()?.join("rencfs_2fa_setup.html"); + + let _file_guard = TempFileGuard(setup_file_path.clone()); + // generate HTML site with embedded QR code let html_content = format!( r#" @@ -381,8 +386,6 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { qr_base64, secret_str ); - let setup_file_path = std::env::current_dir()?.join("rencfs_2fa_setup.html"); - // scope block to force the file to close/flush immediately { let mut file = std::fs::File::create(&setup_file_path)?; @@ -555,4 +558,19 @@ fn remove_pass() { PASS = None; } } +} + +struct TempFileGuard(PathBuf); + +impl Drop for TempFileGuard { + fn drop(&mut self) { + if self.0.exists() { + // try to remove the file + if let Err(e) = std::fs::remove_file(&self.0) { + warn!("Failed to delete temporary setup file: {}", e); + } else { + info!("Temporary setup file deleted."); + } + } + } } \ No newline at end of file From 28286fdc19240d9ffe49cb3b04cc9dff4bbaf531 Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Sat, 31 Jan 2026 16:46:17 +0200 Subject: [PATCH 11/15] Fix memory protection of TOTP secret Signed-off-by: Cristian Andrei --- Cargo.lock | 49 +++++++++++++ Cargo.toml | 2 + src/encryptedfs.rs | 107 ++++++--------------------- src/run.rs | 176 ++++++++++++++++++--------------------------- 4 files changed, 141 insertions(+), 193 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9bc78195..b0209e12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -794,6 +794,28 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags 2.6.0", + "crossterm_winapi", + "parking_lot", + "rustix 0.38.37", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crunchy" version = "0.2.2" @@ -2063,6 +2085,22 @@ dependencies = [ "num-traits", ] +[[package]] +name = "qr2term" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6867c60b38e9747a079a19614dbb5981a53f21b9a56c265f3bfdf6011a50a957" +dependencies = [ + "crossterm", + "qrcode", +] + +[[package]] +name = "qrcode" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec" + [[package]] name = "qrcodegen" version = "1.8.0" @@ -2251,12 +2289,14 @@ dependencies = [ "lru", "num-format", "okaywal", + "qr2term", "rand 0.8.5", "rand_chacha 0.3.1", "rand_core 0.6.4", "retainer", "ring", "rpassword", + "secrecy", "serde", "shush-rs", "strum", @@ -2392,6 +2432,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + [[package]] name = "secret-service" version = "3.1.0" diff --git a/Cargo.toml b/Cargo.toml index 40a20838..2290f30e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,8 @@ criterion = { version = "0.5.1", features = ["html_reports"] } url = "2.5" webbrowser = "1.0" totp-rs = { version = "5.0", features = ["qr", "gen_secret"] } +qr2term = "0.3.3" +secrecy = "0.10.3" [target.'cfg(target_os = "linux")'.dependencies] fuse3 = { version = "0.8.1", features = ["tokio-runtime", "unprivileged"] } diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index b57d8ba2..e49b07e0 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -641,128 +641,63 @@ impl EncryptedFs { Ok(arc) } - // checks if the filesystem at the given path is bound to a specific identity. + /// checks if the filesystem at the given path is bound to a specific identity. pub fn is_identity_bound(data_dir: &Path) -> bool { data_dir.join(SECURITY_DIR).join(IDENTITY_FILENAME).exists() } - // Encrypts and saves the TOTP secret to the vault. + /// encrypts and saves the TOTP secret to the vault. pub async fn bind_totp_secret( data_dir: &Path, password: &SecretString, cipher: Cipher, - secret: &str, + secret: &SecretString, ) -> FsResult<()> { ensure_structure_created(&data_dir.to_path_buf()).await?; - + let key_path = data_dir.join(SECURITY_DIR).join(KEY_ENC_FILENAME); let salt_path = data_dir.join(SECURITY_DIR).join(KEY_SALT_FILENAME); let identity_path = data_dir.join(SECURITY_DIR).join(IDENTITY_FILENAME); // Derive key from password to encrypt the secret let key = read_or_create_key(&key_path, &salt_path, password, cipher)?; - + + // Serialize the SecretString content crypto::atomic_serialize_encrypt_into( - &identity_path, - &secret.to_string(), - cipher, - &key + &identity_path, + &*secret.expose_secret().as_str(), + cipher, + &key, )?; Ok(()) } - // Decrypts and retrieves the TOTP secret from the vault. + /// Decrypts and retrieves the TOTP secret from the vault. pub fn get_totp_secret( data_dir: &Path, password: &SecretString, cipher: Cipher, - ) -> FsResult { + ) -> FsResult { let identity_path = data_dir.join(SECURITY_DIR).join(IDENTITY_FILENAME); - let key_path = data_dir.join(SECURITY_DIR).join(KEY_ENC_FILENAME); - let salt_path = data_dir.join(SECURITY_DIR).join(KEY_SALT_FILENAME); - - let key = read_or_create_key(&key_path, &salt_path, password, cipher)?; - let reader = crypto::create_read(File::open(&identity_path)?, cipher, &key); - - let secret: String = bincode::deserialize_from(reader) - .map_err(|e| { - error!("Failed to decrypt identity: {}", e); - FsError::Other("Failed to decrypt 2FA secret. Wrong password?") - })?; - Ok(secret) - } - // decrypts and returns the bound identity (subject ID) if it exists. - pub fn get_bound_identity( - data_dir: &Path, - password: &SecretString, - cipher: Cipher, - ) -> FsResult> { - let identity_path = data_dir.join(SECURITY_DIR).join(IDENTITY_FILENAME); if !identity_path.exists() { - return Ok(None); + return Err(FsError::Other( + "2FA is enabled but identity file is missing.", + )); } let key_path = data_dir.join(SECURITY_DIR).join(KEY_ENC_FILENAME); let salt_path = data_dir.join(SECURITY_DIR).join(KEY_SALT_FILENAME); - - let key = read_or_create_key(&key_path, &salt_path, password, cipher)?; - - let reader = crypto::create_read(File::open(&identity_path)?, cipher, &key); - let stored_sub: String = bincode::deserialize_from(reader) - .map_err(|_| FsError::Other("Failed to decrypt identity. Wrong password?"))?; - - Ok(Some(stored_sub)) - } - // Verifies the identity of the user against the bound Google Account ID (sub). - // If no identity is bound, it encrypts and stores the provided `subject` to bind the vault. - pub async fn verify_or_bind_identity( - data_dir: &Path, - password: &SecretString, - cipher: Cipher, - subject: &str, - ) -> FsResult<()> { - // Ensure the directory structure exists - ensure_structure_created(&data_dir.to_path_buf()).await?; - - let key_path = data_dir.join(SECURITY_DIR).join(KEY_ENC_FILENAME); - let salt_path = data_dir.join(SECURITY_DIR).join(KEY_SALT_FILENAME); - let identity_path = data_dir.join(SECURITY_DIR).join(IDENTITY_FILENAME); - - // Derive/Load the master key using the provided password let key = read_or_create_key(&key_path, &salt_path, password, cipher)?; + let reader = crypto::create_read(File::open(&identity_path)?, cipher, &key); - if identity_path.exists() { - info!("Identity file found. Verifying 2FA Identity..."); - - // Decrypt the stored ID - let reader = crypto::create_read(File::open(&identity_path)?, cipher, &key); - let stored_sub: String = bincode::deserialize_from(reader) - .map_err(|e| { - error!("Failed to deserialize identity: {}", e); - FsError::Other("Failed to read identity file. Password might be wrong or file corrupted.") - })?; - - if stored_sub != subject { - error!("CRITICAL: Identity mismatch! Vault is bound to user '{}', but current user is '{}'", stored_sub, subject); - return Err(FsError::Other("Access Denied: The Google Account used for 2FA does not match the vault owner.")); - } - info!("Identity verified successfully."); - } else { - info!("No identity file found. Binding vault to Google Account ID: {}", subject); - - // Encrypt and save the ID - crypto::atomic_serialize_encrypt_into( - &identity_path, - &subject.to_string(), - cipher, - &key - )?; - info!("Vault successfully bound to user."); - } + let secret: String = bincode::deserialize_from(reader).map_err(|e| { + error!("Failed to decrypt identity: {}", e); + FsError::Other("Failed to decrypt 2FA secret. Wrong password?") + })?; - Ok(()) + Ok(SecretString::new(Box::new(secret))) } pub fn exists(&self, ino: u64) -> bool { diff --git a/src/run.rs b/src/run.rs index 92550fa8..759fb68f 100644 --- a/src/run.rs +++ b/src/run.rs @@ -21,7 +21,6 @@ use rencfs::encryptedfs::{EncryptedFs, FsError, PasswordProvider}; use rencfs::mount::MountPoint; use rencfs::{log, mount}; use totp_rs::{Algorithm, Secret, TOTP}; -use webbrowser; static mut PASS: Option = None; @@ -285,7 +284,7 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { .as_str(), ) .unwrap(); - + if password.expose_secret().is_empty() { print!("Enter password: "); io::stdout().flush().unwrap(); @@ -329,126 +328,104 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { if init_2fa { if is_bound { - error!("2FA is already initialized for this vault."); - return Err(ExitStatusError::Failure(1).into()); + error!("2FA is already initialized for this vault."); + return Err(ExitStatusError::Failure(1).into()); } info!("Initializing TOTP 2FA..."); - + let secret = Secret::generate_secret(); - let secret_str = secret.to_encoded().to_string(); + let secret_str = SecretString::new(Box::new(secret.to_encoded().to_string())); + let vault_name = data_path + .file_name() + .and_then(|n| n.to_str()) + .unwrap_or("RencfsVault"); + + // Use standard SHA1 for authenticator app compatibility (RFC 6238) let totp = TOTP::new( Algorithm::SHA1, 6, 1, 30, - secret.to_bytes().unwrap(), + secret + .to_bytes() + .map_err(|e| anyhow::anyhow!("Invalid secret bytes: {}", e))?, Some("Rencfs".to_string()), - "MyVault".to_string(), - ).unwrap(); - - let qr_base64 = totp.get_qr_base64() - .map_err(|e| anyhow::anyhow!("QR Code generation error: {}", e))?; - - - let setup_file_path = std::env::current_dir()?.join("rencfs_2fa_setup.html"); - - let _file_guard = TempFileGuard(setup_file_path.clone()); - - // generate HTML site with embedded QR code - let html_content = format!( - r#" - - - - - - Rencfs 2FA Setup - - - -
-

Setup 2FA

-

Scan this QR code with your Authenticator App:

- QR Code -

Or enter this secret manually:

-

{}

-
- - - "#, - qr_base64, secret_str - ); - - // scope block to force the file to close/flush immediately - { - let mut file = std::fs::File::create(&setup_file_path)?; - file.write_all(html_content.as_bytes())?; - file.flush()?; - } + vault_name.to_string(), + ) + .map_err(|e| anyhow::anyhow!("TOTP configuration error: {}", e))?; + + let totp_url = totp.get_url(); - info!("Opening QR code in default browser..."); - - if webbrowser::open(setup_file_path.to_str().unwrap_or("")).is_err() { - warn!("Could not open browser automatically."); - println!("Please open this file manually: {}", setup_file_path.display()); - } - println!("\n=== 2FA SETUP REQUIRED ==="); - println!("1. Open Google Authenticator (or similar app)."); - println!("2. Scan this QR Code from browser:"); - println!("\nOR enter this secret manually: {}\n", secret_str); - + println!("Scan this QR code with your Authenticator App:"); + + // terminal QR display + if let Err(e) = qr2term::print_qr(&totp_url) { + error!("Failed to generate terminal QR code: {}", e); + println!("(QR rendering failed. Please use the secret below manually)"); + } + + // only expose secret for manual entry + println!("\nManual Secret: {}\n", secret_str.expose_secret()); + print!("Enter the 6-digit code to verify and save: "); io::stdout().flush().unwrap(); let mut code = String::new(); io::stdin().read_line(&mut code)?; - - if totp.check_current(code.trim()).unwrap_or(false) { - info!("Code verified. Saving encrypted 2FA secret..."); - EncryptedFs::bind_totp_secret(data_path, &password, cipher, &secret_str).await?; - println!("2FA enabled successfully."); - } else { - error!("Invalid code. Setup aborted."); - return Err(ExitStatusError::Failure(1).into()); - } + match totp.check_current(code.trim()) { + Ok(true) => { + info!("Code verified. Saving encrypted 2FA secret..."); + EncryptedFs::bind_totp_secret(data_path, &password, cipher, &secret_str).await?; + println!("2FA enabled successfully."); + } + Ok(false) => { + error!("Invalid code. Setup aborted."); + return Err(ExitStatusError::Failure(1).into()); + } + Err(e) => { + error!("TOTP verification error: {}", e); + warn!("Hint: Ensure your system clock is synchronized."); + return Err(ExitStatusError::Failure(1).into()); + } + } } else if is_bound { info!("Locked by 2FA."); - - let secret_str = EncryptedFs::get_totp_secret(data_path, &password, cipher) - .map_err(|_| { - error!("Failed to unlock 2FA. Password might be incorrect."); + + let secret_str = + EncryptedFs::get_totp_secret(data_path, &password, cipher).map_err(|e| { + error!("Failed to unlock 2FA: {}. Password might be incorrect.", e); ExitStatusError::Failure(1) })?; - let secret = Secret::Encoded(secret_str); - let totp = TOTP::new( - Algorithm::SHA1, - 6, - 1, - 30, - secret.to_bytes().unwrap(), - None, - String::new(), - ).unwrap(); + let secret_bytes = Secret::Encoded(secret_str.expose_secret().clone()) + .to_bytes() + .map_err(|e| anyhow::anyhow!("Corrupted 2FA secret: {}", e))?; + + let totp = TOTP::new(Algorithm::SHA1, 6, 1, 30, secret_bytes, None, String::new()) + .map_err(|e| anyhow::anyhow!("TOTP init error: {}", e))?; print!("Enter 2FA Code: "); io::stdout().flush().unwrap(); let mut code = String::new(); io::stdin().read_line(&mut code)?; - if !totp.check_current(code.trim()).unwrap_or(false) { - error!("Invalid 2FA Code. Access Denied."); - return Err(ExitStatusError::Failure(1).into()); + match totp.check_current(code.trim()) { + Ok(true) => { + info!("2FA Validated. Mounting..."); + } + Ok(false) => { + error!("Invalid 2FA Code. Access Denied."); + return Err(ExitStatusError::Failure(1).into()); + } + Err(e) => { + error!("TOTP Error: {}", e); + warn!("Hint: Check system clock."); + return Err(ExitStatusError::Failure(1).into()); + } } - info!("2FA Validated. Mounting..."); } if matches.get_flag("umount-on-start") { @@ -559,18 +536,3 @@ fn remove_pass() { } } } - -struct TempFileGuard(PathBuf); - -impl Drop for TempFileGuard { - fn drop(&mut self) { - if self.0.exists() { - // try to remove the file - if let Err(e) = std::fs::remove_file(&self.0) { - warn!("Failed to delete temporary setup file: {}", e); - } else { - info!("Temporary setup file deleted."); - } - } - } -} \ No newline at end of file From 05d9378d2979e62af8d050b470c6d3580645d038 Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Sat, 31 Jan 2026 17:35:34 +0200 Subject: [PATCH 12/15] Add tests for TOTP Signed-off-by: Cristian Andrei --- src/encryptedfs/test.rs | 109 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/src/encryptedfs/test.rs b/src/encryptedfs/test.rs index 81e33d05..a74f0159 100644 --- a/src/encryptedfs/test.rs +++ b/src/encryptedfs/test.rs @@ -2481,3 +2481,112 @@ async fn test_read_only_write() { ) .await; } + +#[tokio::test] +#[traced_test] +async fn test_totp_secret_binding() { + run_test( + TestSetup { + key: "test_totp_binding", + read_only: false, + }, + async { + let fs = get_fs().await; + + // credentials + let pass = SecretString::from_str("password").unwrap(); + let cipher = Cipher::ChaCha20Poly1305; + let secret = SecretString::from_str("JBSWY3DPEHPK3PXP").unwrap(); // Example Base32 secret + + // bind the secret + EncryptedFs::bind_totp_secret(&fs.data_dir, &pass, cipher, &secret) + .await + .expect("Failed to bind TOTP secret"); + + assert!(EncryptedFs::is_identity_bound(&fs.data_dir)); + + // retrieve and decrypt + let retrieved = EncryptedFs::get_totp_secret(&fs.data_dir, &pass, cipher) + .expect("Failed to retrieve secret"); + + assert_eq!( + secret.expose_secret(), + retrieved.expose_secret(), + "Decrypted secret does not match original" + ); + + // test missing file handling + let invalid_path = fs.data_dir.join("non_existent_vault"); + let result = EncryptedFs::get_totp_secret(&invalid_path, &pass, cipher); + assert!(result.is_err()); + }, + ) + .await; +} + +#[tokio::test] +#[traced_test] +async fn test_totp_wrong_password() { + run_test( + TestSetup { + key: "test_totp_wrong_pass", + read_only: false, + }, + async { + let fs = get_fs().await; + + let pass = SecretString::from_str("password").unwrap(); + let wrong_pass = SecretString::from_str("wrong_password").unwrap(); + let cipher = Cipher::ChaCha20Poly1305; + let secret = SecretString::from_str("JBSWY3DPEHPK3PXP").unwrap(); + + EncryptedFs::bind_totp_secret(&fs.data_dir, &pass, cipher, &secret) + .await + .expect("Failed to bind"); + + let result = EncryptedFs::get_totp_secret(&fs.data_dir, &wrong_pass, cipher); + + assert!(result.is_err(), "Should fail with wrong password"); + }, + ) + .await; +} + +#[tokio::test] +#[traced_test] +async fn test_totp_corrupted_file() { + run_test( + TestSetup { + key: "test_totp_corrupt", + read_only: false, + }, + async { + let fs = get_fs().await; + let pass = SecretString::from_str("password").unwrap(); + let cipher = Cipher::ChaCha20Poly1305; + let secret = SecretString::from_str("JBSWY3DPEHPK3PXP").unwrap(); + + // bind correctly + EncryptedFs::bind_totp_secret(&fs.data_dir, &pass, cipher, &secret) + .await + .expect("Failed to bind"); + + // corrupt the identity.enc file manually + let identity_path = fs.data_dir.join("security/identity.enc"); + { + use std::fs::OpenOptions; + use std::io::Write; + let mut file = OpenOptions::new().write(true).open(&identity_path).unwrap(); + // overwrite start of file with garbage + file.write_all(b"GARBAGE_DATA_CORRUPTION").unwrap(); + } + + // try to retrieve + let result = EncryptedFs::get_totp_secret(&fs.data_dir, &pass, cipher); + + // assert failure + assert!(result.is_err(), "Should fail on corrupted file"); + }, + ) + .await; +} From 9a8d45896130310a681f27a6aa2050f7d63fa66b Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Sat, 31 Jan 2026 17:36:26 +0200 Subject: [PATCH 13/15] Add warning for recovery Signed-off-by: Cristian Andrei --- src/run.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/run.rs b/src/run.rs index 759fb68f..f7d7d614 100644 --- a/src/run.rs +++ b/src/run.rs @@ -368,6 +368,9 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { } // only expose secret for manual entry + println!("\n=== IMPORTANT RECOVERY INFORMATION ==="); + println!("If you lose your authenticator device, you will be LOCKED OUT."); + println!("Save the secret below in a secure password manager as a backup:"); println!("\nManual Secret: {}\n", secret_str.expose_secret()); print!("Enter the 6-digit code to verify and save: "); From 8b51e913631c1229848aa24c92a25d5eb0ddc343 Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Sat, 31 Jan 2026 17:46:36 +0200 Subject: [PATCH 14/15] Fix flag description Signed-off-by: Cristian Andrei --- src/run.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/run.rs b/src/run.rs index f7d7d614..b37b228a 100644 --- a/src/run.rs +++ b/src/run.rs @@ -159,7 +159,7 @@ fn get_cli_args() -> ArgMatches { .action(ArgAction::SetTrue) .requires("mount-point") .requires("data-dir") - .help("Initialize Google 2FA for this filesystem. Required only for the first run to bind the user."), + .help("Initialize TOTP 2FA for this filesystem. Required only for the first run to bind the user."), ) .arg( Arg::new("allow-root") From 95f194493781613ab78dffa8ce0c6738c1d982f3 Mon Sep 17 00:00:00 2001 From: Cristian Andrei Date: Fri, 13 Feb 2026 14:11:00 +0200 Subject: [PATCH 15/15] Add additional testing 2FA Signed-off-by: Cristian Andrei --- src/encryptedfs/test.rs | 64 +++++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/src/encryptedfs/test.rs b/src/encryptedfs/test.rs index a74f0159..37c144a7 100644 --- a/src/encryptedfs/test.rs +++ b/src/encryptedfs/test.rs @@ -2492,33 +2492,67 @@ async fn test_totp_secret_binding() { }, async { let fs = get_fs().await; - - // credentials let pass = SecretString::from_str("password").unwrap(); let cipher = Cipher::ChaCha20Poly1305; - let secret = SecretString::from_str("JBSWY3DPEHPK3PXP").unwrap(); // Example Base32 secret + let secret = SecretString::from_str("JBSWY3DPEHPK3PXP").unwrap(); + + // verify inital state + assert!( + !EncryptedFs::is_identity_bound(&fs.data_dir), + "Should not be bound initially" + ); - // bind the secret + // bind secret EncryptedFs::bind_totp_secret(&fs.data_dir, &pass, cipher, &secret) .await .expect("Failed to bind TOTP secret"); - assert!(EncryptedFs::is_identity_bound(&fs.data_dir)); + // verify state after bind + assert!( + EncryptedFs::is_identity_bound(&fs.data_dir), + "Should be bound now" + ); - // retrieve and decrypt + // verify let retrieved = EncryptedFs::get_totp_secret(&fs.data_dir, &pass, cipher) .expect("Failed to retrieve secret"); - assert_eq!( - secret.expose_secret(), - retrieved.expose_secret(), - "Decrypted secret does not match original" - ); + assert_eq!(secret.expose_secret(), retrieved.expose_secret()); + }, + ) + .await; +} + +#[tokio::test] +#[traced_test] +async fn test_totp_update_secret() { + run_test( + TestSetup { + key: "test_totp_update", + read_only: false, + }, + async { + let fs = get_fs().await; + let pass = SecretString::from_str("password").unwrap(); + let cipher = Cipher::ChaCha20Poly1305; + let secret1 = SecretString::from_str("SECRET1").unwrap(); + let secret2 = SecretString::from_str("SECRET2").unwrap(); + + // set first secret + EncryptedFs::bind_totp_secret(&fs.data_dir, &pass, cipher, &secret1) + .await + .unwrap(); + + // overwrite + EncryptedFs::bind_totp_secret(&fs.data_dir, &pass, cipher, &secret2) + .await + .expect("Failed to update secret"); + + // verify for new value + let retrieved = EncryptedFs::get_totp_secret(&fs.data_dir, &pass, cipher).unwrap(); - // test missing file handling - let invalid_path = fs.data_dir.join("non_existent_vault"); - let result = EncryptedFs::get_totp_secret(&invalid_path, &pass, cipher); - assert!(result.is_err()); + assert_eq!(secret2.expose_secret(), retrieved.expose_secret()); + assert_ne!(secret1.expose_secret(), retrieved.expose_secret()); }, ) .await;