From d79c1935a76c76d510f059d76921f962b4946bc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kr=C3=BChlmann?= Date: Mon, 20 Oct 2025 12:55:35 +0200 Subject: [PATCH] Migrate --- Cargo.lock | 1610 ++++++++++++++++++++++++++++++++++++-- Cargo.toml | 18 +- shell.nix | 1 - src/bitwarden.rs | 124 --- src/bw/api.rs | 29 + src/bw/api_client.rs | 70 ++ src/bw/bitwarden.rs | 21 + src/bw/bitwarden_cli.rs | 126 +++ src/bw/mod.rs | 11 + src/bw/url.rs | 18 + src/{ => data}/config.rs | 25 +- src/data/credentials.rs | 55 ++ src/data/domain.rs | 17 + src/{ => data}/email.rs | 2 +- src/data/mod.rs | 12 + src/data/password.rs | 36 + src/lib.rs | 6 +- src/main.rs | 159 +++- src/statics.rs | 23 +- src/store.rs | 303 +++---- 20 files changed, 2316 insertions(+), 350 deletions(-) delete mode 100644 src/bitwarden.rs create mode 100644 src/bw/api.rs create mode 100644 src/bw/api_client.rs create mode 100644 src/bw/bitwarden.rs create mode 100644 src/bw/bitwarden_cli.rs create mode 100644 src/bw/mod.rs create mode 100644 src/bw/url.rs rename src/{ => data}/config.rs (85%) create mode 100644 src/data/credentials.rs create mode 100644 src/data/domain.rs rename src/{ => data}/email.rs (97%) create mode 100644 src/data/mod.rs create mode 100644 src/data/password.rs diff --git a/Cargo.lock b/Cargo.lock index 4fda540..e7e7e6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,41 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -82,6 +117,46 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -95,20 +170,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cfg-if", + "cfg-if 1.0.0", "libc", "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.17.0" @@ -122,21 +233,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] -name = "caps" -version = "0.5.5" +name = "cc" +version = "1.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190baaad529bcfbde9e1a19022c42781bdb6ff9de25721abdb8fd98c0807730b" +checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" dependencies = [ - "libc", - "thiserror 1.0.69", + "shlex", ] +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_lex 0.2.4", + "indexmap 1.9.3", + "strsim 0.10.0", + "termcolor", + "textwrap", +] + [[package]] name = "clap" version = "4.5.37" @@ -155,8 +296,8 @@ checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" dependencies = [ "anstream", "anstyle", - "clap_lex", - "strsim", + "clap_lex 0.7.4", + "strsim 0.11.1", ] [[package]] @@ -171,6 +312,15 @@ dependencies = [ "syn", ] +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "clap_lex" version = "0.7.4" @@ -183,19 +333,95 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "deunicode" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc55fe0d1f6c107595572ec8b107c0999bb1a2e0b75e37429a4fb0d6474a0e7d" +[[package]] +name = "dialog" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736bab36d647d14c985725a57a4110a1182c6852104536cd42f1c97e96d29bf0" +dependencies = [ + "dirs 2.0.2", + "rpassword 2.1.0", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "directories" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" dependencies = [ - "dirs-sys", + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if 0.1.10", + "dirs-sys 0.3.7", ] [[package]] @@ -204,7 +430,18 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys", + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users 0.4.6", + "winapi 0.3.9", ] [[package]] @@ -215,16 +452,82 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users", + "redox_users 0.5.0", "windows-sys 0.59.0", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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 = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures" version = "0.3.31" @@ -314,15 +617,47 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", ] [[package]] @@ -331,6 +666,31 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "h2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.9.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[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" @@ -343,6 +703,301 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +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", +] + [[package]] name = "indexmap" version = "2.9.0" @@ -350,9 +1005,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -377,6 +1047,26 @@ dependencies = [ "slug", ] +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -395,10 +1085,22 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags", + "bitflags 2.9.0", "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" + [[package]] name = "lock_api" version = "0.4.12" @@ -430,6 +1132,12 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[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.8" @@ -446,10 +1154,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -457,7 +1182,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ "overload", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -475,12 +1200,68 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" +dependencies = [ + "bitflags 2.9.0", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + [[package]] name = "overload" version = "0.1.1" @@ -503,13 +1284,40 @@ version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -522,6 +1330,24 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -535,24 +1361,33 @@ dependencies = [ name = "punlock" version = "0.1.0" dependencies = [ + "aes-gcm", "anyhow", - "caps", - "cfg-if", - "clap", + "argon2", + "async-trait", + "base64", + "clap 4.5.37", + "dialog", "directories", - "dirs", + "dirs 6.0.0", "futures", + "hex", + "hkdf", + "hmac", "jmespath", "lazy_static", + "pbkdf2", "regex", - "rpassword", + "reqwest", + "rpassword 7.4.0", "serde", "serde_json", + "sha2", "tokio", "toml", "tracing", "tracing-subscriber", - "users", + "urlencode", ] [[package]] @@ -564,13 +1399,39 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + [[package]] name = "redox_syscall" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" dependencies = [ - "bitflags", + "bitflags 2.9.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", ] [[package]] @@ -579,7 +1440,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ - "getrandom", + "getrandom 0.2.16", "libredox", "thiserror 2.0.12", ] @@ -628,6 +1489,75 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "reqwest" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if 1.0.0", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rpassword" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37473170aedbe66ffa3ad3726939ba677d83c646ad4fd99e5b4bc38712f45ec" +dependencies = [ + "kernel32-sys", + "libc", + "winapi 0.2.8", +] + [[package]] name = "rpassword" version = "7.4.0" @@ -655,6 +1585,58 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustix" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.23.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[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.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" + +[[package]] +name = "rustls-webpki" +version = "0.103.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.20" @@ -667,12 +1649,44 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.219" @@ -714,6 +1728,29 @@ dependencies = [ "serde", ] +[[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 = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -723,6 +1760,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.5" @@ -767,12 +1810,30 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.100" @@ -784,6 +1845,75 @@ dependencies = [ "unicode-ident", ] +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +dependencies = [ + "fastrand", + "getrandom 0.3.2", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" + [[package]] name = "thiserror" version = "1.0.69" @@ -830,10 +1960,20 @@ version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tokio" version = "1.44.2" @@ -863,6 +2003,39 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.8.20" @@ -890,13 +2063,40 @@ version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ - "indexmap", + "indexmap 2.9.0", "serde", "serde_spanned", "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", + "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.41" @@ -958,6 +2158,18 @@ dependencies = [ "tracing-log", ] +[[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.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -965,15 +2177,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] -name = "users" -version = "0.11.0" +name = "universal-hash" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "libc", - "log", + "crypto-common", + "subtle", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "urlencode" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c992a8219ab268d5e605fae32ae2574e740def9cfe641066cbcfa78158c755af" +dependencies = [ + "clap 3.2.25", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[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" @@ -986,19 +2237,49 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", "rustversion", "wasm-bindgen-macro", @@ -1018,6 +2299,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.100" @@ -1050,6 +2344,22 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + [[package]] name = "winapi" version = "0.3.9" @@ -1060,25 +2370,75 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.53.0", +] + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1087,7 +2447,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1096,14 +2456,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -1112,48 +2488,96 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" version = "0.7.6" @@ -1162,3 +2586,97 @@ checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" dependencies = [ "memchr", ] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[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", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 5751e40..223a354 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,21 +8,33 @@ name = "punlock" path = "src/lib.rs" [dependencies] +aes-gcm = "0.10.3" anyhow = "1.0.98" -caps = "0.5.5" -cfg-if = "1.0.0" +argon2 = "0.5.3" +async-trait = "0.1.88" +base64 = "0.22.1" clap = { version = "4.5.37", features = ["derive"] } +dialog = "0.3.0" directories = "6.0.0" dirs = "6.0.0" futures = "0.3.31" +hex = "0.4.3" +hkdf = "0.12.4" +hmac = "0.12.1" jmespath = "0.3.0" lazy_static = "1.5.0" +pbkdf2 = "0.12.2" regex = "1.11.1" +reqwest = { version = "0.12.15", features = ["json"] } rpassword = "7.4.0" serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" +sha2 = "0.10.9" tokio = { version = "1.44.2", features = ["full"] } toml = "0.8.20" tracing = "0.1.41" tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } -users = "0.11.0" +urlencode = "1.0.1" + +[target.x86_64-pc-windows-gnu] +linker = "x86_64-w64-mingw32-gcc" diff --git a/shell.nix b/shell.nix index 3185273..70564ed 100644 --- a/shell.nix +++ b/shell.nix @@ -2,7 +2,6 @@ pkgs.mkShell { buildInputs = [ - pkgs.cargo-llvm-cov pkgs.openssl pkgs.pkg-config pkgs.rust-analyzer diff --git a/src/bitwarden.rs b/src/bitwarden.rs deleted file mode 100644 index d7ab098..0000000 --- a/src/bitwarden.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::process::Stdio; - -use tokio::process::Command; - -use crate::{config::PunlockConfigurationEntry, email::Email}; - -pub struct Bitwarden { - email: Email, - session: S, -} - -impl Bitwarden<()> { - pub fn new(email: Email) -> Self { - Self { email, session: () } - } - - pub async fn authenticate(self, domain: Option) -> anyhow::Result> { - Command::new("bw") - .args(["logout"]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .await - .inspect(|_| tracing::debug!("spawn logout")) - .inspect_err(|error| tracing::error!(?error, "spawn logout")) - .ok(); - if let Some(ref d) = domain { - Command::new("bw") - .args(["config", "server", &d]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .await - .inspect(|_| tracing::debug!(?domain, "spawn config server")) - .inspect_err(|error| tracing::error!(?error, ?domain, "spawn config server")) - .ok(); - } - loop { - let prompt = match domain { - Some(ref d) => format!("Enter password for bitwarden[{d}] user {}: ", self.email), - None => format!("Enter password for bitwarden user {}: ", self.email), - }; - let password = rpassword::prompt_password(prompt) - .inspect_err(|error| tracing::error!(?error, "read password")) - .unwrap_or("".to_string()); - if password.trim().is_empty() { - continue; - } - - let out = Command::new("bw") - .args(["login", self.email.as_ref(), &password, "--raw"]) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .output() - .await - .inspect(|_| tracing::debug!("spawn login")) - .inspect_err(|error| tracing::error!(?error, "spawn login"))?; - - if !out.status.success() { - let error = String::from_utf8_lossy(&out.stderr); - tracing::error!(?error, "bitwarden error"); - continue; - } - - let session = String::from_utf8_lossy(&out.stdout).trim().to_string(); - if session.is_empty() { - continue; - } - return Ok(Bitwarden:: { - email: self.email, - session, - }); - } - } -} - -impl Bitwarden { - pub async fn fetch(&self, entry: &PunlockConfigurationEntry) -> anyhow::Result { - let bw = Command::new("bw") - .args(["get", "item", &entry.id, "--session", &self.session]) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .inspect_err(|error| tracing::error!(?error, "spawn get"))?; - - let output = bw - .wait_with_output() - .await - .inspect_err(|error| tracing::error!(?error, "spawn get"))?; - - if !output.status.success() { - let err = String::from_utf8_lossy(&output.stderr); - anyhow::bail!("`bw get item` failed: {}", err.trim()); - } - - let data: serde_json::Value = serde_json::from_slice(&output.stdout) - .inspect_err(|error| tracing::error!(?error, json = ?output.stdout, "invalid json"))?; - - let expr = jmespath::compile(&entry.query) - .inspect_err(|error| tracing::error!(?error, ?entry, "input/expression mismatch"))?; - let result = expr - .search(&data) - .inspect_err(|error| tracing::error!(?error, ?expr, "query failed to apply"))?; - - let secret = match result.as_string() { - Some(s) => s.to_owned(), - None => anyhow::bail!("invalid item"), - }; - - Ok(secret) - } - - pub async fn logout(&self) -> anyhow::Result<()> { - Command::new("bw") - .args(["logout", "--session", &self.session]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .await - .inspect(|_| tracing::debug!("spawn logout")) - .inspect_err(|error| tracing::error!(?error, "spawn logout"))?; - Ok(()) - } -} diff --git a/src/bw/api.rs b/src/bw/api.rs new file mode 100644 index 0000000..8051184 --- /dev/null +++ b/src/bw/api.rs @@ -0,0 +1,29 @@ +use serde::Deserialize; + +#[derive(Clone, Deserialize)] +pub struct BitwardenToken { + #[serde(rename = "Kdf")] + pub kdf: i64, + #[serde(rename = "KdfIterations")] + pub kdf_iterations: u32, + #[serde(rename = "KdfMemory")] + pub kdf_memory: Option, + #[serde(rename = "KdfParallelism")] + pub kdf_parallelism: Option, + #[serde(rename = "Key")] + pub key: String, + #[serde(rename = "PrivateKey")] + pub private_key: String, + #[serde(rename = "ResetMasterPassword")] + pub reset_master_password: bool, + pub access_token: String, + pub expires_in: usize, + pub scope: String, + pub token_type: String, +} + +#[async_trait::async_trait] +pub trait BitwardenApi { + async fn get_item(&self, id: &str) -> anyhow::Result; + fn get_token(&self) -> &BitwardenToken; +} diff --git a/src/bw/api_client.rs b/src/bw/api_client.rs new file mode 100644 index 0000000..10aff77 --- /dev/null +++ b/src/bw/api_client.rs @@ -0,0 +1,70 @@ +use crate::bw::BitwardenApi; +use crate::bw::BitwardenToken; +use crate::data::BitwardenClientCredentials; +use crate::data::Domain; +use crate::data::Email; + +use super::BitwardenUrl; + +pub struct BitwardenHttpClient { + token: BitwardenToken, + client: reqwest::Client, + url: BitwardenUrl, +} + +impl BitwardenHttpClient { + pub async fn new( + email: &Email, + credentials: &BitwardenClientCredentials, + domain: Domain, + ) -> anyhow::Result { + tracing::debug!(?email, ?domain, "logging in"); + let url = BitwardenUrl(domain); + let client = reqwest::Client::new(); + let params = [ + ("grant_type", "client_credentials"), + ("scope", "api"), + ("client_id", &credentials.id), + ("client_secret", &credentials.secret), + ("device_identifier", env!("CARGO_PKG_NAME")), + ("device_type", env!("CARGO_PKG_NAME")), + ("device_name", env!("CARGO_PKG_NAME")), + ]; + let token = client + .post(url.as_identity_url()) + .form(¶ms) + .send() + .await? + .error_for_status() + .inspect_err(|error| tracing::error!(?error, "http error"))? + .json::() + .await + .inspect_err(|error| tracing::error!(?error, "deserialization error"))?; + + tracing::debug!(?email, ?url, "login successful"); + let bitwarden_client = Self { client, token, url }; + Ok(bitwarden_client) + } +} + +#[async_trait::async_trait] +impl BitwardenApi for BitwardenHttpClient { + async fn get_item(&self, id: &str) -> anyhow::Result { + tracing::debug!(?id, "request item"); + let response = self + .client + .get(self.url.as_cipher_url(id)) + .bearer_auth(&self.token.access_token) + .send() + .await? + .error_for_status()? + .json::() + .await?; + tracing::debug!(?id, ?response, "item retrieved"); + Ok(response) + } + + fn get_token(&self) -> &BitwardenToken { + &self.token + } +} diff --git a/src/bw/bitwarden.rs b/src/bw/bitwarden.rs new file mode 100644 index 0000000..f9e1d15 --- /dev/null +++ b/src/bw/bitwarden.rs @@ -0,0 +1,21 @@ +pub struct EncryptedBitwardenItem(pub String); + +impl From for EncryptedBitwardenItem +where + T: AsRef, +{ + fn from(value: T) -> Self { + Self(value.as_ref().to_string()) + } +} + +impl Into for EncryptedBitwardenItem { + fn into(self) -> String { + self.0.clone() + } +} + +#[async_trait::async_trait] +pub trait Bitwarden { + async fn get_item(&self, id: &str, query: &str) -> anyhow::Result; +} diff --git a/src/bw/bitwarden_cli.rs b/src/bw/bitwarden_cli.rs new file mode 100644 index 0000000..01f2343 --- /dev/null +++ b/src/bw/bitwarden_cli.rs @@ -0,0 +1,126 @@ +use std::process::Stdio; + +use tokio::process::Command; + +use super::{bitwarden::EncryptedBitwardenItem, Bitwarden}; +use crate::data::{Email, Password}; + +enum BitwardenCliCommand<'a> { + SetDomain(&'a str), + GetItem(&'a str, &'a str), + Login(&'a str, &'a str), +} + +impl BitwardenCliCommand<'_> { + fn create_process(self) -> tokio::process::Command { + match self { + BitwardenCliCommand::GetItem(id, session) => { + let mut cmd = Command::new("bw"); + cmd.args(["get", "item", &id, "--session", &session]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + cmd + } + BitwardenCliCommand::SetDomain(domain) => { + let mut cmd = Command::new("bw"); + cmd.args(["config", "server", &domain]) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + cmd + } + BitwardenCliCommand::Login(email, password) => { + let mut cmd = Command::new("bw"); + cmd.args(["login", &email, &password, "--raw"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + cmd + } + } + } +} + +pub struct BitwardenCli { + email: Email, + session: S, +} + +impl BitwardenCli<()> { + pub fn new(email: Email) -> Self { + Self { email, session: () } + } + + pub async fn authenticate( + self, + domain: Option, + ) -> anyhow::Result> { + if let Some(ref d) = domain { + BitwardenCliCommand::SetDomain(d) + .create_process() + .status() + .await + .inspect(|_| tracing::debug!(?domain, "spawn config server")) + .inspect_err(|error| tracing::error!(?error, ?domain, "spawn config server")) + .ok(); + } + loop { + let prompt = match domain { + Some(ref d) => format!("Enter password for bitwarden[{d}] user {}: ", self.email), + None => format!("Enter password for bitwarden user {}: ", self.email), + }; + let Password(password) = Password::from_user_input(&prompt, "Enter Password"); + let output = BitwardenCliCommand::Login(&self.email.to_string(), &password) + .create_process() + .output() + .await + .inspect(|_| tracing::debug!("spawn login")) + .inspect_err(|error| tracing::error!(?error, "spawn login"))?; + + if !output.status.success() { + let error = String::from_utf8_lossy(&output.stderr); + if !error.starts_with("You are already logged in as") { + tracing::error!(?error, "login"); + continue; + } + tracing::info!(?error, "errror"); + } + + let session = String::from_utf8_lossy(&output.stdout).trim().to_string(); + if session.is_empty() { + continue; + } + return Ok(BitwardenCli:: { + email: self.email, + session, + }); + } + } +} + +#[async_trait::async_trait] +impl Bitwarden for BitwardenCli { + async fn get_item(&self, id: &str, query: &str) -> anyhow::Result { + let output = BitwardenCliCommand::GetItem(&id, &self.session) + .create_process() + .spawn() + .inspect_err(|error| tracing::error!(?error, "spawn get"))? + .wait_with_output() + .await + .inspect_err(|error| tracing::error!(?error, "spawn get"))?; + if !output.status.success() { + let err = String::from_utf8_lossy(&output.stderr); + anyhow::bail!("`bw get item` failed: {}", err.trim()); + } + let data: serde_json::Value = serde_json::from_slice(&output.stdout) + .inspect_err(|error| tracing::error!(?error, json = ?output.stdout, "invalid json"))?; + let secret: EncryptedBitwardenItem = jmespath::compile(&query) + .inspect_err(|error| tracing::error!(?error, ?query, "input/expression mismatch"))? + .search(&data) + .inspect_err(|error| tracing::error!(?error, ?query, "query failed to apply"))? + .as_string() + .map(|s| s.to_owned()) + .ok_or(anyhow::anyhow!("invalid item"))? + .into(); + + Ok(secret) + } +} diff --git a/src/bw/mod.rs b/src/bw/mod.rs new file mode 100644 index 0000000..c86c58a --- /dev/null +++ b/src/bw/mod.rs @@ -0,0 +1,11 @@ +pub mod api; +pub mod api_client; +pub mod bitwarden; +pub mod bitwarden_cli; +pub mod url; + +pub use api::BitwardenApi; +pub use api::BitwardenToken; +pub use api_client::BitwardenHttpClient; +pub use bitwarden::Bitwarden; +pub use url::BitwardenUrl; diff --git a/src/bw/url.rs b/src/bw/url.rs new file mode 100644 index 0000000..b1d3c6b --- /dev/null +++ b/src/bw/url.rs @@ -0,0 +1,18 @@ +use crate::data::Domain; + +#[derive(Debug)] +pub struct BitwardenUrl(pub Domain); + +impl BitwardenUrl { + pub fn as_identity_url(&self) -> String { + format!("https://{}/identity/connect/token", self.0) + } + + pub fn as_vault_url(&self) -> String { + format!("https://{}/api/sync", self.0) + } + + pub fn as_cipher_url(&self, id: &str) -> String { + format!("https://{}/api/ciphers/{}", self.0, id) + } +} diff --git a/src/config.rs b/src/data/config.rs similarity index 85% rename from src/config.rs rename to src/data/config.rs index 9dfb65d..be40e88 100644 --- a/src/config.rs +++ b/src/data/config.rs @@ -1,15 +1,16 @@ use std::convert::{TryFrom, TryInto}; use std::path::Path; -use serde::{Deserialize, Serialize}; use tokio::{fs::File, io::AsyncWriteExt}; -use crate::{ - email::Email, - statics::{LATEST_CONFIGURATION_VERSION, SYSTEM_CONFIG_PATH_CANDIDATES}, +use crate::statics::{ + DEFAULT_BITWARDEN_DOMAIN, LATEST_CONFIGURATION_VERSION, SYSTEM_CONFIG_PATH_CANDIDATES, }; -#[derive(Deserialize, Serialize, Debug)] +use super::Domain; +use super::Email; + +#[derive(serde::Deserialize, serde::Serialize, Debug)] pub struct PunlockConfigurationEntry { pub id: String, pub query: String, @@ -19,12 +20,13 @@ pub struct PunlockConfigurationEntry { pub public: bool, } -#[derive(Deserialize)] +#[derive(serde::Deserialize)] pub struct PartialPunlockConfiguration { pub domain: Option, pub version: Option, pub email: Option, pub entries: Option>, + pub cache_token: Option, } impl TryFrom<&Path> for PartialPunlockConfiguration { @@ -60,11 +62,12 @@ impl PartialPunlockConfiguration { } } -#[derive(Serialize)] +#[derive(serde::Serialize)] pub struct PunlockConfiguration { + pub cache_token: bool, pub version: String, pub email: Email, - pub domain: Option, + pub domain: Domain, pub entries: Vec, } @@ -73,7 +76,11 @@ impl TryFrom for PunlockConfiguration { fn try_from(value: PartialPunlockConfiguration) -> anyhow::Result { Ok(Self { - domain: value.domain, + cache_token: value.cache_token.unwrap_or(false), + domain: value + .domain + .unwrap_or(DEFAULT_BITWARDEN_DOMAIN.to_string()) + .into(), version: value .version .unwrap_or(LATEST_CONFIGURATION_VERSION.to_string()), diff --git a/src/data/credentials.rs b/src/data/credentials.rs new file mode 100644 index 0000000..28bb5c1 --- /dev/null +++ b/src/data/credentials.rs @@ -0,0 +1,55 @@ +use std::path::Path; + +use serde::{Deserialize, Serialize}; +use tokio::io::AsyncWriteExt; + +use super::Password; + +#[derive(Deserialize, Serialize)] +pub struct BitwardenClientCredentials { + pub id: String, + pub secret: String, +} + +impl BitwardenClientCredentials { + pub async fn new(path: impl AsRef) -> Self { + match tokio::fs::read(&path).await { + Ok(content_vec_u8) => { + match String::from_utf8_lossy(&content_vec_u8).parse::() { + Ok(content) => match toml::from_str::(&content) { + Ok(config) => return config, + Err(error) => { + tracing::error!(?error, "invalid credentials file; deleting"); + tokio::fs::remove_file(&path).await.inspect_err(|e| tracing::error!(error = ?e, "unable to remove credentials file")).ok(); + } + }, + Err(error) => tracing::warn!(?error, "parse credentials file content"), + } + } + Err(error) => tracing::warn!(?error, "read credentials file"), + }; + return BitwardenClientCredentials::from_user_prompt(); + } + + pub async fn write_to_disk(&self, path: impl AsRef) -> anyhow::Result<()> { + let path = path.as_ref(); + let contents = toml::to_string_pretty(self)?; + let mut file = tokio::fs::File::create(path).await.inspect_err(|error| { + tracing::error!(?path, ?error, "unable to create credentials file") + })?; + file.write_all(contents.as_bytes()) + .await + .inspect_err(|error| { + tracing::error!(?path, ?error, "unable to write credentials to file") + })?; + tracing::debug!(?path, "wrote credentials"); + Ok(()) + } + + fn from_user_prompt() -> Self { + let Password(id) = Password::from_user_input("Enter client id", "Enter client id"); + let Password(secret) = + Password::from_user_input("Enter client secret", "Enter client secret"); + Self { id, secret } + } +} diff --git a/src/data/domain.rs b/src/data/domain.rs new file mode 100644 index 0000000..dbbf85a --- /dev/null +++ b/src/data/domain.rs @@ -0,0 +1,17 @@ +#[derive(Clone, Debug, serde::Serialize)] +pub struct Domain(String); + +impl From for Domain +where + T: Into, +{ + fn from(value: T) -> Self { + Self(value.into()) + } +} + +impl std::fmt::Display for Domain { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/src/email.rs b/src/data/email.rs similarity index 97% rename from src/email.rs rename to src/data/email.rs index ce38c66..c792f8c 100644 --- a/src/email.rs +++ b/src/data/email.rs @@ -5,7 +5,7 @@ use serde::Serialize; use crate::statics::EMAIL_REGEX; -#[derive(Serialize)] +#[derive(Serialize, Clone, Debug)] pub struct Email(String); impl Email { diff --git a/src/data/mod.rs b/src/data/mod.rs new file mode 100644 index 0000000..1eca1ac --- /dev/null +++ b/src/data/mod.rs @@ -0,0 +1,12 @@ +pub mod config; +pub mod credentials; +pub mod domain; +pub mod email; +pub mod password; + +pub use config::PunlockConfiguration; +pub use config::PunlockConfigurationEntry; +pub use credentials::BitwardenClientCredentials; +pub use domain::Domain; +pub use email::Email; +pub use password::Password; diff --git a/src/data/password.rs b/src/data/password.rs new file mode 100644 index 0000000..075bf63 --- /dev/null +++ b/src/data/password.rs @@ -0,0 +1,36 @@ +#[cfg(unix)] +use dialog::DialogBox; + +pub struct Password(pub String); + +impl Password { + pub fn from_user_input(prompt: &str, _title: &str) -> Self { + let mut password = "".to_string(); + while password.trim().is_empty() { + #[cfg(unix)] + { + password = dialog::Password::new(prompt) + .title(_title) + .show() + .unwrap_or(None) + .unwrap_or("".to_string()) + .trim() + .to_string(); + } + #[cfg(not(unix))] + { + password = rpassword::prompt_password(prompt).unwrap_or_else(|_| String::new()); + } + } + password.into() + } +} + +impl From for Password +where + T: AsRef, +{ + fn from(value: T) -> Self { + Self(value.as_ref().to_string()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 4b9abe2..07aeb88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ -pub mod bitwarden; -pub mod config; -pub mod email; +#![feature(stmt_expr_attributes)] +pub mod bw; +pub mod data; pub mod statics; pub mod store; diff --git a/src/main.rs b/src/main.rs index 6b744f9..13990d7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,22 @@ -use std::convert::TryInto; +use aes_gcm::aes::cipher::BlockDecrypt; +use aes_gcm::aes::Aes256; +use aes_gcm::KeyInit; +use anyhow::Context; +use base64::Engine; +use hkdf::Hkdf; +use hmac::Hmac; +use pbkdf2::pbkdf2_hmac; +use sha2::Sha256; +use std::convert::{TryFrom, TryInto}; use std::path::{Path, PathBuf}; use clap::Parser; -use punlock::{ - bitwarden::Bitwarden, - config::{PartialPunlockConfiguration, PunlockConfiguration}, - statics::USER_CONFIG_FILE_PATH, - store::UnmountedSecretStore, -}; +use hmac::Mac; +use punlock::bw::{BitwardenApi, BitwardenHttpClient}; +use punlock::data::config::PartialPunlockConfiguration; +use punlock::data::{BitwardenClientCredentials, Password, PunlockConfiguration}; +use punlock::statics::USER_CREDENTIALS_FILE_PATH; +use punlock::{statics::USER_CONFIG_FILE_PATH, store::SecretStore}; use tracing_subscriber::{ fmt::format::FmtSpan, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, }; @@ -19,8 +28,124 @@ struct Cli { pub config: Option, } +#[derive(Debug)] +struct Cipher { + pub iv: Vec, + pub ct: Vec, + pub mac: Vec, +} + +impl TryFrom<&str> for Cipher { + type Error = anyhow::Error; + + fn try_from(value: &str) -> Result { + let rest = value.splitn(2, '.').nth(1).expect("invalid format, no dot"); + let parts: Vec<&str> = rest.split('|').collect(); + assert_eq!(parts.len(), 3, "expected three parts"); + let iv = base64::engine::general_purpose::STANDARD.decode(parts[0])?; + let ct = base64::engine::general_purpose::STANDARD.decode(parts[1])?; + let mac = base64::engine::general_purpose::STANDARD.decode(parts[2])?; + + Ok(Self { iv, ct, mac }) + } +} +fn decrypt_aes_cbc_pkcs7(key: &[u8; 32], iv: &[u8], ct: &[u8]) -> anyhow::Result> { + let iv: &[u8; 16] = iv.try_into()?; + if ct.len() % 16 != 0 { + anyhow::bail!("ciphertext length must be a multiple of 16"); + } + let cipher = Aes256::new(key.into()); + let mut prev = *iv; + let mut plaintext = Vec::with_capacity(ct.len()); + for chunk in ct.chunks(16) { + let mut block = <[u8; 16]>::try_from(chunk).expect("chunk is 16 bytes"); + cipher.decrypt_block(&mut block.into()); + for i in 0..16 { + block[i] ^= prev[i]; + } + plaintext.extend_from_slice(&block); + prev = <[u8; 16]>::try_from(chunk).unwrap(); + } + + // 4) Strip & verify PKCS#7 padding + let pad_len = *plaintext.last().context("decrypted data is empty")? as usize; + if pad_len == 0 || pad_len > 16 { + anyhow::bail!("invalid padding length"); + } + let len = plaintext.len(); + let pad_start = len - pad_len; + if !plaintext[pad_start..] + .iter() + .all(|&b| b as usize == pad_len) + { + anyhow::bail!("invalid PKCS#7 padding"); + } + plaintext.truncate(pad_start); + + Ok(plaintext) +} + +fn testdecrypt( + config: &PunlockConfiguration, + bitwarden: &BitwardenHttpClient, +) -> anyhow::Result<()> { + let Password(master_password) = Password::from_user_input("Enter master password", ""); + let str = "2.lt/eAKnlHsPcUCR5bGf/Kg==|8xFsF52BQx14Pb6MuW7ByYLE3ptmbTER+FxDhwGBj10=|6CDbFNKPyjXpOYblz64XFV88ofgDUKpM0YVaGLnWVt0="; + let mut master_key = [0u8; 32]; + pbkdf2_hmac::( + master_password.as_bytes(), + config.email.to_string().as_bytes(), + bitwarden.get_token().kdf_iterations, + &mut master_key, + ); + tracing::info!(master_key = hex::encode(&master_key), "master key"); + + // --- HKDF expand --- + + let (_prk_bytes, hk) = Hkdf::::extract(None, &master_key); + let mut stretch_enc = [0u8; 32]; + let mut stretch_mac = [0u8; 32]; + println!("stretch_enc = {:x?}", stretch_enc); + println!("stretch_mac = {:x?}", stretch_mac); + hk.expand(b"enc", &mut stretch_enc) + .map_err(|e| anyhow::anyhow!("{e}")) + .context("HKDF expand(enc) failed")?; + hk.expand(b"mac", &mut stretch_mac) + .map_err(|e| anyhow::anyhow!("{e}")) + .context("HKDF expand(mac) failed")?; + + // --- Parse blob --- + let cipher: Cipher = str.try_into()?; + + // --- Verify HMAC --- + type HmacSha256 = Hmac; + let mut h = ::new_from_slice(&stretch_mac)?; + h.update(&cipher.iv); + h.update(&cipher.ct); + let computed = h.clone().finalize().into_bytes(); + println!("computed MAC = {}", hex::encode(&computed)); + println!("expected MAC = {}", hex::encode(&cipher.mac)); + h.verify_slice(&cipher.mac) + .map_err(|e| anyhow::anyhow!("{e}")) + .context("HMAC verification failed")?; + + // --- AES-CBC decrypt (64 bytes) --- + let clear = decrypt_aes_cbc_pkcs7(&stretch_enc, &cipher.iv, &cipher.ct) + .map_err(|e| anyhow::anyhow!("{e}")) + .context("AES-CBC decrypt failed")?; + + // --- Split into real vault keys --- + let vault_enc_key: [u8; 32] = clear[0..32].try_into().unwrap(); + let vault_mac_key: [u8; 32] = clear[32..64].try_into().unwrap(); + + println!("Vault ENC key: {:x?}", vault_enc_key); + println!("Vault MAC key: {:x?}", vault_mac_key); + + Ok(()) +} + #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() -> anyhow::Result<()> { let formattter = tracing_subscriber::fmt::Layer::new() .with_thread_names(true) .with_span_events(FmtSpan::FULL); @@ -40,17 +165,21 @@ async fn main() -> Result<(), Box> { }; let config: PunlockConfiguration = config.try_into()?; - config .write_to_disk(USER_CONFIG_FILE_PATH.as_path()) .await?; - let bitwarden = Bitwarden::new(config.email) - .authenticate(config.domain) + let credentials = BitwardenClientCredentials::new(USER_CREDENTIALS_FILE_PATH.as_path()).await; + credentials + .write_to_disk(USER_CREDENTIALS_FILE_PATH.as_path()) .await?; - let store = UnmountedSecretStore::new(bitwarden) - .into_platform_store() - .await?; - store.write_secrets(&config.entries).await?; + + let bitwarden = + BitwardenHttpClient::new(&config.email, &credentials, config.domain.clone()).await?; + + testdecrypt(&config, &bitwarden)?; + + // let store = SecretStore::new(bitwarden).await?; + // store.write_secrets(&config.entries).await?; Ok(()) } diff --git a/src/statics.rs b/src/statics.rs index 75d8006..cabad6e 100644 --- a/src/statics.rs +++ b/src/statics.rs @@ -7,16 +7,31 @@ use regex::Regex; lazy_static! { pub static ref LATEST_CONFIGURATION_VERSION: &'static str = "1.0.0"; pub static ref CONFIG_FILE_NAME: &'static str = "config.toml"; + pub static ref CREDENTIALS_FILE_NAME: &'static str = "credentials.toml"; pub static ref PROJECT_DIRS: ProjectDirs = ProjectDirs::from("dev", "kruhlmann", "punlock").unwrap(); pub static ref USER_CONFIG_FILE_PATH: PathBuf = PROJECT_DIRS.config_dir().join(CONFIG_FILE_NAME.to_string()); + pub static ref USER_CREDENTIALS_FILE_PATH: PathBuf = PROJECT_DIRS + .config_dir() + .join(CREDENTIALS_FILE_NAME.to_string()); pub static ref HOME_DIRECTORY: PathBuf = dirs::home_dir().unwrap(); - #[cfg(target_os = "linux")] - pub static ref RUNTIME_DIRECTORY: PathBuf = dirs::runtime_dir().unwrap(); - pub static ref SYSTEM_CONFIG_PATH_CANDIDATES: Vec = [PathBuf::from(CONFIG_FILE_NAME.to_string()), + pub static ref SYSTEM_CONFIG_PATH_CANDIDATES: Vec = [ + PathBuf::from(CONFIG_FILE_NAME.to_string()), USER_CONFIG_FILE_PATH.to_path_buf(), - PathBuf::from("/etc/punlock/").join("CONFIG_FILE_NAME")] + PathBuf::from("/etc/punlock/").join("CONFIG_FILE_NAME") + ] .to_vec(); pub static ref EMAIL_REGEX: Regex = Regex::new(r"^[^\s@]+@[^\s@]+\.[^\s@]+$").unwrap(); + pub static ref DEFAULT_BITWARDEN_DOMAIN: &'static str = "vault.bitwarden.com"; +} + +#[cfg(unix)] +lazy_static! { + pub static ref RUNTIME_DIRECTORY: PathBuf = dirs::runtime_dir().unwrap(); +} + +#[cfg(not(unix))] +lazy_static! { + pub static ref RUNTIME_DIRECTORY: PathBuf = std::env::temp_dir(); } diff --git a/src/store.rs b/src/store.rs index b25e87f..b12d19d 100644 --- a/src/store.rs +++ b/src/store.rs @@ -1,157 +1,71 @@ -use std::{os::unix::fs::PermissionsExt, path::PathBuf, sync::Arc}; +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; -use futures::{StreamExt, stream::FuturesUnordered}; +use aes_gcm::{aead::Aead, aes::Aes256, Aes256Gcm, KeyInit, Nonce}; +use anyhow::Context; +use argon2::{Argon2, Params}; +use base64::{engine::general_purpose, Engine}; +use futures::{stream::FuturesUnordered, StreamExt}; +use hkdf::Hkdf; +use hmac::{Hmac, Mac}; +use pbkdf2::pbkdf2_hmac; +use sha2::Sha256; use tokio::io::AsyncWriteExt; use crate::{ - bitwarden::Bitwarden, - config::PunlockConfigurationEntry, - statics::{self, HOME_DIRECTORY}, + bw::{bitwarden::EncryptedBitwardenItem, BitwardenApi, BitwardenToken}, + data::PunlockConfigurationEntry, + statics::{HOME_DIRECTORY, RUNTIME_DIRECTORY}, }; -pub struct UnmountedSecretStore { - bitwarden: Bitwarden, +pub struct SecretStore { + path: Arc, + bitwarden: Arc>, } -impl UnmountedSecretStore { - pub fn new(bitwarden: Bitwarden) -> Self { - Self { bitwarden } - } -} +// struct DecryptedString(String); -impl UnmountedSecretStore { - pub async fn into_platform_store(self) -> anyhow::Result { - cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - let root_path = statics::RUNTIME_DIRECTORY.join("punlock"); - let store = UnixSecretStore::new(self.bitwarden, root_path).teardown().await?.setup().await?; - Ok(store) - } else if #[cfg(target_os = "macos")] { - // mount_ramdisk_macos(mount_point)?; - panic!("todo"); - } else { - panic!("todo"); - // debug!("On Windows or unsupported OS: using plain dir at {}", mount_point.display()); - } +// impl DecryptedString { +// pub fn from_encrypted(encrypted: &str, credentials: &BitwardenToken) -> anyhow::Result { +// let cipher = Aes256Gcm::new(&credentials.key); +// let blob = base64::engine::general_purpose::STANDARD +// .decode(encrypted) +// .context("failed to Base64‐decode encrypted data")?; +// if blob.len() < 12 + 16 { +// anyhow::bail!("encrypted data too short"); +// } +// let (nonce_bytes, ciphertext_and_tag) = blob.split_at(12); +// let nonce = Nonce::from_slice(nonce_bytes); +// let plaintext = cipher +// .decrypt(nonce, ciphertext_and_tag) +// .map_err(|error| anyhow::anyhow!("decryption failed: {error}"))?; +// let s = String::from_utf8(plaintext).context("decrypted bytes not valid utf-8")?; + +// Ok(Self(s)) +// } +// } + +impl SecretStore { + pub async fn new(bitwarden: impl BitwardenApi + 'static) -> anyhow::Result { + let this = Self { + path: RUNTIME_DIRECTORY.join(env!("CARGO_PKG_NAME")).into(), + bitwarden: Arc::new(Box::new(bitwarden)), } - } -} - -pub struct UnixSecretStore { - bitwarden: Arc>, - root_path: Arc, -} - -impl UnixSecretStore { - pub fn new(bitwarden: Bitwarden, root_path: PathBuf) -> Self { - Self { - bitwarden: Arc::new(bitwarden), - root_path: Arc::new(root_path), - } - } - - pub async fn write_secrets(&self, entries: &[PunlockConfigurationEntry]) -> anyhow::Result<()> { - let mut tasks = FuturesUnordered::new(); - - for entry in entries.iter() { - let root = self.root_path.clone(); - let bw = self.bitwarden.clone(); - - tasks.push( - async move { - let secret = bw.fetch(entry).await.inspect_err(|error| tracing::error!(?error, ?entry, "item not found"))?; - let path = root.join(&entry.path); - - tokio::fs::create_dir_all(path.parent().unwrap_or(&path)).await?; - { - let mut file = tokio::fs::File::create(&path).await?; - file.write_all(secret.as_bytes()).await?; - if !secret.ends_with('\n') { - file.write_all(b"\n").await?; - } - file.flush().await?; - } - - let mut perms = tokio::fs::metadata(&path).await?.permissions(); - perms.set_readonly(true); - - if !entry.public { - #[cfg(unix)] - perms.set_mode(0o400); - } - tokio::fs::set_permissions(&path, perms) - .await - .inspect(|_| tracing::debug!(?path, "set readonly")) - .inspect_err(|error| tracing::error!(?error, ?path, "remove runtime dir"))?; - - if let Some(ref links) = entry.links { - for link in links { - let link_path: PathBuf = if PathBuf::from(link).is_absolute() { - PathBuf::from(link) - } else { - HOME_DIRECTORY.join(link) - }; - tokio::fs::create_dir_all(link_path.parent().unwrap_or(&link_path)) - .await?; - match tokio::fs::symlink_metadata(&link_path).await { - Ok(meta) if meta.file_type().is_symlink() => { - let current = tokio::fs::read_link(&link_path).await?; - if current == path { - tracing::debug!(?link_path, "skipping existing symlink"); - continue; - } - tokio::fs::remove_file(&link_path).await?; - } - Ok(_) => tokio::fs::remove_file(&link_path).await?, - Err(e) if e.kind() == std::io::ErrorKind::NotFound => {} - Err(e) => return Err(e.into()), - } - - let src = path.clone(); - let dst = link_path.clone(); - tokio::task::spawn_blocking(move || -> std::io::Result<()> { - #[cfg(unix)] - std::os::unix::fs::symlink(&src, &dst)?; - #[cfg(windows)] - std::os::windows::fs::symlink_file(&src, &dst)?; - Ok(()) - }) - .await - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))??; - - tracing::info!(src =? path, destination = ?link_path, "created/updated symlink"); - } - } - - Ok::<_, anyhow::Error>((entry.id.clone(), entry.path.clone())) - } - ); - } - - let mut count = 0; - let mut success = 0; - while let Some(res) = tasks.next().await { - count += 1; - match res { - Ok((id, path)) => { - success += 1; - tracing::info!(?id, ?path, "secret written") - } - Err(error) => tracing::error!(?error, "failed to write secret"), - } - } - - tracing::info!("wrote {success}/{count} secrets"); - - Ok(()) + .teardown() + .await? + .setup() + .await?; + Ok(this) } async fn teardown(self) -> anyhow::Result { - if self.root_path.exists() { - tokio::fs::remove_dir_all(&*self.root_path) + if self.path.exists() { + tokio::fs::remove_dir_all(&*self.path) .await .inspect_err( - |error| tracing::error!(?error, path = ?self.root_path, "remove runtime dir"), + |error| tracing::error!(?error, path = ?self.path, "remove runtime dir"), ) .ok(); } @@ -159,11 +73,112 @@ impl UnixSecretStore { } async fn setup(self) -> anyhow::Result { - tokio::fs::create_dir_all(&*self.root_path) - .await - .inspect_err( - |error| tracing::error!(?error, path = ?self.root_path, "create runtime dir"), - )?; + tokio::fs::create_dir_all(&*self.path).await.inspect_err( + |error| tracing::error!(?error, path = ?self.path, "create runtime dir"), + )?; Ok(self) } + + pub async fn write_secrets(&self, entries: &[PunlockConfigurationEntry]) -> anyhow::Result<()> { + // let mut tasks = FuturesUnordered::new(); + // let token = self.bitwarden.get_token(); + + // for entry in entries { + // let root = self.path.clone(); + + // tasks.push(async move { + // let item = self.bitwarden.get_item(&entry.id).await.inspect_err(|error| tracing::error!(id = ?entry.id, ?error, "get item"))?; + // let EncryptedBitwardenItem(cipher) = jmespath::compile(&entry.query) + // .inspect_err(|error| { + // tracing::error!(?error, query = ?entry.query, "input/expression mismatch") + // })? + // .search(&item) + // .inspect_err(|error| tracing::error!(?error, query = ?entry.query, "query failed to apply"))? + // .as_string() + // .map(|s| s.to_owned()) + // .ok_or(anyhow::anyhow!("invalid item"))? + // .into(); + // anyhow::bail!("hi"); + // let DecryptedString(cipher) = DecryptedString::from_encrypted(&cipher, token)?; + // let source = root.join(&entry.path); + // tokio::fs::create_dir_all(source.parent().unwrap_or(&source)).await?; + // { + // let mut file = tokio::fs::File::create(&source).await?; + // file.write_all(cipher.as_bytes()).await?; + // if !cipher.ends_with('\n') { + // file.write_all(b"\n").await?; + // } + // file.flush().await?; + // } + + // let mut perms = tokio::fs::metadata(&source).await?.permissions(); + // perms.set_readonly(true); + // #[cfg(unix)] + // if !entry.public { + // std::os::unix::fs::PermissionsExt::set_mode(&mut perms, 0o400); + // } + // tokio::fs::set_permissions(&source, perms) + // .await + // .inspect(|_| tracing::debug!(?source, "set perms")) + // .inspect_err(|error| tracing::error!(?error, ?source, "set perms"))?; + + // if let Some(links) = &entry.links { + // for link in links { + // let destination: PathBuf = if PathBuf::from(link).is_absolute() { + // PathBuf::from(link) + // } else { + // HOME_DIRECTORY.join(link) + // }; + + // tokio::fs::create_dir_all(destination.parent().unwrap_or(&destination)) + // .await?; + + // match tokio::fs::symlink_metadata(&destination).await { + // Ok(md) if md.file_type().is_symlink() => { + // let cur = tokio::fs::read_link(&destination).await?; + // if cur == source { + // tracing::debug!(?source, ?destination, "skip symlink"); + // continue; + // } + // tokio::fs::remove_file(&destination).await?; + // } + // Ok(_) => tokio::fs::remove_file(&destination).await?, + // Err(e) if e.kind() == std::io::ErrorKind::NotFound => {} + // Err(e) => return Err(e.into()), + // } + + // #[cfg(unix)] + // std::os::unix::fs::symlink(&source, &destination)?; + // #[cfg(windows)] + // if std::fs::metadata(&source)?.is_dir() { + // std::os::windows::fs::symlink_dir(&source, &destination)?; + // } else { + // std::os::windows::fs::symlink_file(&source, &destination)?; + // } + + // tracing::info!(?source, ?destination, "created/updated symlink"); + // } + // } + + // Ok::<_, anyhow::Error>((entry.id.clone(), entry.path.clone())) + // }); + // } + + // tracing::error!(len = tasks.len(), "tasks"); + + // let mut total = 0; + // let mut ok = 0; + // while let Some(r) = tasks.next().await { + // total += 1; + // match r { + // Ok((id, p)) => { + // ok += 1; + // tracing::info!(?id, path = ?p, "write"); + // } + // Err(error) => tracing::error!(?error, "write"), + // } + // } + // tracing::info!(ok, total, "secrets successfully written"); + Ok(()) + } }