From a04e048f7c3c88dd21db6aec56a452c2dc5dd407 Mon Sep 17 00:00:00 2001 From: Ty Date: Sun, 18 Feb 2024 22:55:29 -0700 Subject: [PATCH] Improve import command --- Cargo.lock | 847 ++++++++---------- Cargo.toml | 7 +- crates/migrations/src/lib.rs | 4 +- ...m_20242801_000002_create_profiles_table.rs | 144 ++- crates/migrations/src/main.rs | 4 +- src/commands/keys/import.rs | 290 ++++++ src/commands/keys/import/gpg.rs | 291 ------ src/commands/keys/import/jwk.rs | 116 --- src/commands/keys/import/mod.rs | 18 - src/main.rs | 12 +- 10 files changed, 739 insertions(+), 994 deletions(-) create mode 100644 src/commands/keys/import.rs delete mode 100644 src/commands/keys/import/gpg.rs delete mode 100644 src/commands/keys/import/jwk.rs delete mode 100644 src/commands/keys/import/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 1c7396d..2bf1a10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,6 +196,15 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + [[package]] name = "asp" version = "0.1.0" @@ -230,13 +239,12 @@ dependencies = [ "clap-stdin", "data-encoding", "dialoguer", - "elliptic-curve", "gpgme", "indoc", "josekit", "migrations", - "pgp", "sea-orm", + "sequoia-openpgp", "thiserror", "tokio", ] @@ -314,12 +322,6 @@ 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.7" @@ -344,10 +346,39 @@ dependencies = [ ] [[package]] -name = "bitfield" -version = "0.14.0" +name = "bindgen" +version = "0.68.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" +checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" +dependencies = [ + "bitflags 2.4.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.48", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" @@ -394,25 +425,6 @@ 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 = "blowfish" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" -dependencies = [ - "byteorder", - "cipher", -] - [[package]] name = "borsh" version = "1.3.1" @@ -438,23 +450,15 @@ dependencies = [ ] [[package]] -name = "bstr" -version = "1.9.0" +name = "buffered-reader" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" +checksum = "2b9b0a25eb06e83579bc985d836e1e3b957a7201301b48538764d2b2e78090d4" dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "buffer-redux" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2886ea01509598caac116942abd33ab5a88fa32acdf7e4abfa0fc489ca520c9" -dependencies = [ - "memchr", - "safemem", + "bzip2", + "flate2", + "lazy_static", + "libc", ] [[package]] @@ -504,22 +508,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] -name = "camellia" -version = "0.1.0" +name = "bzip2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3264e2574e9ef2b53ce6f536dea83a69ac0bc600b762d1523ff83fe07230ce30" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" dependencies = [ - "byteorder", - "cipher", + "bzip2-sys", + "libc", ] [[package]] -name = "cast5" -version = "0.11.1" +name = "bzip2-sys" +version = "0.1.11+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b07d673db1ccf000e90f54b819db9e75a8348d6eb056e9b8ab53231b7a9911" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" dependencies = [ - "cipher", + "cc", + "libc", + "pkg-config", ] [[package]] @@ -538,12 +544,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] -name = "cfb-mode" -version = "0.8.2" +name = "cexpr" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "738b8d467867f80a71351933f70461f5b56f24d5c93e0cf216e59229c968d330" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "cipher", + "nom", ] [[package]] @@ -576,8 +582,10 @@ checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-targets 0.52.0", ] @@ -591,6 +599,17 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.4.18" @@ -724,12 +743,6 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" -[[package]] -name = "crc24" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd121741cf3eb82c08dd3023eb55bf2665e5f60ec20f89760cf836ae4562e6a0" - [[package]] name = "crc32fast" version = "1.3.2" @@ -755,16 +768,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] -name = "crypto-bigint" -version = "0.5.5" +name = "crunchy" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core", - "subtle", - "zeroize", -] +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-common" @@ -796,75 +803,12 @@ 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", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - [[package]] name = "custom_derive" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" -[[package]] -name = "darling" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 1.0.109", -] - -[[package]] -name = "darling_macro" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" -dependencies = [ - "darling_core", - "quote", - "syn 1.0.109", -] - [[package]] name = "data-encoding" version = "2.5.0" @@ -903,46 +847,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "derive_builder" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derive_builder_macro" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" -dependencies = [ - "derive_builder_core", - "syn 1.0.109", -] - -[[package]] -name = "des" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" -dependencies = [ - "cipher", -] - [[package]] name = "dialoguer" version = "0.11.0" @@ -956,6 +860,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -968,6 +878,27 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -975,42 +906,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] -name = "ecdsa" -version = "0.16.9" +name = "dyn-clone" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" -dependencies = [ - "curve25519-dalek", - "ed25519", - "serde", - "sha2", - "subtle", - "zeroize", -] +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" [[package]] name = "either" @@ -1021,27 +920,6 @@ dependencies = [ "serde", ] -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", - "hkdf", - "pem-rfc7468", - "pkcs8", - "rand_core", - "sec1", - "subtle", - "zeroize", -] - [[package]] name = "email_address" version = "0.2.4" @@ -1051,6 +929,15 @@ dependencies = [ "serde", ] +[[package]] +name = "ena" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" +dependencies = [ + "log", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -1105,28 +992,18 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" -[[package]] -name = "ff" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" -dependencies = [ - "rand_core", - "subtle", -] - -[[package]] -name = "fiat-crypto" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" - [[package]] name = "finl_unicode" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.28" @@ -1279,7 +1156,6 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] @@ -1289,8 +1165,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1356,17 +1234,6 @@ dependencies = [ "winreg 0.10.1", ] -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core", - "subtle", -] - [[package]] name = "h2" version = "0.3.24" @@ -1567,21 +1434,6 @@ dependencies = [ "cc", ] -[[package]] -name = "idea" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "075557004419d7f2031b8bb7f44bb43e55a83ca7b63076a8fb8fe75753836477" -dependencies = [ - "cipher", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "0.5.0" @@ -1634,6 +1486,26 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.0" @@ -1699,14 +1571,33 @@ dependencies = [ ] [[package]] -name = "keccak" -version = "0.1.5" +name = "lalrpop" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8" dependencies = [ - "cpufeatures", + "ascii-canvas", + "bit-set", + "diff", + "ena", + "is-terminal", + "itertools 0.10.5", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.7.5", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", ] +[[package]] +name = "lalrpop-util" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d" + [[package]] name = "lazy_static" version = "1.4.0" @@ -1716,6 +1607,12 @@ dependencies = [ "spin 0.5.2", ] +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.152" @@ -1733,12 +1630,33 @@ dependencies = [ "winreg 0.10.1", ] +[[package]] +name = "libloading" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "libm" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.2", + "libc", + "redox_syscall", +] + [[package]] name = "libsqlite3-sys" version = "0.27.0" @@ -1806,6 +1724,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memsec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa0916b001582d253822171bd23f4a0229d32b9507fae236f5da8cad515ba7c" + [[package]] name = "migrations" version = "0.1.0" @@ -1870,6 +1794,39 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" +[[package]] +name = "nettle" +version = "7.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fdccf3eae7b161910d2daa2f0155ca35041322e8fe5c5f1f2c9d0b12356336" +dependencies = [ + "getrandom", + "libc", + "nettle-sys", + "thiserror", + "typenum", +] + +[[package]] +name = "nettle-sys" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b495053a10a19a80e3a26bf1212e92e29350797b5f5bdc58268c3f3f818e66ec" +dependencies = [ + "bindgen", + "cc", + "libc", + "pkg-config", + "tempfile", + "vcpkg", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + [[package]] name = "nix" version = "0.27.1" @@ -1915,22 +1872,10 @@ dependencies = [ "num-iter", "num-traits", "rand", - "serde", "smallvec", "zeroize", ] -[[package]] -name = "num-derive" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - [[package]] name = "num-integer" version = "0.1.45" @@ -2070,30 +2015,6 @@ dependencies = [ "syn 2.0.48", ] -[[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 = "parking_lot" version = "0.12.1" @@ -2134,6 +2055,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2150,55 +2077,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] -name = "pgp" -version = "0.10.2" +name = "petgraph" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27e1f8e085bfa9b85763fe3ddaacbe90a09cd847b3833129153a6cb063bbe132" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ - "aes", - "base64", - "bitfield", - "block-padding", - "blowfish", - "bstr", - "buffer-redux", - "byteorder", - "camellia", - "cast5", - "cfb-mode", - "chrono", - "cipher", - "crc24", - "curve25519-dalek", - "derive_builder", - "des", - "digest", - "ed25519-dalek", - "elliptic-curve", - "flate2", - "generic-array", - "hex", - "idea", - "log", - "md-5", - "nom", - "num-bigint-dig", - "num-derive", - "num-traits", - "p256", - "p384", - "rand", - "ripemd", - "rsa", - "sha1", - "sha2", - "sha3", - "signature", - "smallvec", - "thiserror", - "twofish", - "x25519-dalek", - "zeroize", + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", ] [[package]] @@ -2240,12 +2134,6 @@ version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" -[[package]] -name = "platforms" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" - [[package]] name = "polyval" version = "0.6.1" @@ -2271,13 +2159,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] -name = "primeorder" -version = "0.13.6" +name = "precomputed-hash" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", -] +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "proc-macro-crate" @@ -2395,6 +2280,17 @@ 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.3" @@ -2433,6 +2329,12 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + [[package]] name = "regex-syntax" version = "0.8.2" @@ -2486,16 +2388,6 @@ dependencies = [ "winreg 0.50.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 = "ring" version = "0.17.7" @@ -2510,15 +2402,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "ripemd" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" -dependencies = [ - "digest", -] - [[package]] name = "rkyv" version = "0.7.43" @@ -2591,13 +2474,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] -name = "rustc_version" -version = "0.4.0" +name = "rustc-hash" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" @@ -2642,18 +2522,18 @@ dependencies = [ "untrusted", ] +[[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 = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - [[package]] name = "same-file" version = "1.0.6" @@ -2853,20 +2733,6 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" -[[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" @@ -2891,10 +2757,33 @@ dependencies = [ ] [[package]] -name = "semver" -version = "1.0.21" +name = "sequoia-openpgp" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "26e9c71323d9848404e343a6b5c3a73de10bc496ca3481b66586ba9064de027e" +dependencies = [ + "anyhow", + "base64", + "buffered-reader", + "bzip2", + "chrono", + "dyn-clone", + "flate2", + "getrandom", + "idna", + "lalrpop", + "lalrpop-util", + "lazy_static", + "libc", + "memsec", + "nettle", + "once_cell", + "regex", + "regex-syntax 0.8.2", + "sha1collisiondetection", + "thiserror", + "xxhash-rust", +] [[package]] name = "serde" @@ -2970,6 +2859,16 @@ dependencies = [ "digest", ] +[[package]] +name = "sha1collisiondetection" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1d5c4be690002e8a5d7638b0b7323f03c268c7a919bd8af69ce963a4dc83220" +dependencies = [ + "digest", + "generic-array", +] + [[package]] name = "sha2" version = "0.10.8" @@ -2981,16 +2880,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sha3" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" -dependencies = [ - "digest", - "keccak", -] - [[package]] name = "sharded-slab" version = "0.1.7" @@ -3006,6 +2895,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signature" version = "2.2.0" @@ -3022,6 +2917,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -3078,7 +2979,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" dependencies = [ - "itertools", + "itertools 0.12.0", "nom", "unicode_categories", ] @@ -3308,6 +3209,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + [[package]] name = "stringprep" version = "0.1.4" @@ -3430,6 +3344,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + [[package]] name = "thiserror" version = "1.0.56" @@ -3489,6 +3414,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -3660,15 +3594,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "twofish" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78e83a30223c757c3947cd144a31014ff04298d8719ae10d03c31c0448c8013" -dependencies = [ - "cipher", -] - [[package]] name = "typenum" version = "1.17.0" @@ -3708,6 +3633,12 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "unicode_categories" version = "0.1.1" @@ -4169,24 +4100,18 @@ dependencies = [ "tap", ] -[[package]] -name = "x25519-dalek" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" -dependencies = [ - "curve25519-dalek", - "rand_core", - "serde", - "zeroize", -] - [[package]] name = "xdg" version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" +[[package]] +name = "xxhash-rust" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927da81e25be1e1a2901d59b81b37dd2efd1fc9c9345a55007f09bf5a2d3ee03" + [[package]] name = "zerocopy" version = "0.7.32" @@ -4212,17 +4137,3 @@ name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] diff --git a/Cargo.toml b/Cargo.toml index 337ad6f..82b0be5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,14 +27,13 @@ async-trait = "0.1.77" tokio = "1.35.1" clap-stdin = "0.4.0" gpgme = { version = "0.11.0", optional = true } -pgp = { version = "0.10.2", optional = true } -josekit = { version = "0.8.5", optional = true } -elliptic-curve = { version = "0.13.8", optional = true } +sequoia-openpgp = { version = "1.18.0", optional = true } +josekit = { version = "0.8.5" } aes-gcm = "0.10.3" migrations = { path = "crates/migrations" } [features] -gpg-compat = ["dep:gpgme", "dep:pgp", "dep:josekit", "dep:elliptic-curve"] +gpg-compat = ["dep:gpgme", "dep:sequoia-openpgp"] default = ["gpg-compat"] [profile.release] diff --git a/crates/migrations/src/lib.rs b/crates/migrations/src/lib.rs index 9651f01..8387950 100644 --- a/crates/migrations/src/lib.rs +++ b/crates/migrations/src/lib.rs @@ -3,7 +3,7 @@ mod m_20242801_000002_create_profiles_table; use sea_orm_migration::prelude::*; -pub use sea_orm_migration::{SchemaManager, MigratorTrait}; +pub use sea_orm_migration::{MigratorTrait, SchemaManager}; pub struct Migrator; @@ -11,7 +11,7 @@ impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![ Box::new(m_20230701_000001_create_keys_table::Migration), - Box::new(m_20242801_000002_create_profiles_table::Migration) + Box::new(m_20242801_000002_create_profiles_table::Migration), ] } } diff --git a/crates/migrations/src/m_20242801_000002_create_profiles_table.rs b/crates/migrations/src/m_20242801_000002_create_profiles_table.rs index ac6cc8d..61e0a87 100644 --- a/crates/migrations/src/m_20242801_000002_create_profiles_table.rs +++ b/crates/migrations/src/m_20242801_000002_create_profiles_table.rs @@ -12,84 +12,60 @@ impl MigrationName for Migration { impl MigrationTrait for Migration { // Define how to apply this migration: Create the Keys table. async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.create_table( - Table::create() - .table(Claims::Table) - .col( - ColumnDef::new(Claims::Id) - .unsigned() - .not_null() - .primary_key() - .auto_increment() - ) - .col( - ColumnDef::new(Claims::Uri) - .string() - .not_null() - ) - .col( - ColumnDef::new(Claims::Profile) - .string_len(26) - .not_null() - ) - .foreign_key( - ForeignKey::create() - .name("fk-claims_profile-profiles_key") - .from(Claims::Table, Claims::Profile) - .to(Profiles::Table, Profiles::Key) - .on_update(ForeignKeyAction::Cascade) - .on_delete(ForeignKeyAction::Cascade) - ) - .to_owned() - ).await?; + manager + .create_table( + Table::create() + .table(Claims::Table) + .col( + ColumnDef::new(Claims::Id) + .unsigned() + .not_null() + .primary_key() + .auto_increment(), + ) + .col(ColumnDef::new(Claims::Uri).string().not_null()) + .col(ColumnDef::new(Claims::Profile).string_len(26).not_null()) + .foreign_key( + ForeignKey::create() + .name("fk-claims_profile-profiles_key") + .from(Claims::Table, Claims::Profile) + .to(Profiles::Table, Profiles::Key) + .on_update(ForeignKeyAction::Cascade) + .on_delete(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await?; - manager.create_table( - Table::create() - .table(Profiles::Table) - .col( - ColumnDef::new(Profiles::Key) - .string_len(26) - .not_null() - .primary_key(), - ) - .foreign_key( - ForeignKey::create() - .name("fk-profiles_key-keys_fingerprint") - .from(Profiles::Table, Profiles::Key) - .to( - super::m_20230701_000001_create_keys_table::Keys::Table, - super::m_20230701_000001_create_keys_table::Keys::Fingerprint - ) - .on_update(ForeignKeyAction::Cascade) - .on_delete(ForeignKeyAction::Cascade) - ) - .col( - ColumnDef::new(Profiles::Name) - .string() - .not_null() - ) - .col( - ColumnDef::new(Profiles::Description) - .string() - .null() - ) - .col( - ColumnDef::new(Profiles::AvatarUrl) - .string() - .null() - ) - .col( - ColumnDef::new(Profiles::Email) - .string() - .null() - ) - .col( - ColumnDef::new(Profiles::Color) - .string_len(7) - .null() - ) - .to_owned() - ).await?; + manager + .create_table( + Table::create() + .table(Profiles::Table) + .col( + ColumnDef::new(Profiles::Key) + .string_len(26) + .not_null() + .primary_key(), + ) + .foreign_key( + ForeignKey::create() + .name("fk-profiles_key-keys_fingerprint") + .from(Profiles::Table, Profiles::Key) + .to( + super::m_20230701_000001_create_keys_table::Keys::Table, + super::m_20230701_000001_create_keys_table::Keys::Fingerprint, + ) + .on_update(ForeignKeyAction::Cascade) + .on_delete(ForeignKeyAction::Cascade), + ) + .col(ColumnDef::new(Profiles::Name).string().not_null()) + .col(ColumnDef::new(Profiles::Description).string().null()) + .col(ColumnDef::new(Profiles::AvatarUrl).string().null()) + .col(ColumnDef::new(Profiles::Email).string().null()) + .col(ColumnDef::new(Profiles::Color).string_len(7).null()) + .to_owned(), + ) + .await?; Ok(()) } @@ -98,13 +74,13 @@ impl MigrationTrait for Migration { async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table(Claims::Table).to_owned()) - .await?; + .await?; - manager + manager .drop_table(Table::drop().table(Profiles::Table).to_owned()) .await?; - Ok(()) + Ok(()) } } @@ -121,8 +97,8 @@ pub enum Profiles { #[derive(Iden)] pub enum Claims { - Table, - Id, - Uri, - Profile, + Table, + Id, + Uri, + Profile, } diff --git a/crates/migrations/src/main.rs b/crates/migrations/src/main.rs index 1f3ffee..d99ee30 100644 --- a/crates/migrations/src/main.rs +++ b/crates/migrations/src/main.rs @@ -2,5 +2,5 @@ use sea_orm_migration::cli; #[tokio::main] async fn main() { - cli::run_cli(migrations::Migrator).await; -} \ No newline at end of file + cli::run_cli(migrations::Migrator).await; +} diff --git a/src/commands/keys/import.rs b/src/commands/keys/import.rs new file mode 100644 index 0000000..2e44e78 --- /dev/null +++ b/src/commands/keys/import.rs @@ -0,0 +1,290 @@ +use anyhow::{anyhow, bail, Context}; +use argon2::{password_hash::SaltString, Argon2, PasswordHasher}; +use asp::keys::AspKey; +use clap::{Parser, ValueEnum}; +use data_encoding::{BASE64URL_NOPAD, BASE64_NOPAD}; +use dialoguer::{theme::ColorfulTheme, Input, Password}; + +use aes_gcm::{ + aead::{Aead, KeyInit}, + Aes256Gcm, Key, +}; +use indoc::printdoc; +use josekit::jwk::Jwk; +use sea_orm::{ActiveValue, EntityTrait}; + +use crate::{commands::AspmSubcommand, entities::keys}; + +#[derive(ValueEnum, Debug, Clone)] +pub enum KeyImportFormat { + // An unencrypted raw JSON Web Key + Jwk, + // Imports a PGP key from a local GPG store + #[cfg(feature = "gpg-compat")] + GPG, +} + +/// Imports an ASP from raw JWK format. This only will import JWKs that have supported curves. +#[derive(Parser, Debug)] +pub struct KeysImportCommand { + /// The format of key to import + format: KeyImportFormat, + /// The key to import, as a file or "-" for stdin. This must be a valid JWK + key: String, + /// The alias of the key to import. This can be anything, and it can also be omitted to prompt interactively. This has no purpose other than providing a way to nicely name keys, rather than having to remember a fingerprint. + #[arg(short = 'n', long)] + key_alias: Option, +} + +#[async_trait::async_trait] +impl AspmSubcommand for KeysImportCommand { + async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> { + let alias = if let Some(alias) = &self.key_alias { + alias.clone() + } else { + Input::with_theme(&ColorfulTheme::default()) + .with_prompt("Please enter an alias to give to this key") + .allow_empty(false) + .interact() + .context("Unable to prompt on stderr")? + }; + + let key_password = std::env::var("KEY_PASSWORD").or_else(|_| { + Password::with_theme(&ColorfulTheme::default()) + .with_prompt("Please enter a password to store and encrypt the key with") + .with_confirmation( + "Please confirm the password", + "The two inputs did not match!", + ) + .interact() + .context("Unable to prompt on stderr") + })?; + + let asp_key = match self.format { + KeyImportFormat::Jwk => AspKey::from_jwk( + Jwk::from_bytes(self.key.as_bytes()).context("Unable to parse provided JWK")?, + ) + .context("Unable to convert parsed JWK to an AspKey")?, + #[cfg(feature = "gpg-compat")] + KeyImportFormat::GPG => { + use gpgme::{Context as GpgContext, PassphraseRequest}; + use sequoia_openpgp::{crypto::mpi, packet::key, parse::Parse, types::Curve}; + use std::{io::Write, sync::Arc}; + + let mut ctx = GpgContext::from_protocol(gpgme::Protocol::OpenPgp) + .context("Unable to create GPG context")?; + ctx.set_pinentry_mode(gpgme::PinentryMode::Loopback) + .context("Unable to set GPG pinentry mode")?; + + let mut found_key = None::; + for key in ctx + .secret_keys() + .context("Unable to fetch GPG secret keys")? + { + let Ok(key) = key else { continue }; + + if key.fingerprint().unwrap_or("") != self.key + && key + .subkeys() + .all(|subkey| subkey.fingerprint().unwrap_or("") != self.key) + { + continue; + } + + found_key = Some(key); + break; + } + let Some(found_key) = found_key else { + eprintln!("No key was found matching the provided fingerprint"); + std::process::exit(1); + }; + + // For some reason, when exporting secret keys, GPG will prompt for a password but then still + // export the key in an encrypted form. In order to prevent a password needing to be entered + // twice (one for GPG and one to decrypt the data), the password is prompted once here and stored. + let password = Arc::new( + Password::with_theme(&ColorfulTheme::default()) + .with_prompt("Please enter the password for this key. This is used to decrypt the OpenPGP key retrieved from GnuPG") + .interact() + .context("Unable to prompt on stderr")?, + ); + let password_ref = password.clone(); + + let data = ctx.with_passphrase_provider( + move |_: PassphraseRequest, out: &mut dyn Write| { + out.write_all(password_ref.as_bytes())?; + Ok(()) + }, + |ctx| { + let mut data: gpgme::Data<'_> = + gpgme::Data::new().context("Unable to create GPG data object")?; + let export_result = ctx.export( + [found_key.fingerprint().unwrap()], + gpgme::ExportMode::SECRET, + &mut data, + ); + match export_result { + Ok(data) => data, + Err(e) => match e.code() { + 11 => { + eprintln!("Wrong password"); + std::process::exit(1); + } + _ => { + return Err::, anyhow::Error>(e.into()) + .context("Unable to export secret key from GPG") + } + }, + }; + data.try_into_bytes() + .ok_or(anyhow!("Unable to retrieve byte data from gpg data object")) + }, + )?; + + let parsed = + sequoia_openpgp::Cert::from_bytes(&data).context("Unable to parse PGP key")?; + + let key = parsed + .keys() + .find(|k| k.fingerprint().to_hex() == self.key) + .context("Unable to find key in certificate")? + .parts_into_secret() + .context("Key did not have secret data")? + .key() + .clone() + .decrypt_secret(&password.clone().as_bytes().into()) + .context("Unable to decrypt PGP key")?; + + let public_part = key.mpis(); + + let (curve, public, secret) = match key.secret() { + key::SecretKeyMaterial::Unencrypted(data) => { + data.map(|secret_material| { + let (curve, public, private) = match secret_material { + mpi::SecretKeyMaterial::ECDSA { scalar } => { + let mpi::PublicKey::ECDSA { + curve, + q: public_part, + } = public_part + else { + unreachable!() + }; + + (curve, public_part, scalar) + }, + mpi::SecretKeyMaterial::EdDSA { scalar } => { + let mpi::PublicKey::EdDSA { + curve, + q: public_part, + } = public_part + else { + unreachable!() + }; + + (curve, public_part, scalar) + }, + _ => bail!("Invalid PGP key type, must be either P-256 or Ed25519") + }; + + Ok(( + curve.clone(), + public.clone(), + private.clone() + )) + })? + } + _ => unreachable!(), + }; + + AspKey::from_jwk( + Jwk::from_map({ + let mut map = josekit::Map::new(); + + match curve { + Curve::Ed25519 => { + map.insert("kty".to_string(), "OKP".into()); + map.insert("crv".to_string(), "Ed25519".into()); + map.insert( + "d".to_string(), + BASE64URL_NOPAD.encode(secret.value()).into(), + ); + + map.insert( + "x".to_string(), + BASE64URL_NOPAD.encode(public.value()).into(), + ); + }, + Curve::NistP256 => { + map.insert("kty".to_string(), "EC".into()); + map.insert("crv".to_string(), "P-256".into()); + map.insert( + "d".to_string(), + BASE64URL_NOPAD.encode(secret.value()).into(), + ); + + let (x, y) = public.decode_point(&curve).context("Unable to decode p256 public point")?; + + map.insert( + "x".to_string(), + BASE64URL_NOPAD.encode(x).into(), + ); + map.insert( + "y".to_string(), + BASE64URL_NOPAD.encode(y).into(), + ); + }, + _ => bail!("Invalid PGP key type, only Ed25519 and NIST P256 are supported by ASPs.") + } + + map + }).context("Unable to construct Jwk representation of PGP key")? + )? + } + }; + + let argon_salt = SaltString::from_b64( + &BASE64_NOPAD.encode(asp_key.fingerprint.to_uppercase().as_bytes()), + ) + .context("Unable to derive argon2 salt")?; + let argon2 = Argon2::default(); + let hash = argon2 + .hash_password(key_password.as_bytes(), &argon_salt) + .or(Err(anyhow!("Unable to derive encryption key")))?; + let aes_key = hash.hash.context("Unable to derive encryption key")?; + let aes_key = &aes_key.as_bytes()[0..32]; + + let key = Key::::from_slice(aes_key); + let Ok(cipher_text) = Aes256Gcm::new(&key) + .encrypt((&aes_key[0..12]).into(), asp_key.jwk.to_string().as_bytes()) + else { + bail!("Failure encrypting key") + }; + + // Write to db + let entry = keys::ActiveModel { + fingerprint: ActiveValue::Set(asp_key.fingerprint.clone()), + key_type: ActiveValue::Set(asp_key.key_type.clone().into()), + alias: ActiveValue::Set(alias), + cipher_text: ActiveValue::Set(cipher_text), + }; + let res = keys::Entity::insert(entry) + .exec(&state.db) + .await + .context("Unable to add key to database")?; + if res.last_insert_id != asp_key.fingerprint { + bail!("The key was unable to be saved to the database") + } + + printdoc! { + " + Successfully imported key! + Fingerprint: {fpr} + Type: {type:?} + ", + fpr = asp_key.fingerprint, + r#type = asp_key.key_type + }; + + Ok(()) + } +} diff --git a/src/commands/keys/import/gpg.rs b/src/commands/keys/import/gpg.rs deleted file mode 100644 index 6b66dc9..0000000 --- a/src/commands/keys/import/gpg.rs +++ /dev/null @@ -1,291 +0,0 @@ -#![cfg(feature = "gpg-compat")] - -use std::{io::Write, sync::Arc}; - -use aes_gcm::{ - aead::{Aead, KeyInit}, - Aes256Gcm, Key, -}; -use anyhow::{anyhow, bail, Context}; -use argon2::{password_hash::SaltString, Argon2, PasswordHasher}; -use clap::Parser; -use data_encoding::{BASE64URL_NOPAD, BASE64_NOPAD}; -use dialoguer::{theme::ColorfulTheme, Password}; -use elliptic_curve::sec1::{Coordinates, ToEncodedPoint}; -use gpgme::{Context as GpgContext, PassphraseRequest}; -use indoc::printdoc; -use pgp::{ - crypto::ecc_curve::ECCCurve, - types::{EcdsaPublicParams, KeyTrait, PlainSecretParams, PublicParams, SecretParams}, -}; -use sea_orm::{ActiveValue, EntityTrait}; -use tokio::runtime::Runtime; - -use crate::{commands::AspmSubcommand, entities::keys}; - -/// A command to import a key from GPG, by fingerprint. The password used to store the key will be the exact same as the password used to decrypt the key from GPG, and the key alias will be the primary UID (or only UID) from the GPG key. -#[derive(Parser, Debug)] -pub struct KeysImportGpgCommand { - /// The full fingerprint of the GPG key to import. In order to import a subkey, the subkey's fingerprint much be provided, rather than the primary key's. - fingerprint: String, -} - -#[async_trait::async_trait] -impl AspmSubcommand for KeysImportGpgCommand { - fn execute_sync(&self, state: crate::AspmState, runtime: Runtime) -> Result<(), anyhow::Error> { - let mut ctx = GpgContext::from_protocol(gpgme::Protocol::OpenPgp) - .context("Unable to create GPG context")?; - ctx.set_pinentry_mode(gpgme::PinentryMode::Loopback) - .context("Unable to set GPG pinentry mode")?; - - let mut found_key = None::; - for key in ctx - .secret_keys() - .context("Unable to fetch GPG secret keys")? - { - let Ok(key) = key else { continue }; - - if key.fingerprint().unwrap_or("") != self.fingerprint - && key - .subkeys() - .all(|subkey| subkey.fingerprint().unwrap_or("") != self.fingerprint) - { - continue; - } - - found_key = Some(key); - break; - } - let Some(found_key) = found_key else { - eprintln!("No key was found matching the provided fingerprint"); - std::process::exit(1); - }; - - // For some reason, when exporting secret keys, GPG will prompt for a password but then still - // export the key in an encrypted form. In order to prevent a password needing to be entered - // twice (one for GPG and one to decrypt the data), the password is prompted once here and stored. - let password = Arc::new( - Password::with_theme(&ColorfulTheme::default()) - .with_prompt("Please enter the password for this key. This is both used to decrypt the OpenPGP key, and then to re-encrypt it in the aspm format") - .interact() - .context("Unable to prompt on stderr")?, - ); - let password_ref = password.clone(); - - let data = ctx.with_passphrase_provider( - move |_: PassphraseRequest, out: &mut dyn Write| { - out.write_all(password_ref.as_bytes())?; - Ok(()) - }, - |ctx| { - let mut data: gpgme::Data<'_> = - gpgme::Data::new().context("Unable to create GPG data object")?; - let export_result = ctx.export( - [found_key.fingerprint().unwrap()], - gpgme::ExportMode::SECRET, - &mut data, - ); - match export_result { - Ok(data) => data, - Err(e) => match e.code() { - 11 => { - eprintln!("Wrong password"); - std::process::exit(1); - } - _ => { - return Err::, anyhow::Error>(e.into()) - .context("Unable to export secret key from GPG") - } - }, - }; - data.try_into_bytes() - .ok_or(anyhow!("Unable to retrieve byte data from gpg data object")) - }, - )?; - - let mut parsed = pgp::from_bytes_many(data.as_slice()); - let parsed = parsed.next().context("Invalid GPG data")?; - - let Ok(key) = parsed else { - bail!("GPG data was unable to be parsed"); - }; - - let key = key.into_secret(); - let Some(uid) = key - .details - .users - .iter() - .find(|uid| uid.is_primary()) - .or_else(|| { - if key.details.users.len() == 1 { - Some(&key.details.users[0]) - } else { - None - } - }) - else { - eprintln!("Key being imported has no primary uid. This must be set, as it is used for the key alias."); - std::process::exit(1); - }; - let (algorithm, public_params, secret_params) = { - if key - .primary_key - .fingerprint() - .iter() - .map(|byte| format!("{:02X}", byte)) - .collect::>() - .join("") == self.fingerprint - { - ( - key.primary_key.algorithm(), - key.primary_key.public_params(), - key.primary_key.secret_params(), - ) - } else { - let subkey = &key - .secret_subkeys - .iter() - .find(|subkey| { - subkey - .fingerprint() - .iter() - .map(|byte| format!("{:02X}", byte)) - .collect::>() - .join("") == self.fingerprint - }) - .context("Unable to find subkey after parsing")? - .key; - ( - subkey.algorithm(), - subkey.public_params(), - subkey.secret_params(), - ) - } - }; - - let unlocked = match secret_params { - SecretParams::Plain(params) => params.clone(), - SecretParams::Encrypted(params) => { - let password_ref = password.clone(); - params - .unlock(move || password_ref.to_string(), algorithm, public_params) - .context("Unable to parse key secret params")? - } - }; - let asp_key = match &unlocked { - PlainSecretParams::ECDSA(priv_mpi) => { - let public_key = match public_params { - PublicParams::ECDSA(public_params) => { - if let EcdsaPublicParams::P256 { key, p: _ } = public_params { - key - } else { - eprintln!("GPG key uses an unsupported elliptic curve type. Only Ed25519 and P-256 curves are supported."); - std::process::exit(1); - } - } - _ => bail!("Key had EdDSA secret params, but not EdDSA public params"), - }; - asp::keys::AspKey::from_jwk( - josekit::jwk::Jwk::from_map({ - let mut map = josekit::Map::new(); - map.insert("kty".to_string(), "EC".into()); - map.insert("crv".to_string(), "P-256".into()); - map.insert( - "d".to_string(), - BASE64URL_NOPAD.encode(priv_mpi.as_bytes()).into(), - ); - - let encoded = public_key.to_encoded_point(false); - let Coordinates::Uncompressed { x, y } = encoded.coordinates() else { - bail!("EC key coordinates were not uncompressed") - }; - map.insert("x".to_string(), BASE64URL_NOPAD.encode(x.as_slice()).into()); - map.insert("y".to_string(), BASE64URL_NOPAD.encode(y.as_slice()).into()); - map - }) - .context("Unable to construct JWK map")?, - )? - } - PlainSecretParams::EdDSA(priv_mpi) => { - let pub_mpi = match public_params { - PublicParams::EdDSA { curve, q: pub_mpi } => { - if let ECCCurve::Ed25519 = curve { - pub_mpi - } else { - eprintln!("GPG key uses an unsupported elliptic curve type. Only Ed25519 and P-256 curves are supported."); - std::process::exit(1); - } - } - _ => bail!("Key had EdDSA secret params, but not EdDSA public params"), - }; - asp::keys::AspKey::from_jwk( - josekit::jwk::Jwk::from_map({ - let mut map = josekit::Map::new(); - map.insert("kty".to_string(), "OKP".into()); - map.insert("crv".to_string(), "Ed25519".into()); - map.insert( - "d".to_string(), - BASE64URL_NOPAD.encode(priv_mpi.as_bytes()).into(), - ); - - map.insert( - "x".to_string(), - BASE64URL_NOPAD.encode(pub_mpi.as_bytes()).into(), - ); - - map - }) - .context("Unable to construct JWK map")?, - )? - } - _ => { - eprintln!("GPG key is of unsupported type. Only Ed25519 and EC p-256 keys can be imported"); - std::process::exit(1); - } - }; - - let argon_salt = SaltString::from_b64( - &BASE64_NOPAD.encode(asp_key.fingerprint.to_uppercase().as_bytes()), - ) - .context("Unable to derive argon2 salt")?; - let argon2 = Argon2::default(); - let hash = argon2 - .hash_password(password.as_bytes(), &argon_salt) - .or(Err(anyhow!("Unable to derive encryption key")))?; - let aes_key = hash.hash.context("Unable to derive encryption key")?; - let aes_key = &aes_key.as_bytes()[0..32]; - - let key = Key::::from_slice(aes_key); - let Ok(cipher_text) = Aes256Gcm::new(&key) - .encrypt((&aes_key[0..12]).into(), asp_key.jwk.to_string().as_bytes()) - else { - bail!("Failure encrypting key") - }; - - let entry = keys::ActiveModel { - fingerprint: ActiveValue::Set(asp_key.fingerprint.clone()), - key_type: ActiveValue::Set(asp_key.key_type.clone().into()), - alias: ActiveValue::Set(format!("{uid}", uid = uid.id.id())), - cipher_text: ActiveValue::Set(cipher_text), - }; - // Because GPGME's context object is not 'Send', normal .await can't be used here, so this just blocks as a workaround - let res = runtime - .block_on(keys::Entity::insert(entry).exec(&state.db)) - .context("Unable to add key to database")?; - if res.last_insert_id != asp_key.fingerprint { - bail!("The key was unable to be saved to the database") - } - - printdoc! { - " - Successfully imported key! - ASP Fingerprint: {fpr} - Type: {type:?} - ", - fpr = asp_key.fingerprint, - r#type = asp_key.key_type - }; - - Ok(()) - } -} diff --git a/src/commands/keys/import/jwk.rs b/src/commands/keys/import/jwk.rs deleted file mode 100644 index 0661ae2..0000000 --- a/src/commands/keys/import/jwk.rs +++ /dev/null @@ -1,116 +0,0 @@ -use std::io::Read; - -use anyhow::{anyhow, bail, Context}; -use argon2::{password_hash::SaltString, Argon2, PasswordHasher}; -use asp::keys::AspKey; -use clap::{Parser, ValueEnum}; -use clap_stdin::FileOrStdin; -use data_encoding::BASE64_NOPAD; -use dialoguer::{theme::ColorfulTheme, Input, Password}; - -use aes_gcm::{ - aead::{Aead, KeyInit}, - Aes256Gcm, Key, -}; -use indoc::printdoc; -use josekit::jwk::Jwk; -use sea_orm::{ActiveValue, EntityTrait}; - -use crate::{commands::AspmSubcommand, entities::keys}; - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)] -pub enum KeyGenerationType { - Ed25519, - ES256, -} - -/// Imports an ASP from raw JWK format. This only will import JWKs that have supported curves. -#[derive(Parser, Debug)] -pub struct KeysImportJwkCommand { - /// The key to import, as a file or "-" for stdin. This must be a valid JWK - key: FileOrStdin, - /// The alias of the key to import. This can be anything, and it can also be omitted to prompt interactively. This has no purpose other than providing a way to nicely name keys, rather than having to remember a fingerprint. - #[arg(short = 'n', long)] - key_alias: Option, -} - -#[async_trait::async_trait] -impl AspmSubcommand for KeysImportJwkCommand { - async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> { - let alias = if let Some(alias) = &self.key_alias { - alias.clone() - } else { - Input::with_theme(&ColorfulTheme::default()) - .with_prompt("Please enter an alias to give to this key") - .allow_empty(false) - .interact() - .context("Unable to prompt on stderr")? - }; - - let key_password = std::env::var("KEY_PASSWORD").or_else(|_| { - Password::with_theme(&ColorfulTheme::default()) - .with_prompt("Please enter a password to store and encrypt the key with") - .with_confirmation( - "Please confirm the password", - "The two inputs did not match!", - ) - .interact() - .context("Unable to prompt on stderr") - })?; - - let asp_key = AspKey::from_jwk( - Jwk::from_bytes({ - let mut buf = Vec::new(); - self.key.into_reader()?.read_to_end(&mut buf)?; - buf - }) - .context("Unable to parse provided JWK")?, - ) - .context("Unable to convert parsed JWK to an AspKey")?; - - let argon_salt = SaltString::from_b64( - &BASE64_NOPAD.encode(asp_key.fingerprint.to_uppercase().as_bytes()), - ) - .context("Unable to derive argon2 salt")?; - let argon2 = Argon2::default(); - let hash = argon2 - .hash_password(key_password.as_bytes(), &argon_salt) - .or(Err(anyhow!("Unable to derive encryption key")))?; - let aes_key = hash.hash.context("Unable to derive encryption key")?; - let aes_key = &aes_key.as_bytes()[0..32]; - - let key = Key::::from_slice(aes_key); - let Ok(cipher_text) = Aes256Gcm::new(&key) - .encrypt((&aes_key[0..12]).into(), asp_key.jwk.to_string().as_bytes()) - else { - bail!("Failure encrypting key") - }; - - // Write to db - let entry = keys::ActiveModel { - fingerprint: ActiveValue::Set(asp_key.fingerprint.clone()), - key_type: ActiveValue::Set(asp_key.key_type.clone().into()), - alias: ActiveValue::Set(alias), - cipher_text: ActiveValue::Set(cipher_text), - }; - let res = keys::Entity::insert(entry) - .exec(&state.db) - .await - .context("Unable to add key to database")?; - if res.last_insert_id != asp_key.fingerprint { - bail!("The key was unable to be saved to the database") - } - - printdoc! { - " - Successfully imported key! - Fingerprint: {fpr} - Type: {type:?} - ", - fpr = asp_key.fingerprint, - r#type = asp_key.key_type - }; - - Ok(()) - } -} diff --git a/src/commands/keys/import/mod.rs b/src/commands/keys/import/mod.rs deleted file mode 100644 index 6fabf27..0000000 --- a/src/commands/keys/import/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -use clap::{Parser, Subcommand}; - -#[cfg(feature = "gpg-compat")] -mod gpg; -mod jwk; - -/// A subcommand to allow the management of keys, which can then be used to create, modify, or delete profiles. -#[derive(Parser)] -pub struct KeysImportCommand { - #[command(subcommand)] - pub subcommand: KeysImportSubcommands, -} - -#[derive(Subcommand)] -pub enum KeysImportSubcommands { - Gpg(gpg::KeysImportGpgCommand), - Jwk(jwk::KeysImportJwkCommand), -} diff --git a/src/main.rs b/src/main.rs index 36e65c4..9c0b9e9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,13 +5,10 @@ use anstyle::{AnsiColor, Color as AnstyleColor, Style as Anstyle}; use anyhow::Context; use app_dirs2::{AppDataType, AppInfo}; use clap::{Parser, Subcommand}; -use commands::{ - keys::{import::KeysImportSubcommands, KeysSubcommands}, - AspmSubcommand, -}; +use commands::{keys::KeysSubcommands, AspmSubcommand}; +use migrations::{Migrator, MigratorTrait, SchemaManager}; use sea_orm::{Database, DatabaseConnection}; use thiserror::Error; -use migrations::{Migrator, SchemaManager, MigratorTrait}; use std::path::PathBuf; @@ -151,10 +148,7 @@ fn cli(parsed: AspmCommand) -> Result<(), anyhow::Error> { KeysSubcommands::List(subcommand) => subcommand.execute_sync(state, runtime), KeysSubcommands::Export(subcommand) => subcommand.execute_sync(state, runtime), KeysSubcommands::Delete(subcommand) => subcommand.execute_sync(state, runtime), - KeysSubcommands::Import(subcommand) => match &subcommand.subcommand { - KeysImportSubcommands::Gpg(subcommand) => subcommand.execute_sync(state, runtime), - KeysImportSubcommands::Jwk(subcommand) => subcommand.execute_sync(state, runtime), - }, + KeysSubcommands::Import(subcommand) => subcommand.execute_sync(state, runtime), }, } }