From 4ef0c74f4c1c1b86cadbcc09152c11242d7e0f28 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 19 Dec 2024 05:16:19 -0800 Subject: [PATCH] [PM-10941] Add ssh import (#82) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 🎟ī¸ Tracking https://bitwarden.atlassian.net/browse/PM-10941 ## 📔 Objective Adds ssh import to the sdk so we can validate, decrypt, and convert keys on import on all clients, and unblock importing 1Password keys from pux exports. This much cleans up the implementation of the `desktop_native` module, replacing a lot of mapping and matching code. Further, this moves generator out of `bitwarden-ssh`'s `lib.rs`. Note: importer will subsequently be removed from `desktop_native` ## ⏰ Reminders before review - Contributor guidelines followed - All formatters and local linters executed and passed - Written new unit and / or integration tests where applicable - Protected functional changes with optionality (feature flags) - Used internationalization (i18n) for all UI strings - CI builds passed - Communicated to DevOps any deployment requirements - Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team ## đŸĻŽ Reviewer guidelines - 👍 (`:+1:`) or similar for great changes - 📝 (`:memo:`) or ℹī¸ (`:information_source:`) for notes or general info - ❓ (`:question:`) for questions - 🤔 (`:thinking:`) or 💭 (`:thought_balloon:`) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion - 🎨 (`:art:`) for suggestions / improvements - ❌ (`:x:`) or ⚠ī¸ (`:warning:`) for more significant problems or concerns needing attention - 🌱 (`:seedling:`) or â™ģī¸ (`:recycle:`) for future improvements or indications of technical debt - ⛏ (`:pick:`) for minor or nitpick changes --------- Co-authored-by: Oscar Hinton --- Cargo.lock | 63 +++-- crates/bitwarden-ssh/Cargo.toml | 12 +- .../generator}/ed25519_key | 0 .../generator}/rsa3072_key | 0 .../generator}/rsa4096_key | 0 .../import/ecdsa_openssh_unencrypted | 8 + .../import/ecdsa_openssh_unencrypted.pub | 1 + .../import/ed25519_openssh_encrypted | 8 + .../import/ed25519_openssh_encrypted.pub | 1 + .../import/ed25519_openssh_unencrypted | 7 + .../import/ed25519_openssh_unencrypted.pub | 1 + .../import/ed25519_pkcs8_unencrypted | 4 + .../import/ed25519_pkcs8_unencrypted.pub | 1 + .../import/ed25519_putty_openssh_unencrypted | 8 + .../resources/import/rsa_openssh_encrypted | 39 +++ .../import/rsa_openssh_encrypted.pub | 1 + .../resources/import/rsa_openssh_unencrypted | 38 +++ .../import/rsa_openssh_unencrypted.pub | 1 + .../resources/import/rsa_pkcs8_encrypted | 42 ++++ .../resources/import/rsa_pkcs8_encrypted.pub | 1 + .../resources/import/rsa_pkcs8_unencrypted | 40 +++ .../import/rsa_pkcs8_unencrypted.pub | 1 + .../import/rsa_putty_openssh_unencrypted | 30 +++ .../import/rsa_putty_pkcs1_unencrypted | 27 +++ .../resources/import/wrong_label | 8 + crates/bitwarden-ssh/src/error.rs | 24 +- crates/bitwarden-ssh/src/generator.rs | 87 +++++++ crates/bitwarden-ssh/src/import.rs | 227 ++++++++++++++++++ crates/bitwarden-ssh/src/lib.rs | 103 ++------ crates/bitwarden-wasm-internal/src/ssh.rs | 35 ++- 30 files changed, 705 insertions(+), 113 deletions(-) rename crates/bitwarden-ssh/{tests => resources/generator}/ed25519_key (100%) rename crates/bitwarden-ssh/{tests => resources/generator}/rsa3072_key (100%) rename crates/bitwarden-ssh/{tests => resources/generator}/rsa4096_key (100%) create mode 100644 crates/bitwarden-ssh/resources/import/ecdsa_openssh_unencrypted create mode 100644 crates/bitwarden-ssh/resources/import/ecdsa_openssh_unencrypted.pub create mode 100644 crates/bitwarden-ssh/resources/import/ed25519_openssh_encrypted create mode 100644 crates/bitwarden-ssh/resources/import/ed25519_openssh_encrypted.pub create mode 100644 crates/bitwarden-ssh/resources/import/ed25519_openssh_unencrypted create mode 100644 crates/bitwarden-ssh/resources/import/ed25519_openssh_unencrypted.pub create mode 100644 crates/bitwarden-ssh/resources/import/ed25519_pkcs8_unencrypted create mode 100644 crates/bitwarden-ssh/resources/import/ed25519_pkcs8_unencrypted.pub create mode 100644 crates/bitwarden-ssh/resources/import/ed25519_putty_openssh_unencrypted create mode 100644 crates/bitwarden-ssh/resources/import/rsa_openssh_encrypted create mode 100644 crates/bitwarden-ssh/resources/import/rsa_openssh_encrypted.pub create mode 100644 crates/bitwarden-ssh/resources/import/rsa_openssh_unencrypted create mode 100644 crates/bitwarden-ssh/resources/import/rsa_openssh_unencrypted.pub create mode 100644 crates/bitwarden-ssh/resources/import/rsa_pkcs8_encrypted create mode 100644 crates/bitwarden-ssh/resources/import/rsa_pkcs8_encrypted.pub create mode 100644 crates/bitwarden-ssh/resources/import/rsa_pkcs8_unencrypted create mode 100644 crates/bitwarden-ssh/resources/import/rsa_pkcs8_unencrypted.pub create mode 100644 crates/bitwarden-ssh/resources/import/rsa_putty_openssh_unencrypted create mode 100644 crates/bitwarden-ssh/resources/import/rsa_putty_pkcs1_unencrypted create mode 100644 crates/bitwarden-ssh/resources/import/wrong_label create mode 100644 crates/bitwarden-ssh/src/generator.rs create mode 100644 crates/bitwarden-ssh/src/import.rs diff --git a/Cargo.lock b/Cargo.lock index ad1d28d3..7f03988d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -584,8 +584,12 @@ name = "bitwarden-ssh" version = "1.0.0" dependencies = [ "bitwarden-error", + "ed25519", + "pem-rfc7468", + "pkcs8", "rand", "rand_chacha", + "rsa", "serde", "ssh-key", "thiserror 1.0.69", @@ -1398,6 +1402,7 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ + "pkcs8", "signature", ] @@ -2580,24 +2585,8 @@ 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", - "sha2", ] [[package]] @@ -2711,6 +2700,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest", + "hmac", ] [[package]] @@ -2751,6 +2741,21 @@ dependencies = [ "spki", ] +[[package]] +name = "pkcs5" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" +dependencies = [ + "aes", + "cbc", + "der", + "pbkdf2", + "scrypt", + "sha2", + "spki", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -2758,6 +2763,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", + "pkcs5", + "rand_core", "spki", ] @@ -3245,6 +3252,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -3321,6 +3337,17 @@ dependencies = [ "syn", ] +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2", +] + [[package]] name = "sec1" version = "0.7.3" @@ -3674,12 +3701,8 @@ dependencies = [ "bcrypt-pbkdf", "ed25519-dalek", "num-bigint-dig", - "p256", - "p384", - "p521", "rand_core", "rsa", - "sec1", "sha2", "signature", "ssh-cipher", diff --git a/crates/bitwarden-ssh/Cargo.toml b/crates/bitwarden-ssh/Cargo.toml index f1f1095e..e17f53e3 100644 --- a/crates/bitwarden-ssh/Cargo.toml +++ b/crates/bitwarden-ssh/Cargo.toml @@ -3,6 +3,7 @@ name = "bitwarden-ssh" description = """ Internal crate for the bitwarden crate. Do not use. """ +exclude = ["/resources"] version.workspace = true authors.workspace = true @@ -22,14 +23,17 @@ wasm = [ [dependencies] bitwarden-error = { workspace = true } -rand = "0.8.5" +ed25519 = { version = ">=2.2.3, <3.0", features = ["pkcs8"] } +pem-rfc7468 = "0.7.0" +pkcs8 = { version = ">=0.10.2, <0.11", features = ["encryption"] } +rand = ">=0.8.5, <0.9" +rsa = ">=0.9.2, <0.10" serde.workspace = true -ssh-key = { version = "0.6.7", features = [ +ssh-key = { version = ">=0.6.7, <0.7", features = [ "ed25519", "encryption", "rsa", - "getrandom", -] } +], default-features = false } thiserror = { workspace = true } tsify-next = { workspace = true, optional = true } wasm-bindgen = { workspace = true, optional = true } diff --git a/crates/bitwarden-ssh/tests/ed25519_key b/crates/bitwarden-ssh/resources/generator/ed25519_key similarity index 100% rename from crates/bitwarden-ssh/tests/ed25519_key rename to crates/bitwarden-ssh/resources/generator/ed25519_key diff --git a/crates/bitwarden-ssh/tests/rsa3072_key b/crates/bitwarden-ssh/resources/generator/rsa3072_key similarity index 100% rename from crates/bitwarden-ssh/tests/rsa3072_key rename to crates/bitwarden-ssh/resources/generator/rsa3072_key diff --git a/crates/bitwarden-ssh/tests/rsa4096_key b/crates/bitwarden-ssh/resources/generator/rsa4096_key similarity index 100% rename from crates/bitwarden-ssh/tests/rsa4096_key rename to crates/bitwarden-ssh/resources/generator/rsa4096_key diff --git a/crates/bitwarden-ssh/resources/import/ecdsa_openssh_unencrypted b/crates/bitwarden-ssh/resources/import/ecdsa_openssh_unencrypted new file mode 100644 index 00000000..9cf518f8 --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/ecdsa_openssh_unencrypted @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS +1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQRQzzQ8nQEouF1FMSHkPx1nejNCzF7g +Yb8MHXLdBFM0uJkWs0vzgLJkttts2eDv3SHJqIH6qHpkLtEvgMXE5WcaAAAAoOO1BebjtQ +XmAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFDPNDydASi4XUUx +IeQ/HWd6M0LMXuBhvwwdct0EUzS4mRazS/OAsmS222zZ4O/dIcmogfqoemQu0S+AxcTlZx +oAAAAhAKnIXk6H0Hs3HblklaZ6UmEjjdE/0t7EdYixpMmtpJ4eAAAAB3Rlc3RrZXk= +-----END OPENSSH PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/resources/import/ecdsa_openssh_unencrypted.pub b/crates/bitwarden-ssh/resources/import/ecdsa_openssh_unencrypted.pub new file mode 100644 index 00000000..75e08b88 --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/ecdsa_openssh_unencrypted.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFDPNDydASi4XUUxIeQ/HWd6M0LMXuBhvwwdct0EUzS4mRazS/OAsmS222zZ4O/dIcmogfqoemQu0S+AxcTlZxo= testkey diff --git a/crates/bitwarden-ssh/resources/import/ed25519_openssh_encrypted b/crates/bitwarden-ssh/resources/import/ed25519_openssh_encrypted new file mode 100644 index 00000000..d3244a3d --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/ed25519_openssh_encrypted @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAUTNb0if +fqsoqtfv70CfukAAAAGAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIHGs3Uw3eyqnFjBI +2eb7Qto4KVc34ZdnBac59Bab54BLAAAAkPA6aovfxQbP6FoOfaRH6u22CxqiUM0bbMpuFf +WETn9FLaBE6LjoHH0ZI5rzNjJaQUNfx0cRcqsIrexw8YINrdVjySmEqrl5hw8gpgy0gGP5 +1Y6vKWdHdrxJCA9YMFOfDs0UhPfpLKZCwm2Sg+Bd8arlI8Gy7y4Jj/60v2bZOLhD2IZQnK +NdJ8xATiIINuTy4g== +-----END OPENSSH PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/resources/import/ed25519_openssh_encrypted.pub b/crates/bitwarden-ssh/resources/import/ed25519_openssh_encrypted.pub new file mode 100644 index 00000000..1188fa43 --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/ed25519_openssh_encrypted.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHGs3Uw3eyqnFjBI2eb7Qto4KVc34ZdnBac59Bab54BL testkey diff --git a/crates/bitwarden-ssh/resources/import/ed25519_openssh_unencrypted b/crates/bitwarden-ssh/resources/import/ed25519_openssh_unencrypted new file mode 100644 index 00000000..08184f31 --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/ed25519_openssh_unencrypted @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACAyQo22TXXNqvF+L8jUSSNeu8UqrsDjvf9pwIwDC9ML6gAAAJDSHpL60h6S ++gAAAAtzc2gtZWQyNTUxOQAAACAyQo22TXXNqvF+L8jUSSNeu8UqrsDjvf9pwIwDC9ML6g +AAAECLdlFLIJbEiFo/f0ROdXMNZAPHGPNhvbbftaPsUZEjaDJCjbZNdc2q8X4vyNRJI167 +xSquwOO9/2nAjAML0wvqAAAAB3Rlc3RrZXkBAgMEBQY= +-----END OPENSSH PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/resources/import/ed25519_openssh_unencrypted.pub b/crates/bitwarden-ssh/resources/import/ed25519_openssh_unencrypted.pub new file mode 100644 index 00000000..5c398822 --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/ed25519_openssh_unencrypted.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDJCjbZNdc2q8X4vyNRJI167xSquwOO9/2nAjAML0wvq testkey diff --git a/crates/bitwarden-ssh/resources/import/ed25519_pkcs8_unencrypted b/crates/bitwarden-ssh/resources/import/ed25519_pkcs8_unencrypted new file mode 100644 index 00000000..09eb7286 --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/ed25519_pkcs8_unencrypted @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MFECAQEwBQYDK2VwBCIEIDY6/OAdDr3PbDss9NsLXK4CxiKUvz5/R9uvjtIzj4Sz +gSEAxsxm1xpZ/4lKIRYm0JrJ5gRZUh7H24/YT/0qGVGzPa0= +-----END PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/resources/import/ed25519_pkcs8_unencrypted.pub b/crates/bitwarden-ssh/resources/import/ed25519_pkcs8_unencrypted.pub new file mode 100644 index 00000000..40997e18 --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/ed25519_pkcs8_unencrypted.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMbMZtcaWf+JSiEWJtCayeYEWVIex9uP2E/9KhlRsz2t diff --git a/crates/bitwarden-ssh/resources/import/ed25519_putty_openssh_unencrypted b/crates/bitwarden-ssh/resources/import/ed25519_putty_openssh_unencrypted new file mode 100644 index 00000000..aa9c01b8 --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/ed25519_putty_openssh_unencrypted @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtz +c2gtZWQyNTUxOQAAACDp0/9zFBCyZs5BFqXCJN5i1DTanzPGHpUeo2LP8FmQ9wAA +AKCyIXPqsiFz6gAAAAtzc2gtZWQyNTUxOQAAACDp0/9zFBCyZs5BFqXCJN5i1DTa +nzPGHpUeo2LP8FmQ9wAAAEDQioomhjmD+sh2nsxfQLJ5YYGASNUAlUZHe9Jx0p47 +H+nT/3MUELJmzkEWpcIk3mLUNNqfM8YelR6jYs/wWZD3AAAAEmVkZHNhLWtleS0y +MDI0MTExOAECAwQFBgcICQoL +-----END OPENSSH PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/resources/import/rsa_openssh_encrypted b/crates/bitwarden-ssh/resources/import/rsa_openssh_encrypted new file mode 100644 index 00000000..bb7bbd85 --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/rsa_openssh_encrypted @@ -0,0 +1,39 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +2MmKBw== +-----END OPENSSH PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/resources/import/rsa_openssh_encrypted.pub b/crates/bitwarden-ssh/resources/import/rsa_openssh_encrypted.pub new file mode 100644 index 00000000..d37f573b --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/rsa_openssh_encrypted.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC/v18xGP3qzRV9iWqyiuwHZ4GpC4K2NO2/i2Yv5A3/bnal7CmiMh/S78lphgxcWtFkwrwlb321FmdHBv6KOW+EzSiPvmsdkkbpfBXB3Qf2SlhZOZZ7lYeu8KAxL3exvvn8O1GGlUjXGUrFgmC60tHWDBc1Ncmo8a2dwDLmA/sbLa8su2dvYEFmRg1vaytLDpkn8GS7zAxrUl/g0W2RwkPsByduUziQuX90v9WAy7MqOlwBRq6t5o8wdDBVODe0VIXC7N1OS42YUsKF+N0XOnLiJrIIKkXpahMDpKZHeHQAdUQzsJVhKoLJR8DNDTYyhnJoQG7Q6m2gDTca9oAWvsBiNoEwCvwrt7cDNCz/GslH9HXQgfWcVXn8+fuZgvjO3CxUI16Ev33m0jWoOKJcgK/ZLRnk8SEvsJ8NO32MeR/qUb7IN/yUcDmPMI/3ecQsakF2cwNzHkyiGVo//yVTpf+vk8b89L+GXbYU5rtswtc2ZEGsQnUkaoNqS8mHqhWQBUk= testkey diff --git a/crates/bitwarden-ssh/resources/import/rsa_openssh_unencrypted b/crates/bitwarden-ssh/resources/import/rsa_openssh_unencrypted new file mode 100644 index 00000000..0d2692e1 --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/rsa_openssh_unencrypted @@ -0,0 +1,38 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn +NhAAAAAwEAAQAAAYEAtVIe0gnPtD6299/roT7ntZgVe+qIqIMIruJdI2xTanLGhNpBOlzg +WqokbQK+aXATcaB7iQL1SPxIWV2M4jEBQbZuimIgDQvKbJ4TZPKEe1VdsrfuIo+9pDK7cG +Kc+JiWhKjqeTRMj91/qR1fW5IWOUyE1rkwhTNkwJqtYKZLVmd4TXtQsYMMC+I0cz4krfk1 +Yqmaae/gj12h8BvE3Y+Koof4JoLsqPufH+H/bVEayv63RyAQ1/tUv9l+rwJ+svWV4X3zf3 +z40hGF43L/NGl90Vutbn7b9G/RgEdiXyLZciP3XbWbLUM+r7mG9KNuSeoixe5jok15UKqC +XXxVb5IEZ73kaubSfz9JtsqtKG/OjOq6Fbl3Ky7kjvJyGpIvesuSInlpzPXqbLUCLJJfOA +PUZ1wi8uuuRNePzQBMMhq8UtAbB2Dy16d+HlgghzQ00NxtbQMfDZBdApfxm3shIxkUcHzb +DSvriHVaGGoOkmHPAmsdMsMiekuUMe9ljdOhmdTxAAAFgF8XjBxfF4wcAAAAB3NzaC1yc2 +EAAAGBALVSHtIJz7Q+tvff66E+57WYFXvqiKiDCK7iXSNsU2pyxoTaQTpc4FqqJG0Cvmlw +E3Gge4kC9Uj8SFldjOIxAUG2bopiIA0LymyeE2TyhHtVXbK37iKPvaQyu3BinPiYloSo6n +k0TI/df6kdX1uSFjlMhNa5MIUzZMCarWCmS1ZneE17ULGDDAviNHM+JK35NWKpmmnv4I9d +ofAbxN2PiqKH+CaC7Kj7nx/h/21RGsr+t0cgENf7VL/Zfq8CfrL1leF98398+NIRheNy/z +RpfdFbrW5+2/Rv0YBHYl8i2XIj9121my1DPq+5hvSjbknqIsXuY6JNeVCqgl18VW+SBGe9 +5Grm0n8/SbbKrShvzozquhW5dysu5I7ychqSL3rLkiJ5acz16my1AiySXzgD1GdcIvLrrk +TXj80ATDIavFLQGwdg8tenfh5YIIc0NNDcbW0DHw2QXQKX8Zt7ISMZFHB82w0r64h1Whhq +DpJhzwJrHTLDInpLlDHvZY3ToZnU8QAAAAMBAAEAAAGAEL3wpRWtVTf+NnR5QgX4KJsOjs +bI0ABrVpSFo43uxNMss9sgLzagq5ZurxcUBFHKJdF63puEkPTkbEX4SnFaa5of6kylp3a5 +fd55rXY8F9Q5xtT3Wr8ZdFYP2xBr7INQUJb1MXRMBnOeBDw3UBH01d0UHexzB7WHXcZacG +Ria+u5XrQebwmJ3PYJwENSaTLrxDyjSplQy4QKfgxeWNPWaevylIG9vtue5Xd9WXdl6Szs +ONfD3mFxQZagPSIWl0kYIjS3P2ZpLe8+sakRcfci8RjEUP7U+QxqY5VaQScjyX1cSYeQLz +t+/6Tb167aNtQ8CVW3IzM2EEN1BrSbVxFkxWFLxogAHct06Kn87nPn2+PWGWOVCBp9KheO +FszWAJ0Kzjmaga2BpOJcrwjSpGopAb1YPIoRPVepVZlQ4gGwy5gXCFwykT9WTBoJfg0BMQ +r3MSNcoc97eBomIWEa34K0FuQ3rVjMv9ylfyLvDBbRqTJ5zebeOuU+yCQHZUKk8klRAAAA +wAsToNZvYWRsOMTWQom0EW1IHzoL8Cyua+uh72zZi/7enm4yHPJiu2KNgQXfB0GEEjHjbo +9peCW3gZGTV+Ee+cAqwYLlt0SMl/VJNxN3rEG7BAqPZb42Ii2XGjaxzFq0cliUGAdo6UEd +swU8d2I7m9vIZm4nDXzsWOBWgonTKBNyL0DQ6KNOGEyj8W0BTCm7Rzwy7EKzFWbIxr4lSc +vDrJ3t6kOd7jZTF58kRMT0nxR0bf43YzF/3/qSvLYhQm/OOAAAAMEA2F6Yp8SrpQDNDFxh +gi4GeywArrQO9r3EHjnBZi/bacxllSzCGXAvp7m9OKC1VD2wQP2JL1VEIZRUTuGGT6itrm +QpX8OgoxlEJrlC5W0kHumZ3MFGd33W11u37gOilmd6+VfVXBziNG2rFohweAgs8X+Sg5AA +nIfMV6ySXUlvLzMHpGeKRRnnQq9Cwn4rDkVQENLd1i4e2nWFhaPTUwVMR8YuOT766bywr3 +7vG1PQLF7hnf2c/oPHAru+XD9gJWs5AAAAwQDWiB2G23F4Tvq8FiK2mMusSjQzHupl83rm +o3BSNRCvCjaLx6bWhDPSA1edNEF7VuP6rSp+i+UfSORHwOnlgnrvtcJeoDuA72hUeYuqD/ +1C9gghdhKzGTVf/IGTX1tH3rn2Gq9TEyrJs/ITcoOyZprz7VbaD3bP/NEER+m1EHi2TS/3 +SXQEtRm+IIBwba+QLUcsrWdQyIO+1OCXywDrAw50s7tjgr/goHgXTcrSXaKcIEOlPgBZH3 +YPuVuEtRYgX3kAAAAHdGVzdGtleQECAwQ= +-----END OPENSSH PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/resources/import/rsa_openssh_unencrypted.pub b/crates/bitwarden-ssh/resources/import/rsa_openssh_unencrypted.pub new file mode 100644 index 00000000..9ec8fec5 --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/rsa_openssh_unencrypted.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC1Uh7SCc+0Prb33+uhPue1mBV76oiogwiu4l0jbFNqcsaE2kE6XOBaqiRtAr5pcBNxoHuJAvVI/EhZXYziMQFBtm6KYiANC8psnhNk8oR7VV2yt+4ij72kMrtwYpz4mJaEqOp5NEyP3X+pHV9bkhY5TITWuTCFM2TAmq1gpktWZ3hNe1CxgwwL4jRzPiSt+TViqZpp7+CPXaHwG8Tdj4qih/gmguyo+58f4f9tURrK/rdHIBDX+1S/2X6vAn6y9ZXhffN/fPjSEYXjcv80aX3RW61uftv0b9GAR2JfItlyI/ddtZstQz6vuYb0o25J6iLF7mOiTXlQqoJdfFVvkgRnveRq5tJ/P0m2yq0ob86M6roVuXcrLuSO8nIaki96y5IieWnM9epstQIskl84A9RnXCLy665E14/NAEwyGrxS0BsHYPLXp34eWCCHNDTQ3G1tAx8NkF0Cl/GbeyEjGRRwfNsNK+uIdVoYag6SYc8Cax0ywyJ6S5Qx72WN06GZ1PE= testkey diff --git a/crates/bitwarden-ssh/resources/import/rsa_pkcs8_encrypted b/crates/bitwarden-ssh/resources/import/rsa_pkcs8_encrypted new file mode 100644 index 00000000..e84d1f07 --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/rsa_pkcs8_encrypted @@ -0,0 +1,42 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +-----END ENCRYPTED PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/resources/import/rsa_pkcs8_encrypted.pub b/crates/bitwarden-ssh/resources/import/rsa_pkcs8_encrypted.pub new file mode 100644 index 00000000..f3c1b15f --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/rsa_pkcs8_encrypted.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCcHkc0xfH4w9aW41S9M/BfancSY4QPc2O4G1cRjFfK8QrLEGDA7NiHtoEML0afcurRXD3NVxuKaAns0w6EoS4CjzXUqVHTLA4SUyuapr8k0Eu2xOpbCwC3jDovhckoKloq7BvE6rC2i5wjSMadtIJKt/dqWI3HLjUMz1BxQJAU/qAbicj1SFZSjA/MubVBzcq93XOvByMtlIFu7wami3FTc37rVkGeUFHtK8ZbvG3n1aaTF79bBgSPuoq5BfcMdGr4WfQyGQzgse4v4hQ8yKYrtE0jo0kf06hEORimwOIU/W5IH1r+/xFs7qGKcPnFSZRIFv5LfMPTo8b+OsBRflosyfUumDEX97GZE7DSQl0EJzNvWeKwl7dQ8RUJTkbph2CjrxY77DFim+165Uj/WRr4uq2qMNhA2xNSD19+TA6AHdpGw4WZd37q2/n+EddlaJEH8MzpgtHNG9MiYh5ScZ+AG0QugflozJcQNc7n8N9Lpu1sRoejV5RhurHg/TYwVK8= testkey diff --git a/crates/bitwarden-ssh/resources/import/rsa_pkcs8_unencrypted b/crates/bitwarden-ssh/resources/import/rsa_pkcs8_unencrypted new file mode 100644 index 00000000..0bfe2bc5 --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/rsa_pkcs8_unencrypted @@ -0,0 +1,40 @@ +-----BEGIN PRIVATE KEY----- +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 +QjDEB37RqGZxqyIx8V2ZYwU= +-----END PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/resources/import/rsa_pkcs8_unencrypted.pub b/crates/bitwarden-ssh/resources/import/rsa_pkcs8_unencrypted.pub new file mode 100644 index 00000000..a3e04eed --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/rsa_pkcs8_unencrypted.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCn4+QiJojZ9mgc9KYJIvDWGaz4qFhf0CButg6L8zEoHKwuiN+mqcEciCCOa9BNiJmm8NTTehZvrrglGG59zIbqYtDAHjVn+vtb49xPzIv+M651Yqj08lIbR9tEIHKCq7aH8GlDm8NgG9EzJGjlL7okQym4TH1MHl+s4mUyr/qb2unlZBDixAQsphU8iCLftukWCIkmQg4CSj1Gh3WbBlZ+EX5eW0EXuAw4XsSbBTWV9CHRowVIpYqPvEYSpHsoCjEcd988p19hpiGknA0J4z7JfUlNgyT/1chb8GCTDT+2DCBRApbsIg6TOBVS+PR6emAQ3eZzUW0+3/oRM4ip0ujltQy8uU6gvYIAqx5wXGMThVpZcUgahKiSsVo/s4b84iMe4DG3W8jz4qi6yyNv0VedEzPUZ1lXd1GJFoy9uKNuSTe+1ksicAcluZN6LuNsPHcPxFCzOcmoNnVXEKAXInt+ys//5CDVasroZSAHZnDjUD4oNsLI3VIOnGxgXrkwSH0= testkey diff --git a/crates/bitwarden-ssh/resources/import/rsa_putty_openssh_unencrypted b/crates/bitwarden-ssh/resources/import/rsa_putty_openssh_unencrypted new file mode 100644 index 00000000..bbb8edfe --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/rsa_putty_openssh_unencrypted @@ -0,0 +1,30 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdz +c2gtcnNhAAAAAwEAAQAAAQEAootgTLcKjSgPLS2+RT3ZElhktL1CwIyM/+3IqEq0 +0fl/rRHBT8otklV3Ld7DOR50HVZSoV0u9qs0WOdxcjEJlJACDClmxZmFr0BQ/E2y +V5xzuMZj3Mj+fL26jTmM3ueRHZ0tU5ubSFvINIFyDGG70F7VgkpBA8zsviineMSU +t1iIPi/6feL6h7QAFUk6JdQJpPTs9Nb2+DAQ9lMS2614cxLXkfUIXA4NvHMfZGdU +dq1mBJIAWZ4PPJ6naUcu0lVYjuVEAOE4UoHxr6YlW4yyAF/I1YXBFpcHG7P0egvg +neTPli5Wzum0XDsOPivqr6z2E5k7nzyGXUaP5MjRfDDVLwAAA9D6lTpR+pU6UQAA +AAdzc2gtcnNhAAABAQCii2BMtwqNKA8tLb5FPdkSWGS0vULAjIz/7cioSrTR+X+t +EcFPyi2SVXct3sM5HnQdVlKhXS72qzRY53FyMQmUkAIMKWbFmYWvQFD8TbJXnHO4 +xmPcyP58vbqNOYze55EdnS1Tm5tIW8g0gXIMYbvQXtWCSkEDzOy+KKd4xJS3WIg+ +L/p94vqHtAAVSTol1Amk9Oz01vb4MBD2UxLbrXhzEteR9QhcDg28cx9kZ1R2rWYE +kgBZng88nqdpRy7SVViO5UQA4ThSgfGvpiVbjLIAX8jVhcEWlwcbs/R6C+Cd5M+W +LlbO6bRcOw4+K+qvrPYTmTufPIZdRo/kyNF8MNUvAAAAAwEAAQAAAQB6YVPVDq9s +DfA3RMyQF3vbOyA/kIu0q13xx1cflnfD7AT8CnUwnPloxt5fc+wqkko8WGUIRz93 +yvkzwrYAkvkymKZh/734IpmrlFIlVF5lZk8enIhNkCtDQho2AFGW9mSlFlUtMOhe +N3RqS9fRiLg+r1gzq7J9qQnKNpO48tFBpLkIqr8nZOVhEn8IASrQYBUoocClNrv6 +Pdl8ni5jqnZ/0K0nq4+41Ag1VMI4LUcRCucid8ci1HKdOmGXkvClbzuFMWv3UC0k +qDgzg/gOIgj75I7B34FYVx47UGZ6jmC7iRkHd6RiCHYkmsDSjRQHR6eRbtLPdl9w +TlG2NrwkbSlhAAAAgQCapfJLqew9aK8PKfe3FwiC9sb0itCAXPXHhD+pQ6Tl9UMZ +hmnG2g9qbowCprz3/kyix+nWL/Kx7eKAZYH2MBz6cxfqs2A+BSuxvX/hsnvF96BP +u1I47rGrd0NC78DTY2NDO4Ccirx6uN+AoCl4cC+KC00Kykww6TTEBrQsdQTk5QAA +AIEA7JwbIIMwDiQUt3EY/VU0SYvg67aOiyOYEWplSWCGdT58jnfS1H95kGVw+qXR +eSQ0VNv6LBz7XDRpfQlNXDNJRnDZuHBbk+T9ZwnynRLWuzK7VqZBPJoNoyLFSMW2 +DBhLVKIrg0MsBAnRBMDUlVDlzs2LoNLEra3dj8Zb9vMdlbEAAACBAK/db27GfXXg +OikZkIqWiFgBArtj0T4iFc7BLUJUeFtl0RP9LLjfvaxSdA1cmVYzzkgmuc2iZLF0 +37zuPkDrfYVRiw8rSihT3D+WDt3/Tt013WCuxVQOQSW+Qtw6yZpM92DKncbvYsUy +5DNklW1+TYxyn2ltM7SaZjmF8UeoTnDfAAAAEHJzYS1rZXktMjAyNDExMTgBAgME +BQYHCAkK +-----END OPENSSH PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/resources/import/rsa_putty_pkcs1_unencrypted b/crates/bitwarden-ssh/resources/import/rsa_putty_pkcs1_unencrypted new file mode 100644 index 00000000..e5bedfbd --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/rsa_putty_pkcs1_unencrypted @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAootgTLcKjSgPLS2+RT3ZElhktL1CwIyM/+3IqEq00fl/rRHB +T8otklV3Ld7DOR50HVZSoV0u9qs0WOdxcjEJlJACDClmxZmFr0BQ/E2yV5xzuMZj +3Mj+fL26jTmM3ueRHZ0tU5ubSFvINIFyDGG70F7VgkpBA8zsviineMSUt1iIPi/6 +feL6h7QAFUk6JdQJpPTs9Nb2+DAQ9lMS2614cxLXkfUIXA4NvHMfZGdUdq1mBJIA +WZ4PPJ6naUcu0lVYjuVEAOE4UoHxr6YlW4yyAF/I1YXBFpcHG7P0egvgneTPli5W +zum0XDsOPivqr6z2E5k7nzyGXUaP5MjRfDDVLwIDAQABAoIBAHphU9UOr2wN8DdE +zJAXe9s7ID+Qi7SrXfHHVx+Wd8PsBPwKdTCc+WjG3l9z7CqSSjxYZQhHP3fK+TPC +tgCS+TKYpmH/vfgimauUUiVUXmVmTx6ciE2QK0NCGjYAUZb2ZKUWVS0w6F43dGpL +19GIuD6vWDOrsn2pCco2k7jy0UGkuQiqvydk5WESfwgBKtBgFSihwKU2u/o92Xye +LmOqdn/QrSerj7jUCDVUwjgtRxEK5yJ3xyLUcp06YZeS8KVvO4Uxa/dQLSSoODOD ++A4iCPvkjsHfgVhXHjtQZnqOYLuJGQd3pGIIdiSawNKNFAdHp5Fu0s92X3BOUbY2 +vCRtKWECgYEA7JwbIIMwDiQUt3EY/VU0SYvg67aOiyOYEWplSWCGdT58jnfS1H95 +kGVw+qXReSQ0VNv6LBz7XDRpfQlNXDNJRnDZuHBbk+T9ZwnynRLWuzK7VqZBPJoN +oyLFSMW2DBhLVKIrg0MsBAnRBMDUlVDlzs2LoNLEra3dj8Zb9vMdlbECgYEAr91v +bsZ9deA6KRmQipaIWAECu2PRPiIVzsEtQlR4W2XRE/0suN+9rFJ0DVyZVjPOSCa5 +zaJksXTfvO4+QOt9hVGLDytKKFPcP5YO3f9O3TXdYK7FVA5BJb5C3DrJmkz3YMqd +xu9ixTLkM2SVbX5NjHKfaW0ztJpmOYXxR6hOcN8CgYASLZAb+Fg5zeXVjhfYZrJk +sB1wno7m+64UMHNlpsfNvCY/n88Pyldhk5mReCnWv8RRfLEEsJlTJSexloReAAay +JbtkYyV2AFLDls0P6kGbEjO4XX+Hk2JW1TYI+D+bQEaRUwA6zm9URBjN3661Zgix +0bLXgTnhCgmKoTexik4MkQKBgEZR14XGzlG81+SpOTeBG4F83ffJ4NfkTy395jf4 +iKubGa/Rcvl1VWU7DvZsyU9Dpb8J5Q+JWJPwdKoZ5UCWKPmO8nidSai4Z3/xY352 +4LTpHdzT5UlH7drGqftfck9FaUEFo3LxM2BAiijWlj1S3HVFO+Ku7JbRigCEQ0bw +0HSnAoGBAJql8kup7D1orw8p97cXCIL2xvSK0IBc9ceEP6lDpOX1QxmGacbaD2pu +jAKmvPf+TKLH6dYv8rHt4oBlgfYwHPpzF+qzYD4FK7G9f+Gye8X3oE+7Ujjusat3 +Q0LvwNNjY0M7gJyKvHq434CgKXhwL4oLTQrKTDDpNMQGtCx1BOTl +-----END RSA PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/resources/import/wrong_label b/crates/bitwarden-ssh/resources/import/wrong_label new file mode 100644 index 00000000..93833b27 --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/wrong_label @@ -0,0 +1,8 @@ +-----BEGIN WRONG LABEL KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS +1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQRQzzQ8nQEouF1FMSHkPx1nejNCzF7g +Yb8MHXLdBFM0uJkWs0vzgLJkttts2eDv3SHJqIH6qHpkLtEvgMXE5WcaAAAAoOO1BebjtQ +XmAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFDPNDydASi4XUUx +IeQ/HWd6M0LMXuBhvwwdct0EUzS4mRazS/OAsmS222zZ4O/dIcmogfqoemQu0S+AxcTlZx +oAAAAhAKnIXk6H0Hs3HblklaZ6UmEjjdE/0t7EdYixpMmtpJ4eAAAAB3Rlc3RrZXk= +-----END WRONG LABEL KEY----- diff --git a/crates/bitwarden-ssh/src/error.rs b/crates/bitwarden-ssh/src/error.rs index f4c0409a..62883f54 100644 --- a/crates/bitwarden-ssh/src/error.rs +++ b/crates/bitwarden-ssh/src/error.rs @@ -6,6 +6,26 @@ use thiserror::Error; pub enum KeyGenerationError { #[error("Failed to generate key: {0}")] KeyGenerationError(ssh_key::Error), - #[error("Failed to convert key: {0}")] - KeyConversionError(ssh_key::Error), + #[error("Failed to convert key")] + KeyConversionError, +} + +#[bitwarden_error(flat)] +#[derive(Error, Debug, PartialEq)] +pub enum SshKeyImportError { + #[error("Failed to parse key")] + ParsingError, + #[error("Password required")] + PasswordRequired, + #[error("Wrong password")] + WrongPassword, + #[error("Unsupported key type")] + UnsupportedKeyType, +} + +#[bitwarden_error(flat)] +#[derive(Error, Debug, PartialEq)] +pub enum SshKeyExportError { + #[error("Failed to convert key")] + KeyConversionError, } diff --git a/crates/bitwarden-ssh/src/generator.rs b/crates/bitwarden-ssh/src/generator.rs new file mode 100644 index 00000000..51b8f9f7 --- /dev/null +++ b/crates/bitwarden-ssh/src/generator.rs @@ -0,0 +1,87 @@ +use serde::{Deserialize, Serialize}; +use ssh_key::{rand_core::CryptoRngCore, Algorithm}; +#[cfg(feature = "wasm")] +use tsify_next::Tsify; + +use crate::{error, error::KeyGenerationError, SshKey}; + +#[derive(Serialize, Deserialize)] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] +pub enum KeyAlgorithm { + Ed25519, + Rsa3072, + Rsa4096, +} + +/** + * Generate a new SSH key pair, for the provided key algorithm, returning + * an [SshKey] struct containing the private key, public key, and key fingerprint, + * with the private key in OpenSSH format. + */ +pub fn generate_sshkey(key_algorithm: KeyAlgorithm) -> Result { + let rng = rand::thread_rng(); + generate_sshkey_internal(key_algorithm, rng) +} + +fn generate_sshkey_internal( + key_algorithm: KeyAlgorithm, + mut rng: impl CryptoRngCore, +) -> Result { + let private_key = match key_algorithm { + KeyAlgorithm::Ed25519 => ssh_key::PrivateKey::random(&mut rng, Algorithm::Ed25519) + .map_err(KeyGenerationError::KeyGenerationError), + KeyAlgorithm::Rsa3072 => create_rsa_key(&mut rng, 3072), + KeyAlgorithm::Rsa4096 => create_rsa_key(&mut rng, 4096), + }?; + + private_key + .try_into() + .map_err(|_| KeyGenerationError::KeyConversionError) +} + +fn create_rsa_key( + mut rng: impl CryptoRngCore, + bits: usize, +) -> Result { + let rsa_keypair = ssh_key::private::RsaKeypair::random(&mut rng, bits) + .map_err(KeyGenerationError::KeyGenerationError)?; + let private_key = + ssh_key::PrivateKey::new(ssh_key::private::KeypairData::from(rsa_keypair), "") + .map_err(KeyGenerationError::KeyGenerationError)?; + Ok(private_key) +} + +#[cfg(test)] +mod tests { + use rand::SeedableRng; + + use super::KeyAlgorithm; + use crate::generator::generate_sshkey_internal; + + #[test] + fn generate_ssh_key_ed25519() { + let rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]); + let key_algorithm = KeyAlgorithm::Ed25519; + let result = generate_sshkey_internal(key_algorithm, rng); + let target = include_str!("../resources/generator/ed25519_key").replace("\r\n", "\n"); + assert_eq!(result.unwrap().private_key, target); + } + + #[test] + fn generate_ssh_key_rsa3072() { + let rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]); + let key_algorithm = KeyAlgorithm::Rsa3072; + let result = generate_sshkey_internal(key_algorithm, rng); + let target = include_str!("../resources/generator/rsa3072_key").replace("\r\n", "\n"); + assert_eq!(result.unwrap().private_key, target); + } + + #[test] + fn generate_ssh_key_rsa4096() { + let rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]); + let key_algorithm = KeyAlgorithm::Rsa4096; + let result = generate_sshkey_internal(key_algorithm, rng); + let target = include_str!("../resources/generator/rsa4096_key").replace("\r\n", "\n"); + assert_eq!(result.unwrap().private_key, target); + } +} diff --git a/crates/bitwarden-ssh/src/import.rs b/crates/bitwarden-ssh/src/import.rs new file mode 100644 index 00000000..30e92de0 --- /dev/null +++ b/crates/bitwarden-ssh/src/import.rs @@ -0,0 +1,227 @@ +use ed25519; +use pem_rfc7468::PemLabel; +use pkcs8::{der::Decode, pkcs5, DecodePrivateKey, PrivateKeyInfo, SecretDocument}; +use ssh_key::private::{Ed25519Keypair, RsaKeypair}; + +use crate::{error::SshKeyImportError, SshKey}; + +/// Import a PKCS8 or OpenSSH encoded private key, and returns a decoded [SshKey], +/// with the public key and fingerprint, and the private key in OpenSSH format. +/// A password can be provided for encrypted keys. +/// # Returns +/// - [SshKeyImportError::PasswordRequired] if the key is encrypted and no password is provided +/// - [SshKeyImportError::WrongPassword] if the password provided is incorrect +/// - [SshKeyImportError::UnsupportedKeyType] if the key type is not supported +/// - [SshKeyImportError::ParsingError] if the key is otherwise malformed and cannot be parsed +pub fn import_key( + encoded_key: String, + password: Option, +) -> Result { + let label = pem_rfc7468::decode_label(encoded_key.as_bytes()) + .map_err(|_| SshKeyImportError::ParsingError)?; + + match label { + pkcs8::PrivateKeyInfo::PEM_LABEL => import_pkcs8_key(encoded_key, None), + pkcs8::EncryptedPrivateKeyInfo::PEM_LABEL => import_pkcs8_key( + encoded_key, + Some(password.ok_or(SshKeyImportError::PasswordRequired)?), + ), + ssh_key::PrivateKey::PEM_LABEL => import_openssh_key(encoded_key, password), + _ => Err(SshKeyImportError::UnsupportedKeyType), + } +} + +fn import_pkcs8_key( + encoded_key: String, + password: Option, +) -> Result { + let doc = if let Some(password) = password { + SecretDocument::from_pkcs8_encrypted_pem(&encoded_key, password.as_bytes()).map_err( + |err| match err { + pkcs8::Error::EncryptedPrivateKey(pkcs5::Error::DecryptFailed) => { + SshKeyImportError::WrongPassword + } + _ => SshKeyImportError::ParsingError, + }, + )? + } else { + SecretDocument::from_pkcs8_pem(&encoded_key).map_err(|_| SshKeyImportError::ParsingError)? + }; + + let private_key_info = + PrivateKeyInfo::from_der(doc.as_bytes()).map_err(|_| SshKeyImportError::ParsingError)?; + + let private_key = match private_key_info.algorithm.oid { + ed25519::pkcs8::ALGORITHM_OID => { + let private_key: ed25519::KeypairBytes = private_key_info + .try_into() + .map_err(|_| SshKeyImportError::ParsingError)?; + + ssh_key::private::PrivateKey::from(Ed25519Keypair::from(&private_key.secret_key.into())) + } + rsa::pkcs1::ALGORITHM_OID => { + let private_key: rsa::RsaPrivateKey = private_key_info + .try_into() + .map_err(|_| SshKeyImportError::ParsingError)?; + + ssh_key::private::PrivateKey::from( + RsaKeypair::try_from(private_key).map_err(|_| SshKeyImportError::ParsingError)?, + ) + } + _ => return Err(SshKeyImportError::UnsupportedKeyType), + }; + + private_key + .try_into() + .map_err(|_| SshKeyImportError::ParsingError) +} + +fn import_openssh_key( + encoded_key: String, + password: Option, +) -> Result { + let private_key = + ssh_key::private::PrivateKey::from_openssh(&encoded_key).map_err(|err| match err { + ssh_key::Error::AlgorithmUnknown | ssh_key::Error::AlgorithmUnsupported { .. } => { + SshKeyImportError::UnsupportedKeyType + } + _ => SshKeyImportError::ParsingError, + })?; + + let private_key = if private_key.is_encrypted() { + let password = password.ok_or(SshKeyImportError::PasswordRequired)?; + private_key + .decrypt(password.as_bytes()) + .map_err(|_| SshKeyImportError::WrongPassword)? + } else { + private_key + }; + + private_key + .try_into() + .map_err(|_| SshKeyImportError::ParsingError) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn import_key_ed25519_openssh_unencrypted() { + let private_key = include_str!("../resources/import/ed25519_openssh_unencrypted"); + let public_key = include_str!("../resources/import/ed25519_openssh_unencrypted.pub").trim(); + let result = import_key(private_key.to_string(), Some("".to_string())).unwrap(); + assert_eq!(result.public_key, public_key); + } + + #[test] + fn import_key_ed25519_openssh_encrypted() { + let private_key = include_str!("../resources/import/ed25519_openssh_encrypted"); + let public_key = include_str!("../resources/import/ed25519_openssh_encrypted.pub").trim(); + let result = import_key(private_key.to_string(), Some("password".to_string())).unwrap(); + assert_eq!(result.public_key, public_key); + } + + #[test] + fn import_key_rsa_openssh_unencrypted() { + let private_key = include_str!("../resources/import/rsa_openssh_unencrypted"); + let public_key = include_str!("../resources/import/rsa_openssh_unencrypted.pub").trim(); + let result = import_key(private_key.to_string(), Some("".to_string())).unwrap(); + assert_eq!(result.public_key, public_key); + } + + #[test] + fn import_key_rsa_openssh_encrypted() { + let private_key = include_str!("../resources/import/rsa_openssh_encrypted"); + let public_key = include_str!("../resources/import/rsa_openssh_encrypted.pub").trim(); + let result = import_key(private_key.to_string(), Some("password".to_string())).unwrap(); + assert_eq!(result.public_key, public_key); + } + + #[test] + fn import_key_ed25519_pkcs8_unencrypted() { + let private_key = include_str!("../resources/import/ed25519_pkcs8_unencrypted"); + let public_key = include_str!("../resources/import/ed25519_pkcs8_unencrypted.pub") + .replace("testkey", ""); + let public_key = public_key.trim(); + let result = import_key(private_key.to_string(), Some("".to_string())).unwrap(); + assert_eq!(result.public_key, public_key); + } + + #[test] + fn import_key_rsa_pkcs8_unencrypted() { + let private_key = include_str!("../resources/import/rsa_pkcs8_unencrypted"); + // for whatever reason pkcs8 + rsa does not include the comment in the public key + let public_key = + include_str!("../resources/import/rsa_pkcs8_unencrypted.pub").replace("testkey", ""); + let public_key = public_key.trim(); + let result = import_key(private_key.to_string(), Some("".to_string())).unwrap(); + assert_eq!(result.public_key, public_key); + } + + #[test] + fn import_key_rsa_pkcs8_encrypted() { + let private_key = include_str!("../resources/import/rsa_pkcs8_encrypted"); + let public_key = + include_str!("../resources/import/rsa_pkcs8_encrypted.pub").replace("testkey", ""); + let public_key = public_key.trim(); + let result = import_key(private_key.to_string(), Some("password".to_string())).unwrap(); + assert_eq!(result.public_key, public_key); + } + + #[test] + fn import_key_ed25519_openssh_encrypted_wrong_password() { + let private_key = include_str!("../resources/import/ed25519_openssh_encrypted"); + let result = import_key(private_key.to_string(), Some("wrongpassword".to_string())); + assert_eq!(result.unwrap_err(), SshKeyImportError::WrongPassword); + } + + #[test] + fn import_non_key_error() { + let result = import_key("not a key".to_string(), Some("".to_string())); + assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError); + } + + #[test] + fn import_wrong_label_error() { + let private_key = include_str!("../resources/import/wrong_label"); + let result = import_key(private_key.to_string(), Some("".to_string())); + assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType); + } + + #[test] + fn import_ecdsa_error() { + let private_key = include_str!("../resources/import/ecdsa_openssh_unencrypted"); + let result = import_key(private_key.to_string(), Some("".to_string())); + assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType); + } + + // Putty-exported keys should be supported, but are not due to a parser incompatibility. + // Should this test start failing, please change it to expect a correct key, and + // make sure the documentation support for putty-exported keys this is updated. + // https://bitwarden.atlassian.net/browse/PM-14989 + #[test] + fn import_key_ed25519_putty() { + let private_key = include_str!("../resources/import/ed25519_putty_openssh_unencrypted"); + let result = import_key(private_key.to_string(), Some("".to_string())); + assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError); + } + + // Putty-exported keys should be supported, but are not due to a parser incompatibility. + // Should this test start failing, please change it to expect a correct key, and + // make sure the documentation support for putty-exported keys this is updated. + // https://bitwarden.atlassian.net/browse/PM-14989 + #[test] + fn import_key_rsa_openssh_putty() { + let private_key = include_str!("../resources/import/rsa_putty_openssh_unencrypted"); + let result = import_key(private_key.to_string(), Some("".to_string())); + assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError); + } + + #[test] + fn import_key_rsa_pkcs8_putty() { + let private_key = include_str!("../resources/import/rsa_putty_pkcs1_unencrypted"); + let result = import_key(private_key.to_string(), Some("".to_string())); + assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType); + } +} diff --git a/crates/bitwarden-ssh/src/lib.rs b/crates/bitwarden-ssh/src/lib.rs index 7736ee33..1a972efe 100644 --- a/crates/bitwarden-ssh/src/lib.rs +++ b/crates/bitwarden-ssh/src/lib.rs @@ -1,100 +1,35 @@ -use error::KeyGenerationError; -use ssh_key::{rand_core::CryptoRngCore, Algorithm, HashAlg, LineEnding}; - pub mod error; +pub mod generator; +pub mod import; +use error::SshKeyExportError; +use pkcs8::LineEnding; use serde::{Deserialize, Serialize}; +use ssh_key::{HashAlg, PrivateKey}; #[cfg(feature = "wasm")] use tsify_next::Tsify; -#[derive(Serialize, Deserialize)] -#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] -pub enum KeyAlgorithm { - Ed25519, - Rsa3072, - Rsa4096, -} - -pub fn generate_sshkey( - key_algorithm: KeyAlgorithm, -) -> Result { - let rng = rand::thread_rng(); - generate_sshkey_internal(key_algorithm, rng) -} - -fn generate_sshkey_internal( - key_algorithm: KeyAlgorithm, - mut rng: impl CryptoRngCore, -) -> Result { - let key = match key_algorithm { - KeyAlgorithm::Ed25519 => ssh_key::PrivateKey::random(&mut rng, Algorithm::Ed25519), - KeyAlgorithm::Rsa3072 | KeyAlgorithm::Rsa4096 => { - let bits = match key_algorithm { - KeyAlgorithm::Rsa3072 => 3072, - KeyAlgorithm::Rsa4096 => 4096, - _ => unreachable!(), - }; - - let rsa_keypair = ssh_key::private::RsaKeypair::random(&mut rng, bits) - .map_err(KeyGenerationError::KeyGenerationError)?; - - let private_key = - ssh_key::PrivateKey::new(ssh_key::private::KeypairData::from(rsa_keypair), "") - .map_err(KeyGenerationError::KeyGenerationError)?; - Ok(private_key) - } - } - .map_err(KeyGenerationError::KeyGenerationError)?; - - let private_key_openssh = key - .to_openssh(LineEnding::LF) - .map_err(KeyGenerationError::KeyConversionError)?; - Ok(GenerateSshKeyResult { - private_key: private_key_openssh.to_string(), - public_key: key.public_key().to_string(), - key_fingerprint: key.fingerprint(HashAlg::Sha256).to_string(), - }) -} - -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] #[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] -pub struct GenerateSshKeyResult { +pub struct SshKey { + /// The private key in OpenSSH format pub private_key: String, pub public_key: String, pub key_fingerprint: String, } -#[cfg(test)] -mod tests { - use rand::SeedableRng; +impl TryFrom for SshKey { + type Error = SshKeyExportError; - use super::KeyAlgorithm; - use crate::generate_sshkey_internal; - - #[test] - fn generate_ssh_key_ed25519() { - let rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]); - let key_algorithm = KeyAlgorithm::Ed25519; - let result = generate_sshkey_internal(key_algorithm, rng); - let target = include_str!("../tests/ed25519_key").replace("\r\n", "\n"); - assert_eq!(result.unwrap().private_key, target); - } - - #[test] - fn generate_ssh_key_rsa3072() { - let rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]); - let key_algorithm = KeyAlgorithm::Rsa3072; - let result = generate_sshkey_internal(key_algorithm, rng); - let target = include_str!("../tests/rsa3072_key").replace("\r\n", "\n"); - assert_eq!(result.unwrap().private_key, target); - } + fn try_from(value: PrivateKey) -> Result { + let private_key_openssh = value + .to_openssh(LineEnding::LF) + .map_err(|_| SshKeyExportError::KeyConversionError)?; - #[test] - fn generate_ssh_key_rsa4096() { - let rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]); - let key_algorithm = KeyAlgorithm::Rsa4096; - let result = generate_sshkey_internal(key_algorithm, rng); - let target = include_str!("../tests/rsa4096_key").replace("\r\n", "\n"); - assert_eq!(result.unwrap().private_key, target); + Ok(SshKey { + private_key: private_key_openssh.to_string(), + public_key: value.public_key().to_string(), + key_fingerprint: value.fingerprint(HashAlg::Sha256).to_string(), + }) } } diff --git a/crates/bitwarden-wasm-internal/src/ssh.rs b/crates/bitwarden-wasm-internal/src/ssh.rs index e3ac77bf..1255a573 100644 --- a/crates/bitwarden-wasm-internal/src/ssh.rs +++ b/crates/bitwarden-wasm-internal/src/ssh.rs @@ -1,8 +1,37 @@ use wasm_bindgen::prelude::*; +/// Generate a new SSH key pair +/// +/// # Arguments +/// - `key_algorithm` - The algorithm to use for the key pair +/// +/// # Returns +/// - `Ok(SshKey)` if the key was successfully generated +/// - `Err(KeyGenerationError)` if the key could not be generated #[wasm_bindgen] pub fn generate_ssh_key( - key_algorithm: bitwarden_ssh::KeyAlgorithm, -) -> Result { - bitwarden_ssh::generate_sshkey(key_algorithm) + key_algorithm: bitwarden_ssh::generator::KeyAlgorithm, +) -> Result { + bitwarden_ssh::generator::generate_sshkey(key_algorithm) +} + +/// Convert a PCKS8 or OpenSSH encrypted or unencrypted private key +/// to an OpenSSH private key with public key and fingerprint +/// +/// # Arguments +/// - `imported_key` - The private key to convert +/// - `password` - The password to use for decrypting the key +/// +/// # Returns +/// - `Ok(SshKey)` if the key was successfully coneverted +/// - `Err(PasswordRequired)` if the key is encrypted and no password was provided +/// - `Err(WrongPassword)` if the password provided is incorrect +/// - `Err(ParsingError)` if the key could not be parsed +/// - `Err(UnsupportedKeyType)` if the key type is not supported +#[wasm_bindgen] +pub fn import_ssh_key( + imported_key: &str, + password: Option, +) -> Result { + bitwarden_ssh::import::import_key(imported_key.to_string(), password) }