From 018c8c63b28fb297b53ac3d43694b9478f9014a5 Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Sat, 23 Dec 2023 22:59:25 +0300 Subject: [PATCH] First source commit --- Cargo.lock | 1120 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 32 ++ src/cli/add_command.rs | 20 + src/cli/clean_command.rs | 15 + src/cli/list_command.rs | 120 ++++ src/cli/mod.rs | 73 +++ src/errors.rs | 41 ++ src/macros.rs | 59 ++ src/main.rs | 32 ++ src/password/cipher.rs | 32 ++ src/password/mod.rs | 129 +++++ src/password/validator.rs | 19 + src/traits.rs | 6 + src/utils.rs | 22 + 14 files changed, 1720 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/cli/add_command.rs create mode 100644 src/cli/clean_command.rs create mode 100644 src/cli/list_command.rs create mode 100644 src/cli/mod.rs create mode 100644 src/errors.rs create mode 100644 src/macros.rs create mode 100644 src/main.rs create mode 100644 src/password/cipher.rs create mode 100644 src/password/mod.rs create mode 100644 src/password/validator.rs create mode 100644 src/traits.rs create mode 100644 src/utils.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..85ec174 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1120 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-trait" +version = "0.1.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[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 = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "comfy-table" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c64043d6c7b7a4c58e39e7efccfdea7b93d885a795d0c054a69dbbf4dd52686" +dependencies = [ + "crossterm 0.27.0", + "strum", + "strum_macros", + "unicode-width", +] + +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + +[[package]] +name = "crossterm" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.4.1", + "crossterm_winapi", + "libc", + "parking_lot", + "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 = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "env_logger" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[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 = "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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "passrs" +version = "0.1.0" +dependencies = [ + "base64", + "clap", + "comfy-table", + "directories", + "log", + "passwords", + "pretty_env_logger", + "regex", + "scanpw", + "serde", + "serde_json", + "sha256", + "soft-aes", + "thiserror", + "url", +] + +[[package]] +name = "passwords" +version = "3.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11407193a7c2bd14ec6b0ec3394da6fdcf7a4d5dcbc8c3cc38dfb17802c8d59c" +dependencies = [ + "random-pick", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "pretty_env_logger" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" +dependencies = [ + "env_logger", + "log", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "random-number" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3da5cbb4c27c5150c03a54a7e4745437cd90f9e329ae657c0b889a144bb7be" +dependencies = [ + "proc-macro-hack", + "rand", + "random-number-macro-impl", +] + +[[package]] +name = "random-number-macro-impl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b86292cf41ccfc96c5de7165c1c53d5b4ac540c5bab9d1857acbe9eba5f1a0b" +dependencies = [ + "proc-macro-hack", + "quote", + "syn", +] + +[[package]] +name = "random-pick" +version = "1.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c179499072da789afe44127d5f4aa6012de2c2f96ef759990196b37387a2a0f8" +dependencies = [ + "random-number", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "scanpw" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6196197a27d908a0b1cb44606d5a4f358c73d04dd1e9ba0ddc034841fa043a03" +dependencies = [ + "crossterm 0.26.1", + "nix", + "thiserror", + "windows", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha256" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7895c8ae88588ccead14ff438b939b0c569cd619116f14b4d13fdff7b8333386" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2", +] + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + +[[package]] +name = "soft-aes" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d33909ace50facaccea7c996cf623416b8426f249ca446db3d6a3f560ad84cc" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[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" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9701d26 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "passrs" +version = "0.1.0" +edition = "2021" +license = "GPL-3.0-only" +authors = ["Awiteb "] +readme = "README.md" +description = "Local CLI password manager" +repository = "https://github.com/TheAwiteb/passrs" +rust-version = "1.70.0" +keywords = ["password", "manager", "CLI"] +categories = ["command-line-utilities"] + +[dependencies] +base64 = "0.21.5" +clap = { version = "4.4.11", features = ["derive"] } +comfy-table = "7.1.0" +directories = "5.0.1" +log = "0.4.20" +passwords = { version = "3.1.16", features = ["common-password"] } +pretty_env_logger = "0.5.0" +regex = "1.10.2" +scanpw = "1.0.0" +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" +sha256 = { version = "1.4.0", default-features = false } +soft-aes = "0.1.0" +thiserror = "1.0.51" +url = { version = "2.5.0", features = ["serde"] } + +[profile.release] +strip = true # Automatically strip symbols from the binary. diff --git a/src/cli/add_command.rs b/src/cli/add_command.rs new file mode 100644 index 0000000..12185d7 --- /dev/null +++ b/src/cli/add_command.rs @@ -0,0 +1,20 @@ +use clap::Args; + +use crate::{ + password::{Password, Passwords}, + PassrsResult, RunCommand, +}; + +#[derive(Debug, Args)] +#[command(author, version, about, long_about = None)] +pub struct Add { + #[command(flatten)] + password_info: Password, +} + +impl RunCommand for Add { + fn run(&self, mut password_manager: Passwords) -> PassrsResult<()> { + password_manager.add_password(self.password_info.clone()); + password_manager.try_export() + } +} diff --git a/src/cli/clean_command.rs b/src/cli/clean_command.rs new file mode 100644 index 0000000..8d41730 --- /dev/null +++ b/src/cli/clean_command.rs @@ -0,0 +1,15 @@ +use std::fs; + +use clap::Args; + +use crate::{password::Passwords, PassrsError, PassrsResult, RunCommand}; + +#[derive(Debug, Args)] +#[command(author, version, about, long_about = None)] +pub struct Clean {} + +impl RunCommand for Clean { + fn run(&self, password_manager: Passwords) -> PassrsResult<()> { + fs::write(password_manager.passwords_file, "[]").map_err(PassrsError::Io) + } +} diff --git a/src/cli/list_command.rs b/src/cli/list_command.rs new file mode 100644 index 0000000..4b70e97 --- /dev/null +++ b/src/cli/list_command.rs @@ -0,0 +1,120 @@ +use clap::Args; +use comfy_table::Table; +use regex::Regex; + +use crate::{password::Passwords, PassrsError, PassrsResult, RunCommand}; + +#[derive(Debug, Args)] +#[command(author, version, about, long_about = None)] +pub struct List { + /// Show the clean password + #[arg(short = 'p', long)] + unhide_password: bool, + /// Show the service of the password and search in it if you search + #[arg(short = 's', long)] + with_service: bool, + /// Show the note of the password and search in it if you search + #[arg(short = 'n', long)] + with_note: bool, + + /// Return the password with spesifc index + #[arg(short, long, value_name = "INDEX")] + get: Option, + /// Search and display only matching passwords. + /// + /// The name and username will be searched. And service and note if included + #[arg(short = 'e', long, value_name = "TEXT")] + search: Option, + /// Enable regex in the search + #[arg(short, long)] + regex: bool, +} + +impl RunCommand for List { + fn run(&self, password_manager: Passwords) -> PassrsResult<()> { + if password_manager.passwords.is_empty() { + println!("Looks like there is no passwords to list") + } else { + if self.get.is_some() && self.search.is_some() { + return Err(PassrsError::ArgsConflict( + "You cannot use `--get` arg with `--search` arg".to_owned(), + )); + } + if self.regex && self.search.is_none() { + return Err(PassrsError::ArgsConflict( + "You cannot use `--regex` without `--search` arg".to_owned(), + )); + } + + let mut table = Table::new(); + let mut header = vec!["Index", "Name", "Username", "Password"]; + if self.with_service { + header.push("Service"); + } + if self.with_note { + header.push("Note"); + } + let re = Regex::new(self.search.as_deref().unwrap_or("."))?; + + table.set_header(header); + let passwords = password_manager + .passwords + .iter() + .enumerate() + .filter(|(idx, pass)| { + if let Some(index) = self.get { + return (idx + 1) == index; + } + if let Some(ref pattern) = self.search { + if self.regex { + return re.is_match(&pass.name) + || re.is_match(&pass.username) + || (self.with_service + && pass.service.as_ref().is_some_and(|s| re.is_match(s))) + || (self.with_note + && pass.note.as_ref().is_some_and(|n| re.is_match(n))); + } else { + let pattern = pattern.to_lowercase(); + return pass.name.to_lowercase().contains(&pattern) + || pass.username.to_lowercase().contains(&pattern) + || (self.with_service + && pass + .service + .as_ref() + .is_some_and(|s| s.to_lowercase().contains(&pattern))) + || (self.with_note + && pass + .note + .as_ref() + .is_some_and(|n| n.to_lowercase().contains(&pattern))); + } + } + + true + }); + for (idx, password) in passwords { + let hide_password = "*".repeat(password.password.chars().count()); + let idx = (idx + 1).to_string(); + let mut row = vec![ + idx.as_str(), + password.name.as_str(), + password.username.as_str(), + if self.unhide_password { + password.password.as_str() + } else { + hide_password.as_str() + }, + ]; + if self.with_service { + row.push(password.service.as_deref().unwrap_or("Not Set")) + } + if self.with_note { + row.push(password.note.as_deref().unwrap_or("Not Set")) + } + table.add_row(row); + } + println!("{table}"); + } + Ok(()) + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs new file mode 100644 index 0000000..32f8a09 --- /dev/null +++ b/src/cli/mod.rs @@ -0,0 +1,73 @@ +use std::path::PathBuf; + +use clap::Parser; + +use crate::{ + password::{self, Passwords}, + PassrsError, PassrsResult, RunCommand, +}; + +pub mod add_command; +pub mod clean_command; +pub mod list_command; + +crate::create_commands!( + enum Commands + "Add new password", Add => add_command::Add + "List your password and search", List => list_command::List + "Clean the password file", Clean => clean_command::Clean + // TODO: Edit command + // TODO: Delete command + // TODO: Export command + // TODO: Import command +); + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct Cli { + /// The passwords json file, default: $HOME/.local/share/passrs/passwords.json + #[arg(short, long)] + passwords_file: Option, + + // TODO: verbose flag + #[command(subcommand)] + command: Commands, +} + +impl Cli { + /// Run the cli + pub fn run(self) -> PassrsResult<()> { + let passwords_file = if let Some(ref path) = self.passwords_file { + path.clone() + } else { + crate::utils::passwords_file()? + }; + log::debug!( + "Getting password file: {}", + passwords_file.to_string_lossy() + ); + let password = scanpw::scanpw!("Master Password: "); + + if password::is_new_password_file(&passwords_file)? { + let analyzed = passwords::analyzer::analyze(&password); + if analyzed.length() < 15 { + return Err(PassrsError::WeakPassword( + "The password length must be beggier then 15".to_owned(), + )); + } else if passwords::scorer::score(&analyzed) < 80.0 { + return Err(PassrsError::WeakPassword( + "Your password is not stronge enough".to_owned(), + )); + } + } + + let master_password = sha256::digest(password); + let password_manager = Passwords::try_reload( + passwords_file, + master_password.into_bytes().into_iter().take(32).collect(), + )?; + self.command.run(password_manager)?; + + Ok(()) + } +} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..608aedf --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,41 @@ +use std::{process::ExitCode, string::FromUtf8Error}; + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Invalid Json Path Error: {0}")] + InvalidJsonPath(String), + #[error("Encryption Error: {0}")] + Encryption(String), + #[error("Decryption Error: {0}")] + Decryption(String), + #[error( + "Wrong Master Password Error: Wrong password or you may have played with the password file" + )] + WrongMasterPassword, + #[error("Weak Password Error: {0}")] + WeakPassword(String), + #[error("Args Conflict Error: {0}")] + ArgsConflict(String), + + #[error("Invalid Regex: {0}")] + InvalidRegex(#[from] regex::Error), + #[error("UTF8 Error: {0}")] + Utf8(#[from] FromUtf8Error), + #[error("Base64 Decode Error: {0}")] + BaseDecodeError(#[from] base64::DecodeError), + #[error("Json Error: {0}")] + Json(#[from] serde_json::Error), + #[error("Project Folder Error: {0}")] + ProjectDir(String), + #[error("IO Error: {0}")] + Io(#[from] std::io::Error), +} + +impl Error { + /// Return the exit code of the error + pub fn exit_code(&self) -> ExitCode { + ExitCode::FAILURE + } +} diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..a96e572 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,59 @@ +/// Creates commands macro, to create the `Commands` enum and impl `RunCommand` to it. +/// +/// ### Notes: +/// The `$command` must impl `RunCommand` trait +/// +/// ### Example: +/// ```rust +/// create_commands!( +/// enum TestCommands +/// "Test command", Test => TestArgs +/// "Do something", Some => SomeArgs +/// ); +/// ``` +/// #### Output +/// ```rust +/// ///The passrs commands +/// pub enum TestCommands { +/// ///Test command +/// Test(TestArgs), +/// ///Do something +/// Some(SomeArgs), +/// } +/// +/// impl crate::RunCommand for TestCommands { +/// fn run( +/// &self, +/// password_manager: crate::password::Passwords, +/// ) -> crate::PassrsResult<()> { +/// match self { +/// Self::Test(command) => command.run(password_manager), +/// Self::Some(command) => command.run(password_manager), +/// } +/// } +/// } +/// ``` +#[macro_export] +macro_rules! create_commands { + (enum $enum_name: ident $($doc:tt, $varint: ident => $command: ty)+) => { + #[doc = "The passrs commands"] + #[derive(Debug, clap::Subcommand)] + pub enum $enum_name { + $( + #[doc = $doc] + $varint($command), + )+ + } + + #[automatically_derived] + impl $crate::RunCommand for $enum_name{ + fn run(&self, password_manager: $crate::password::Passwords) -> $crate::PassrsResult<()> { + match self { + $( + Self::$varint(command) => command.run(password_manager), + )+ + } + } + } + }; +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..466caf5 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,32 @@ +use std::process::ExitCode; + +use base64::{ + alphabet, + engine::{general_purpose::PAD, GeneralPurpose}, +}; +use clap::Parser; + +pub mod cli; +pub mod errors; +pub mod password; +pub mod utils; + +mod macros; +mod traits; + +pub use {macros::*, traits::*}; + +pub use errors::{Error as PassrsError, Result as PassrsResult}; + +pub const STANDARDBASE: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, PAD); + +fn main() -> ExitCode { + pretty_env_logger::init(); + + if let Err(err) = cli::Cli::parse().run() { + eprintln!("{err}"); + err.exit_code() + } else { + ExitCode::SUCCESS + } +} diff --git a/src/password/cipher.rs b/src/password/cipher.rs new file mode 100644 index 0000000..cc69b6b --- /dev/null +++ b/src/password/cipher.rs @@ -0,0 +1,32 @@ +use base64::Engine; +use soft_aes::aes::{aes_dec_ecb, aes_enc_ecb}; + +use crate::{PassrsError, PassrsResult}; + +/// Encrypt the string with AEC ECB +pub fn encrypt(master_password: &[u8], data: &str) -> PassrsResult { + let padding = Some("PKCS7"); + + aes_enc_ecb(data.as_bytes(), master_password, padding) + .map(|d| crate::STANDARDBASE.encode(d)) + .map_err(|err| PassrsError::Encryption(err.to_string())) +} + +/// Decrypt the string with AEC ECB +pub fn decrypt(master_password: &[u8], data: &str) -> PassrsResult { + let padding = Some("PKCS7"); + + aes_dec_ecb( + crate::STANDARDBASE.decode(data)?.as_slice(), + master_password, + padding, + ) + .map_err(|err| { + if err.to_string().contains("Invalid padding") { + PassrsError::WrongMasterPassword + } else { + PassrsError::Decryption(err.to_string()) + } + }) + .map(|d| String::from_utf8(d).map_err(PassrsError::Utf8))? +} diff --git a/src/password/mod.rs b/src/password/mod.rs new file mode 100644 index 0000000..cc30b85 --- /dev/null +++ b/src/password/mod.rs @@ -0,0 +1,129 @@ +use std::{fs, path::PathBuf}; + +use clap::Parser; +use serde::{Deserialize, Serialize}; + +use crate::{PassrsError, PassrsResult}; + +pub mod cipher; +mod validator; + +pub use validator::*; + +/// The passwords manager +#[derive(Deserialize, Serialize)] +pub struct Passwords { + /// Hash of the master password + #[serde(skip)] + pub master_password: Vec, + /// The json passwords file + #[serde(skip)] + pub passwords_file: PathBuf, + /// The passwords + pub passwords: Vec, +} + +/// The password struct +#[derive(Clone, Debug, Deserialize, Serialize, Parser)] +pub struct Password { + /// The name of the password + #[arg(short, long)] + pub name: String, + /// The username + #[arg(short, long)] + pub username: String, + /// The password + #[arg(short, long)] + pub password: String, + /// The service name. e.g the website url + #[arg(short, long)] + pub service: Option, + /// The note of the password + #[arg(short = 'o', long)] + pub note: Option, +} + +impl Password { + /// Encrypt the password data + pub fn encrypt(self, master_password: &[u8]) -> PassrsResult { + Ok(Self { + name: cipher::encrypt(master_password, &self.name)?, + username: cipher::encrypt(master_password, &self.username)?, + password: cipher::encrypt(master_password, &self.password)?, + service: self + .service + .map(|url| cipher::encrypt(master_password, &url)) + .transpose()?, + note: self + .note + .map(|note| cipher::encrypt(master_password, ¬e)) + .transpose()?, + }) + } + + /// Decrypt the password data + pub fn decrypt(self, master_password: &[u8]) -> PassrsResult { + Ok(Self { + name: cipher::decrypt(master_password, &self.name)?, + username: cipher::decrypt(master_password, &self.username)?, + password: cipher::decrypt(master_password, &self.password)?, + service: self + .service + .map(|url| cipher::decrypt(master_password, &url)) + .transpose()?, + note: self + .note + .map(|note| cipher::decrypt(master_password, ¬e)) + .transpose()?, + }) + } +} + +impl Passwords { + /// Create new Passwords instnce + pub fn new( + master_password: Vec, + passwords_file: PathBuf, + passwords: Vec, + ) -> Self { + Self { + master_password, + passwords_file, + passwords, + } + } + + /// Encrypt the passwords + fn encrypt(self) -> PassrsResult { + Ok(Self { + passwords: self + .passwords + .into_iter() + .map(|p| p.encrypt(&self.master_password)) + .collect::>>()?, + ..self + }) + } + + /// Reload the passwords from the file + pub fn try_reload(passwords_file: PathBuf, master_password: Vec) -> PassrsResult { + let passwords = + serde_json::from_str::>(&fs::read_to_string(&passwords_file)?)? + .into_iter() + .map(|p| p.decrypt(master_password.as_slice())) + .collect::>>()?; + + Ok(Self::new(master_password, passwords_file, passwords)) + } + + /// Export the passwords to the file + pub fn try_export(self) -> PassrsResult<()> { + let path = self.passwords_file.to_path_buf(); + fs::write(path, serde_json::to_string(&self.encrypt()?.passwords)?).map_err(PassrsError::Io) + } + + /// Add new password + pub fn add_password(&mut self, password: Password) { + self.passwords.push(password) + } +} diff --git a/src/password/validator.rs b/src/password/validator.rs new file mode 100644 index 0000000..c7abde4 --- /dev/null +++ b/src/password/validator.rs @@ -0,0 +1,19 @@ +use std::{fs, path::Path}; + +use crate::PassrsResult; + +use super::Password; + +/// Return if the password file new file or not +pub fn is_new_password_file(path: &Path) -> PassrsResult { + if path.exists() { + let file_content = fs::read_to_string(path)?; + if !file_content.is_empty() + && file_content.trim() != "[]" + && serde_json::from_str::>(&file_content).is_ok() + { + return Ok(false); + } + } + Ok(true) +} diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 0000000..cb8bf7d --- /dev/null +++ b/src/traits.rs @@ -0,0 +1,6 @@ +use crate::{password::Passwords, PassrsResult}; + +/// Trait to run the command +pub trait RunCommand { + fn run(&self, password_manager: Passwords) -> PassrsResult<()>; +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..392b8f3 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,22 @@ +use std::{fs, path::PathBuf}; + +use crate::{PassrsError, PassrsResult}; + +/// Return the default passwords json file +pub fn passwords_file() -> PassrsResult { + if let Some(path) = directories::ProjectDirs::from("", "", "passrs") + .map(|d| d.data_local_dir().to_path_buf().join("passwords.json")) + { + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent)?; + } + if !path.exists() { + fs::write(&path, "[]")?; + } + Ok(path) + } else { + Err(PassrsError::ProjectDir( + "Can't extract the project_dir from this OS".to_owned(), + )) + } +}