diff --git a/Cargo.lock b/Cargo.lock index 8d2d0a9e..b0209e12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -338,7 +338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23e32862ecc63d580f4a5e1436a685f51e0629caeb7a7933e4f017d5e2099e13" dependencies = [ "nix 0.29.0", - "rand", + "rand 0.8.5", ] [[package]] @@ -362,6 +362,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base32" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" + [[package]] name = "base64" version = "0.22.1" @@ -414,7 +420,7 @@ dependencies = [ "arrayvec 0.5.2", "cc", "cfg-if 0.1.10", - "constant_time_eq", + "constant_time_eq 0.1.5", ] [[package]] @@ -479,12 +485,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" @@ -515,6 +533,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" @@ -616,6 +640,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" @@ -631,6 +665,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" @@ -641,6 +681,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" @@ -665,6 +715,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" @@ -735,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" @@ -827,6 +908,17 @@ dependencies = [ "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 = "either" version = "1.13.0" @@ -934,6 +1026,25 @@ 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 = "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" @@ -958,6 +1069,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" @@ -1092,6 +1212,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" @@ -1170,12 +1302,127 @@ dependencies = [ "windows-sys 0.52.0", ] +[[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 = "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 = "2.7.0" @@ -1248,6 +1495,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" @@ -1305,6 +1574,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" @@ -1365,11 +1640,12 @@ dependencies = [ [[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]] @@ -1384,15 +1660,31 @@ 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]] +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" @@ -1517,6 +1809,31 @@ dependencies = [ "autocfg", ] +[[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]] name = "object" version = "0.36.5" @@ -1601,10 +1918,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] +[[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" @@ -1656,6 +1979,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" @@ -1687,6 +2023,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" @@ -1731,6 +2076,48 @@ 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 = "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" +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", + "image", + "qrcodegen", +] + [[package]] name = "quote" version = "1.0.37" @@ -1740,6 +2127,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" @@ -1747,8 +2140,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]] @@ -1758,7 +2161,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]] @@ -1767,7 +2180,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]] @@ -1867,12 +2289,14 @@ dependencies = [ "lru", "num-format", "okaywal", - "rand", - "rand_chacha", - "rand_core", + "qr2term", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", "retainer", "ring", "rpassword", + "secrecy", "serde", "shush-rs", "strum", @@ -1883,10 +2307,13 @@ dependencies = [ "thread_local", "tokio", "tokio-stream", + "totp-rs", "tracing", "tracing-appender", "tracing-subscriber", "tracing-test", + "url", + "webbrowser", ] [[package]] @@ -1898,7 +2325,7 @@ dependencies = [ "async-lock 2.8.0", "async-timer", "log", - "rand", + "rand 0.8.5", ] [[package]] @@ -1909,7 +2336,7 @@ checksum = "ed9b823fa29b721a59671b41d6b06e66b29e0628e207e8b1c3ceeda701ec928d" dependencies = [ "cc", "cfg-if 1.0.0", - "getrandom", + "getrandom 0.2.15", "libc", "untrusted", "windows-sys 0.52.0", @@ -2005,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" @@ -2018,7 +2454,7 @@ dependencies = [ "hkdf", "num", "once_cell", - "rand", + "rand 0.8.5", "serde", "sha2", "zbus", @@ -2031,7 +2467,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 +2491,28 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 +2521,15 @@ 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]] @@ -2154,6 +2601,12 @@ dependencies = [ "libc", ] +[[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" @@ -2198,6 +2651,12 @@ dependencies = [ "lock_api", ] +[[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 +2716,17 @@ dependencies = [ "unicode-ident", ] +[[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 = "tempfile" version = "3.13.0" @@ -2351,6 +2821,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" @@ -2418,6 +2898,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 = "tracing" version = "0.1.40" @@ -2552,6 +3049,30 @@ 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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -2592,6 +3113,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" @@ -2657,6 +3187,22 @@ dependencies = [ "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 = "which" version = "6.0.3" @@ -2700,6 +3246,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[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 +3282,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 +3328,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 +3346,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 +3364,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 +3388,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 +3406,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 +3424,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 +3442,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" @@ -2863,6 +3475,18 @@ 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + [[package]] name = "xdg-home" version = "1.3.0" @@ -2873,6 +3497,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" @@ -2900,7 +3547,7 @@ dependencies = [ "nix 0.26.4", "once_cell", "ordered-stream", - "rand", + "rand 0.8.5", "serde", "serde_repr", "sha1", @@ -2960,12 +3607,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..2290f30e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,12 @@ bon = "3.3.0" shush-rs = "0.1.10" 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 7c2da5a4..e49b07e0 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,65 @@ 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() + } + + /// encrypts and saves the TOTP secret to the vault. + pub async fn bind_totp_secret( + data_dir: &Path, + password: &SecretString, + cipher: Cipher, + 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.expose_secret().as_str(), + 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); + + if !identity_path.exists() { + 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 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(SecretString::new(Box::new(secret))) + } + pub fn exists(&self, ino: u64) -> bool { self.ino_file(ino).is_file() } diff --git a/src/encryptedfs/test.rs b/src/encryptedfs/test.rs index 81e33d05..37c144a7 100644 --- a/src/encryptedfs/test.rs +++ b/src/encryptedfs/test.rs @@ -2481,3 +2481,146 @@ 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; + let pass = SecretString::from_str("password").unwrap(); + let cipher = Cipher::ChaCha20Poly1305; + let secret = SecretString::from_str("JBSWY3DPEHPK3PXP").unwrap(); + + // verify inital state + assert!( + !EncryptedFs::is_identity_bound(&fs.data_dir), + "Should not be bound initially" + ); + + // bind secret + EncryptedFs::bind_totp_secret(&fs.data_dir, &pass, cipher, &secret) + .await + .expect("Failed to bind TOTP secret"); + + // verify state after bind + assert!( + EncryptedFs::is_identity_bound(&fs.data_dir), + "Should be bound now" + ); + + // 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()); + }, + ) + .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(); + + assert_eq!(secret2.expose_secret(), retrieved.expose_secret()); + assert_ne!(secret1.expose_secret(), retrieved.expose_secret()); + }, + ) + .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; +} diff --git a/src/run.rs b/src/run.rs index 8a71ff10..b37b228a 100644 --- a/src/run.rs +++ b/src/run.rs @@ -20,6 +20,7 @@ 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; @@ -152,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 TOTP 2FA for this filesystem. Required only for the first run to bind the user."), + ) .arg( Arg::new("allow-root") .long("allow-root") @@ -267,20 +276,21 @@ async fn run_mount(cipher: Cipher, matches: &ArgMatches) -> Result<()> { .to_string(); let data_dir: String = matches.get_one::("data-dir").unwrap().to_string(); + let data_path = Path::new(&data_dir); - // 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(); password = SecretString::new(Box::new(read_password()?)); + // logic for new directory confirmation if !PathBuf::new().join(data_dir.clone()).is_dir() || fs::read_dir(&data_dir) .await @@ -290,7 +300,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()?)); @@ -300,19 +309,128 @@ 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); }); 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()); } } + // TOTP 2FA Logic + let is_bound = EncryptedFs::is_identity_bound(data_path); + let init_2fa = matches.get_flag("init-2fa"); + + if init_2fa { + if is_bound { + 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 = 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() + .map_err(|e| anyhow::anyhow!("Invalid secret bytes: {}", e))?, + Some("Rencfs".to_string()), + vault_name.to_string(), + ) + .map_err(|e| anyhow::anyhow!("TOTP configuration error: {}", e))?; + + let totp_url = totp.get_url(); + + println!("\n=== 2FA SETUP REQUIRED ==="); + 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!("\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: "); + io::stdout().flush().unwrap(); + let mut code = String::new(); + io::stdin().read_line(&mut code)?; + + 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(|e| { + error!("Failed to unlock 2FA: {}. Password might be incorrect.", e); + ExitStatusError::Failure(1) + })?; + + 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)?; + + 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()); + } + } + } + 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}");