From d0a2532c27950757aef51dbb18125a18e73ee4f8 Mon Sep 17 00:00:00 2001 From: Joseph Montanaro Date: Sun, 16 Jun 2024 07:08:10 -0400 Subject: [PATCH] start working on generalizing credential logic --- .gitignore | 4 - src-tauri/Cargo.lock | 590 +++++++++++++++++- src-tauri/Cargo.toml | 3 + src-tauri/migrations/20240612192956_kv.sql | 11 + .../src/{credentials.rs => _credentials.rs} | 0 src-tauri/src/app.rs | 2 +- src-tauri/src/bin/agent.rs | 7 + src-tauri/src/bin/key.rs | 13 + src-tauri/src/config.rs | 28 +- src-tauri/src/credentials/aws.rs | 174 ++++++ src-tauri/src/credentials/mod.rs | 193 ++++++ src-tauri/src/errors.rs | 36 +- src-tauri/src/kv.rs | 103 +++ src-tauri/src/lib.rs | 3 +- src-tauri/src/server/mod.rs | 2 + src-tauri/src/server/ssh_agent.rs | 77 +++ 16 files changed, 1192 insertions(+), 54 deletions(-) create mode 100644 src-tauri/migrations/20240612192956_kv.sql rename src-tauri/src/{credentials.rs => _credentials.rs} (100%) create mode 100644 src-tauri/src/bin/agent.rs create mode 100644 src-tauri/src/bin/key.rs create mode 100644 src-tauri/src/credentials/aws.rs create mode 100644 src-tauri/src/credentials/mod.rs create mode 100644 src-tauri/src/kv.rs create mode 100644 src-tauri/src/server/ssh_agent.rs diff --git a/.gitignore b/.gitignore index 8e46064..9d339f6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,3 @@ src-tauri/target/ # .env is system-specific .env .vscode - -# just in case -credentials* -!credentials.rs diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 28e75eb..29da4a3 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -27,6 +27,31 @@ dependencies = [ "generic-array", ] +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.7.6" @@ -296,9 +321,9 @@ checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", @@ -653,6 +678,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.21.4" @@ -680,6 +711,17 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bcrypt-pbkdf" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeac2e1fe888769f34f05ac343bbef98b14d1ffb292ab69d4608b3abc86f2a2" +dependencies = [ + "blowfish", + "pbkdf2", + "sha2", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -719,6 +761,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "block2" version = "0.5.1" @@ -743,6 +794,16 @@ dependencies = [ "log", ] +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "brotli" version = "3.3.4" @@ -868,6 +929,15 @@ dependencies = [ "toml 0.8.2", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.0.83" @@ -1053,6 +1123,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "convert_case" version = "0.4.0" @@ -1150,8 +1226,11 @@ dependencies = [ "rfd", "serde", "serde_json", + "signature 2.2.0", "sodiumoxide", "sqlx", + "ssh-agent-lib", + "ssh-key", "strum", "strum_macros", "sysinfo", @@ -1219,6 +1298,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -1267,6 +1358,42 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "platforms", + "rustc_version", + "subtle", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "darling" version = "0.20.3" @@ -1302,6 +1429,16 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.11" @@ -1343,6 +1480,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -1489,13 +1627,48 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature 2.2.0", + "spki", +] + [[package]] name = "ed25519" version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ - "signature", + "signature 1.6.4", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "signature 2.2.0", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" +dependencies = [ + "curve25519-dalek", + "ed25519 2.2.3", + "sha2", + "subtle", ] [[package]] @@ -1504,6 +1677,25 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "embed-resource" version = "2.3.0" @@ -1649,6 +1841,22 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" + [[package]] name = "field-offset" version = "0.3.6" @@ -1755,10 +1963,25 @@ dependencies = [ ] [[package]] -name = "futures-channel" -version = "0.3.28" +name = "futures" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -1766,15 +1989,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -1794,9 +2017,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" @@ -1830,9 +2053,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", @@ -1841,22 +2064,23 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1997,6 +2221,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -2021,6 +2246,16 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.28.0" @@ -2141,6 +2376,17 @@ dependencies = [ "system-deps", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "gtk" version = "0.18.1" @@ -2538,6 +2784,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ + "block-padding", "generic-array", ] @@ -2682,6 +2929,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] [[package]] name = "libappindicator" @@ -2723,6 +2973,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "libsodium-sys" version = "0.2.7" @@ -2996,6 +3252,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -3006,6 +3279,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.16" @@ -3013,6 +3297,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -3245,6 +3530,44 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p521" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primeorder", + "rand_core 0.6.4", + "sha2", +] + [[package]] name = "pango" version = "0.18.3" @@ -3341,6 +3664,24 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.0" @@ -3513,12 +3854,39 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "platforms" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" + [[package]] name = "plist" version = "1.5.0" @@ -3577,6 +3945,18 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -3595,6 +3975,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -3753,6 +4142,15 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "raunch" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a67226789ccd419c55f5c826e04f7d58690b0593364f61bc8ed6e7dbbab49c5" +dependencies = [ + "libc", +] + [[package]] name = "raw-window-handle" version = "0.5.2" @@ -3906,6 +4304,16 @@ dependencies = [ "winreg 0.52.0", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rfd" version = "0.14.1" @@ -3944,6 +4352,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "sha2", + "signature 2.2.0", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -4089,6 +4518,20 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.9.2" @@ -4266,6 +4709,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "service-binding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5580e177917150862f941023eb13ff219acc59daefa42b76fa747612723402c" +dependencies = [ + "raunch", +] + [[package]] name = "servo_arc" version = "0.1.1" @@ -4289,9 +4741,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -4322,6 +4774,16 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "simd-abstraction" version = "0.7.1" @@ -4384,7 +4846,7 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e26be3acb6c2d9a7aac28482586a7856436af4cfe7100031d219de2d2ecb0028" dependencies = [ - "ed25519", + "ed25519 1.5.3", "libc", "libsodium-sys", "serde", @@ -4455,6 +4917,16 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "sqlformat" version = "0.2.2" @@ -4552,6 +5024,74 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "ssh-agent-lib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae60055310a897706ffeffdd82cc8d5bb6ae8a50481157fb2407f579d404df1" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "futures", + "log", + "serde", + "service-binding", + "tokio", + "tokio-util", +] + +[[package]] +name = "ssh-cipher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f" +dependencies = [ + "aes", + "aes-gcm", + "cbc", + "chacha20", + "cipher", + "ctr", + "poly1305", + "ssh-encoding", + "subtle", +] + +[[package]] +name = "ssh-encoding" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15" +dependencies = [ + "base64ct", + "pem-rfc7468", + "sha2", +] + +[[package]] +name = "ssh-key" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca9b366a80cf18bb6406f4cf4d10aebfb46140a8c0c33f666a144c5c76ecbafc" +dependencies = [ + "bcrypt-pbkdf", + "ed25519-dalek", + "num-bigint-dig", + "p256", + "p384", + "p521", + "rand_core 0.6.4", + "rsa", + "sec1", + "sha2", + "signature 2.2.0", + "ssh-cipher", + "ssh-encoding", + "subtle", + "zeroize", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -6338,9 +6878,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.6.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] name = "zvariant" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 47468ec..dbb29a1 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -50,6 +50,9 @@ time = "0.3.31" tauri-plugin-single-instance = "2.0.0-beta.9" tauri-plugin-global-shortcut = "2.0.0-beta.6" rfd = "0.14.1" +ssh-agent-lib = "0.4.0" +ssh-key = { version = "0.6.6", features = ["rsa", "ed25519", "encryption"] } +signature = "2.2.0" [features] # by default Tauri runs in production mode diff --git a/src-tauri/migrations/20240612192956_kv.sql b/src-tauri/migrations/20240612192956_kv.sql new file mode 100644 index 0000000..c12eac6 --- /dev/null +++ b/src-tauri/migrations/20240612192956_kv.sql @@ -0,0 +1,11 @@ +-- key-value store, will be used for various one-off values, serialized to bytes +CREATE TABLE kv ( + name TEXT PRIMARY KEY, + value BLOB +); + +-- config is currently stored in its own table, as text +INSERT INTO kv (name, value) +SELECT 'config', CAST(data AS BLOB) FROM config; + +DROP TABLE config; diff --git a/src-tauri/src/credentials.rs b/src-tauri/src/_credentials.rs similarity index 100% rename from src-tauri/src/credentials.rs rename to src-tauri/src/_credentials.rs diff --git a/src-tauri/src/app.rs b/src-tauri/src/app.rs index 78c6e82..2b38e25 100644 --- a/src-tauri/src/app.rs +++ b/src-tauri/src/app.rs @@ -100,7 +100,7 @@ async fn setup(app: &mut App) -> Result<(), Box> { let mut conf = match AppConfig::load(&pool).await { Ok(c) => c, - Err(SetupError::ConfigParseError(_)) => { + Err(LoadKvError::Invalid(_)) => { setup_errors.push( "Could not load configuration from database. Reverting to defaults.".into() ); diff --git a/src-tauri/src/bin/agent.rs b/src-tauri/src/bin/agent.rs new file mode 100644 index 0000000..45f6436 --- /dev/null +++ b/src-tauri/src/bin/agent.rs @@ -0,0 +1,7 @@ +use creddy::server::ssh_agent; + + +#[tokio::main] +async fn main() { + ssh_agent::run().await; +} diff --git a/src-tauri/src/bin/key.rs b/src-tauri/src/bin/key.rs new file mode 100644 index 0000000..44bed00 --- /dev/null +++ b/src-tauri/src/bin/key.rs @@ -0,0 +1,13 @@ +use ssh_key::private::PrivateKey; + + +fn main() { + // let passphrase = std::env::var("PRIVKEY_PASSPHRASE").unwrap(); + let p = AsRef::::as_ref("/home/joe/.ssh/test"); + let privkey = PrivateKey::read_openssh_file(p) + .unwrap(); + // .decrypt(passphrase.as_bytes()) + // .unwrap(); + + dbg!(String::from_utf8_lossy(&privkey.to_bytes().unwrap())); +} diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index ea6dcfc..596cc8c 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -7,6 +7,7 @@ use serde::{Serialize, Deserialize}; use sqlx::SqlitePool; use crate::errors::*; +use crate::kv; #[derive(Clone, Debug, Serialize, Deserialize)] @@ -77,31 +78,16 @@ impl Default for AppConfig { impl AppConfig { - pub async fn load(pool: &SqlitePool) -> Result { - let res = sqlx::query!("SELECT * from config where name = 'main'") - .fetch_optional(pool) - .await?; + pub async fn load(pool: &SqlitePool) -> Result { + let config = kv::load(pool, "config") + .await? + .unwrap_or_else(|| AppConfig::default()); - let row = match res { - Some(row) => row, - None => return Ok(AppConfig::default()), - }; - - Ok(serde_json::from_str(&row.data)?) + Ok(config) } pub async fn save(&self, pool: &SqlitePool) -> Result<(), sqlx::error::Error> { - let data = serde_json::to_string(self).unwrap(); - sqlx::query( - "INSERT INTO config (name, data) VALUES ('main', ?) - ON CONFLICT (name) DO UPDATE SET data = ?" - ) - .bind(&data) - .bind(&data) - .execute(pool) - .await?; - - Ok(()) + kv::save(pool, "config", self).await } } diff --git a/src-tauri/src/credentials/aws.rs b/src-tauri/src/credentials/aws.rs new file mode 100644 index 0000000..2c19ae7 --- /dev/null +++ b/src-tauri/src/credentials/aws.rs @@ -0,0 +1,174 @@ +use serde::{ + Serialize, + Deserialize, + Serializer, + Deserializer, +}; +use sqlx::SqlitePool; + +use super::{Crypto, PersistentCredential}; + +use crate::errors::*; + + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct AwsBaseCredential { + #[serde(default = "default_credentials_version")] + pub version: usize, + pub access_key_id: String, + pub secret_access_key: String, +} + +impl AwsBaseCredential { + pub fn new(access_key_id: String, secret_access_key: String) -> Self { + Self {version: 1, access_key_id, secret_access_key} + } +} + +impl PersistentCredential for AwsBaseCredential { + pub async fn save(&self, crypt: &Crypto, pool: &SqlitePool) -> Result<(), SaveCredentialsError> { + let (nonce, ciphertext) = crypto.encrypt(self.secret_access_key.as_bytes())?; + sqlx::query!( + "INSERT INTO aws_credentials ( + name, + key_id, + secret_key_enc, + nonce, + updated_at + ) + VALUES ('main', ?, ?, ? strftime('%s')) + ON CONFLICT DO UPDATE SET + key_id = excluded.key_id, + secret_key_enc = excluded.secret_key_enc, + nonce = excluded.nonce + updated_at = excluded.updated_at", + self.access_key_id, + ciphertext, + nonce, + ).execute(pool).await?; + + Ok(()) + } + + pub async fn load(crypt: &Crypto, pool: &SqlitePool) -> Result { + let row = sqlx::query!("SELECT * FROM aws_credentials WHERE name = 'main'") + .fetch_optional(pool) + .await? + .ok_or(LoadCredentialsError::NoCredentials); + + let secret_key = crypto.decrypt(&row.nonce, &row.secret_key_enc)?; + let creds = Self { + version: 1, + access_key_id: row.key_id, + secret_access_key: secret_key, + }; + Ok(creds) + } +} + + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct AwsSessionCredential { + #[serde(default = "default_credentials_version")] + pub version: usize, + pub access_key_id: String, + pub secret_access_key: String, + pub session_token: String, + #[serde(serialize_with = "serialize_expiration")] + #[serde(deserialize_with = "deserialize_expiration")] + pub expiration: DateTime, +} + +impl AwsSessionCredential { + pub async fn from_base(base: &BaseCredentials) -> Result { + let req_creds = aws_sdk_sts::Credentials::new( + &base.access_key_id, + &base.secret_access_key, + None, // token + None, //expiration + "Creddy", // "provider name" apparently + ); + let config = aws_config::from_env() + .credentials_provider(req_creds) + .load() + .await; + + let client = aws_sdk_sts::Client::new(&config); + let resp = client.get_session_token() + .duration_seconds(43_200) + .send() + .await?; + + let aws_session = resp.credentials().ok_or(GetSessionError::EmptyResponse)?; + + let access_key_id = aws_session.access_key_id() + .ok_or(GetSessionError::EmptyResponse)? + .to_string(); + let secret_access_key = aws_session.secret_access_key() + .ok_or(GetSessionError::EmptyResponse)? + .to_string(); + let session_token = aws_session.session_token() + .ok_or(GetSessionError::EmptyResponse)? + .to_string(); + let expiration = aws_session.expiration() + .ok_or(GetSessionError::EmptyResponse)? + .clone(); + + let session_creds = SessionCredentials { + version: 1, + access_key_id, + secret_access_key, + session_token, + expiration, + }; + + #[cfg(debug_assertions)] + println!("Got new session:\n{}", serde_json::to_string(&session_creds).unwrap()); + + Ok(session_creds) + } + + pub fn is_expired(&self) -> bool { + let current_ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() // doesn't panic because UNIX_EPOCH won't be later than now() + .as_secs(); + + let expire_ts = self.expiration.secs(); + let remaining = expire_ts - (current_ts as i64); + remaining < 60 + } +} + + +struct DateTimeVisitor; + +impl<'de> Visitor<'de> for DateTimeVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + write!(formatter, "an RFC 3339 UTC string, e.g. \"2014-01-05T10:17:34Z\"") + } + + fn visit_str(self, v: &str) -> Result { + DateTime::from_str(v, Format::DateTime) + .map_err(|_| E::custom(format!("Invalid date/time: {v}"))) + } +} + + +fn deserialize_expiration<'de, D>(deserializer: D) -> Result +where D: Deserializer<'de> +{ + deserializer.deserialize_str(DateTimeVisitor) +} + +fn serialize_expiration(exp: &DateTime, serializer: S) -> Result +where S: Serializer +{ + // this only fails if the d/t is out of range, which it can't be for this format + let time_str = exp.fmt(Format::DateTime).unwrap(); + serializer.serialize_str(&time_str) +} diff --git a/src-tauri/src/credentials/mod.rs b/src-tauri/src/credentials/mod.rs new file mode 100644 index 0000000..ac9cd78 --- /dev/null +++ b/src-tauri/src/credentials/mod.rs @@ -0,0 +1,193 @@ +use argon2::{ + Argon2, + Algorithm, + Version, + ParamsBuilder, + password_hash::rand_core::{RngCore, OsRng}, +}; +use chacha20poly1305::{ + XChaCha20Poly1305, + XNonce, + aead::{ + Aead, + AeadCore, + KeyInit, + Error as AeadError, + generic_array::GenericArray, + }, +}; +use serde::{Serialize, Deserialize}; +use sqlx::{FromRow, SqlitePool}; + +use crate::kv; + +mod aws; +pub use aws::{AwsBaseCredential, AwsSessionCredential}; + + +pub enum CredentialKind { + AwsBase, + AwsSession, +} + + +pub trait PersistentCredential { + async fn load(crypt: &Crypto, pool: &SqlitePool) -> Result; + async fn save(&self, crypt: &Crypto, pool: &SqlitePool) -> Result<(), SaveCredentialsError>; +} + + +#[derive(Debug, Clone)] +pub enum AppSession { + Unlocked { + salt: [u8; 32], + crypto: Crypto, + }, + Locked { + salt: [u8; 32], + verify_nonce: XNonce, + verify_blob: Vec + }, + Empty, +} + +impl AppSession { + pub fn new(passphrase: &str) -> Result { + let salt = Crypto::salt(); + let crypto = Crypto::new(passphrase, &salt); + Ok(Self::Unlocked {salt, crypto}) + } + + pub fn unlock(self, passphrase: &str) -> Result { + let (salt, nonce, blob) = match self { + Self::Empty => return Err(UnlockError::NoCredentials), + Self::Unlocked => return Err(UnlockError::NotLocked), + Self::Locked {salt, verify_nonce, verify_blob} => (salt, verify_nonce, verify_blob), + }; + + let crypto = Crypto::new(passphrase, salt) + .map_err(|e| CryptoError::Argon2(e))?; + + // if passphrase is incorrect, this will fail + let verify = crypto.decrypt(&nonce, &blob)?; + + Ok(Self::Unlocked{crypto, salt}) + } + + pub async fn load(pool: &SqlitePool) -> Result { + match kv::load_bytes_multi!(pool, "salt", "verify_nonce", "verify_blob").await? { + Some((salt, verify_nonce, verify_blob)) => { + Ok(Self::Locked {salt, verify_nonce, verify_blob}), + }, + None => Ok(Self::Empty), + } + } + + pub async fn save(&self, pool: &SqlitePool) -> Result<(), LockError> { + let (salt, nonce, blob) = match self { + Self::Unlocked {salt, crypto} => { + let (nonce, blob) = crypto.encrypt(b"correct horse battery staple") + .map_err(|e| CryptoError::Aead(e))?; + (salt, nonce, blob) + }, + Self::Locked {salt, verify_nonce, verify_blob} => (salt, verify_nonce, verify_blob), + // "saving" an empty session just means doing nothing + Self::Empty => return Ok(()), + }; + + kv::save(pool, "salt", salt).await?; + kv::save(pool, "verify_nonce", nonce).await?; + kv::save(pool, "verify_blob", blob).await?; + + Ok(()) + } + + pub fn try_encrypt(&self, data: &[u8]) -> Result<(XNonce, Vec), CryptoError> { + let crypto = match self { + Self::Empty => Err(GetCredentialsError::Empty), + Self::Locked => Err(GetCredentialsError::Locked), + Self::Unlocked {crypto, ..} => crypto, + }?; + let res = crypto.encrypt(data)?; + Ok(res) + } + + pub fn try_decrypt(&self, nonce: XNonce, data: &[u8]) -> Result, CryptoError> { + let crypto = match self { + Self::Empty => Err(GetCredentialsError::Empty), + Self::Locked => Err(GetCredentialsError::Locked), + Self::Unlocked {crypto, ..} => crypto, + }?; + let res = crypto.decrypt(nonce, data)?; + Ok(res) + } +} + + +pub struct Crypto { + cipher: XChaCha20Poly1305, +} + +impl Crypto { + /// Argon2 params rationale: + /// + /// m_cost is measured in KiB, so 128 * 1024 gives us 128MiB. + /// This should roughly double the memory usage of the application + /// while deriving the key. + /// + /// p_cost is irrelevant since (at present) there isn't any parallelism + /// implemented, so we leave it at 1. + /// + /// With the above m_cost, t_cost = 8 results in about 800ms to derive + /// a key on my (somewhat older) CPU. This is probably overkill, but + /// given that it should only have to happen ~once a day for most + /// usage, it should be acceptable. + #[cfg(not(debug_assertions))] + const MEM_COST: u32 = 128 * 1024; + #[cfg(not(debug_assertions))] + const TIME_COST: u32 = 8; + + /// But since this takes a million years without optimizations, + /// we turn it way down in debug builds. + #[cfg(debug_assertions)] + const MEM_COST: u32 = 48 * 1024; + #[cfg(debug_assertions)] + const TIME_COST: u32 = 1; + + + fn new(passphrase: &str, salt: &[u8]) -> argon2::Result { + let params = ParamsBuilder::new() + .m_cost(Self::MEM_COST) + .p_cost(1) + .t_cost(Self::TIME_COST) + .build() + .unwrap(); // only errors if the given params are invalid + + let hasher = Argon2::new( + Algorithm::Argon2id, + Version::V0x13, + params, + ); + + let mut key = [0; 32]; + hasher.hash_password_into(passphrase.as_bytes(), &salt, &mut key)?; + let cipher = XChaCha20Poly1305::new(GenericArray::from_slice(&key)); + Ok(Crypto { cipher }) + } + + fn salt() -> [u8; 32] { + let mut salt = [0; 32]; + OsRng.fill_bytes(&mut salt); + salt + } + + fn encrypt(&self, data: &[u8]) -> Result<(XNonce, Vec), AeadError> { + let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng); + let ciphertext = self.cipher.encrypt(&nonce, data)?; + Ok((nonce, ciphertext)) + } + + fn decrypt(&self, nonce: &XNonce, data: &[u8]) -> Result, AeadError> { + self.cipher.decrypt(nonce, data) + } +} diff --git a/src-tauri/src/errors.rs b/src-tauri/src/errors.rs index 2ca4d45..cb8d548 100644 --- a/src-tauri/src/errors.rs +++ b/src-tauri/src/errors.rs @@ -127,10 +127,10 @@ pub enum SetupError { InvalidRecord, // e.g. wrong size blob for nonce or salt #[error("Error from database: {0}")] DbError(#[from] SqlxError), + #[error("Error loading data: {0}")] + KvError(#[from] LoadKvError), #[error("Error running migrations: {0}")] MigrationError(#[from] MigrateError), - #[error("Error parsing configuration from database")] - ConfigParseError(#[from] serde_json::Error), #[error("Failed to set up start-on-login: {0}")] AutoLaunchError(#[from] auto_launch::Error), #[error("Failed to start listener: {0}")] @@ -251,6 +251,38 @@ pub enum LockError { Setup(#[from] SetupError), #[error(transparent)] TauriError(#[from] tauri::Error), + #[error(transparent)] + Crypto(#[from] CryptoError), +} + + +#[derive(Debug, ThisError, AsRefStr)] +pub enum SaveCredentialsError { + #[error("Database error: {0}")] + DbError(#[from] SqlxError), + #[error("Encryption error: {0}")] + Encryption(#[from] chacha20poly1305::Error), +} + +#[derive(Debug, ThisError, AsRefStr)] +pub enum LoadCredentialsError { + #[error("Database error: {0}")] + DbError(#[from] SqlxError), + #[error("Encryption error: {0}")] + Encryption(#[from] chacha20poly1305::Error), + #[error("Credentials not found")] + NoCredentials, + #[error("Could not decode credentials: {0}")] + Invalid(#[from] serde_json::Error), +} + + +#[derive(Debug, ThisError, AsRefStr)] +pub enum LoadKvError { + #[error("Database error: {0}")] + DbError(#[from] SqlxError), + #[error("Could not parse value from database: {0}")] + Invalid(#[from] serde_json::Error), } diff --git a/src-tauri/src/kv.rs b/src-tauri/src/kv.rs new file mode 100644 index 0000000..f4a0732 --- /dev/null +++ b/src-tauri/src/kv.rs @@ -0,0 +1,103 @@ +use serde::Serialize; +use serde::de::DeserializeOwned; +use sqlx::SqlitePool; + +use crate::errors::*; + + +pub async fn save(pool: &SqlitePool, name: &str, value: &T) -> Result<(), sqlx::Error> + where T: Serialize +{ + let bytes = serde_json::to_vec(value).unwrap(); + save_bytes(pool, name, &bytes).await +} + + +pub async fn save_bytes(pool: &SqlitePool, name: &str, bytes: &[u8]) -> Result<(), sqlx::Error> { + sqlx::query!( + "INSERT INTO kv (name, value) VALUES (?, ?) + ON CONFLICT(name) DO UPDATE SET value = excluded.value;", + name, + bytes, + ).execute(pool).await?; + Ok(()) +} + + +pub async fn load(pool: &SqlitePool, name: &str) -> Result, LoadKvError> + where T: DeserializeOwned +{ + let v = load_bytes(pool, name) + .await? + .map(|bytes| serde_json::from_slice(&bytes)) + .transpose()?; + Ok(v) +} + + +pub async fn load_bytes(pool: &SqlitePool, name: &str) -> Result>, sqlx::Error> { + sqlx::query!("SELECT name, value FROM kv WHERE name = ?", name) + .map(|row| row.value) + .fetch_optional(pool) + .await + .map(|o| o.flatten()) +} + + +// pub async fn load_bytes_multi( +// pool: &SqlitePool, +// names: [&str; N], +// ) -> Result; N]>, sqlx::Error> { +// // just use multiple queries, who cares +// let res: [Vec; N] = Default::default(); +// for (i, name) in names.as_slice().iter().enumerate() { +// match load_bytes(pool, name).await? { +// Some(bytes) => res[i] = bytes, +// None => return Ok(None), +// } +// } +// Ok(res); +// } + + +macro_rules! load_bytes_multi { + ( + $pool:ident, + $($name:literal),* + ) => { + // wrap everything up in an immediately-invoked closure for easy short-circuiting + (|| { + // a tuple, with one item for each repetition of $name + ( + // repeat this match block for every name + $( + // load_bytes returns Result>, the Result is handled by + // the ? and we match on the Option + match load_bytes(pool, $name)? { + Some(v) => v, + None => return Ok(None) + }, + )* + ) + })() + } +} + + +// macro_rules! load_multi { +// ( +// $pool:ident, +// $($name:literal),* +// ) => { +// (|| { +// ( +// $( +// match load(pool, $name)? { +// Some(v) => v, +// None => return Ok(None) +// }, +// )* +// ) +// })() +// } +// } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 70c2e98..ef09f40 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -5,8 +5,9 @@ mod credentials; pub mod errors; mod clientinfo; mod ipc; +mod kv; mod state; -mod server; +pub mod server; mod shortcuts; mod terminal; mod tray; diff --git a/src-tauri/src/server/mod.rs b/src-tauri/src/server/mod.rs index 40b0bc0..66b4816 100644 --- a/src-tauri/src/server/mod.rs +++ b/src-tauri/src/server/mod.rs @@ -26,6 +26,8 @@ pub use server_unix::Server; #[cfg(unix)] use server_unix::Stream; +pub mod ssh_agent; + #[derive(Serialize, Deserialize)] pub enum Request { diff --git a/src-tauri/src/server/ssh_agent.rs b/src-tauri/src/server/ssh_agent.rs new file mode 100644 index 0000000..f1d21ac --- /dev/null +++ b/src-tauri/src/server/ssh_agent.rs @@ -0,0 +1,77 @@ +use signature::Signer; +use ssh_agent_lib::agent::{Agent, Session}; +use ssh_agent_lib::proto::message::Message; +use ssh_key::public::PublicKey; +use ssh_key::private::PrivateKey; +use tokio::net::UnixListener; + + +struct SshAgent; + +impl std::default::Default for SshAgent { + fn default() -> Self { + SshAgent {} + } +} + +#[ssh_agent_lib::async_trait] +impl Session for SshAgent { + async fn handle(&mut self, message: Message) -> Result> { + println!("Received message"); + match message { + Message::RequestIdentities => { + let p = std::path::PathBuf::from("/home/joe/.ssh/id_ed25519.pub"); + let pubkey = PublicKey::read_openssh_file(&p).unwrap(); + let id = ssh_agent_lib::proto::message::Identity { + pubkey_blob: pubkey.to_bytes().unwrap(), + comment: pubkey.comment().to_owned(), + }; + Ok(Message::IdentitiesAnswer(vec![id])) + }, + Message::SignRequest(req) => { + println!("Received sign request"); + let mut req_bytes = vec![13]; + encode_string(&mut req_bytes, &req.pubkey_blob); + encode_string(&mut req_bytes, &req.data); + req_bytes.extend(req.flags.to_be_bytes()); + std::fs::File::create("/tmp/signreq").unwrap().write(&req_bytes).unwrap(); + + let p = std::path::PathBuf::from("/home/joe/.ssh/id_ed25519"); + let passphrase = std::env::var("PRIVKEY_PASSPHRASE").unwrap(); + let privkey = PrivateKey::read_openssh_file(&p) + .unwrap() + .decrypt(passphrase.as_bytes()) + .unwrap(); + + + + let sig = Signer::sign(&privkey, &req.data); + use std::io::Write; + std::fs::File::create("/tmp/sig").unwrap().write(sig.as_bytes()).unwrap(); + + let mut payload = Vec::with_capacity(128); + encode_string(&mut payload, "ssh-ed25519".as_bytes()); + encode_string(&mut payload, sig.as_bytes()); + println!("Payload length: {}", payload.len()); + std::fs::File::create("/tmp/payload").unwrap().write(&payload).unwrap(); + Ok(Message::SignResponse(payload)) + }, + _ => Ok(Message::Failure), + } + } +} + + +fn encode_string(buf: &mut Vec, s: &[u8]) { + let len = s.len() as u32; + buf.extend(len.to_be_bytes()); + buf.extend(s); +} + + +pub async fn run() { + let socket = "/tmp/creddy-agent.sock"; + let _ = std::fs::remove_file(socket); + let listener = UnixListener::bind(socket).unwrap(); + SshAgent.listen(listener).await.unwrap(); +}