From 5e6542d08e8e83117ecf7dc30492299889a2c3db Mon Sep 17 00:00:00 2001 From: Joseph Montanaro Date: Mon, 1 Jul 2024 06:38:46 -0400 Subject: [PATCH] initial ssh key model and creation ui --- src-tauri/src/app.rs | 1 + .../src/credentials/fixtures/ssh_ed25519_enc | 8 ++ .../credentials/fixtures/ssh_ed25519_enc.pub | 1 + .../credentials/fixtures/ssh_ed25519_plain | 7 + .../fixtures/ssh_ed25519_plain.json | 1 + .../fixtures/ssh_ed25519_plain.pub | 1 + .../src/credentials/fixtures/ssh_rsa_enc | 39 ++++++ .../src/credentials/fixtures/ssh_rsa_enc.pub | 1 + .../src/credentials/fixtures/ssh_rsa_plain | 38 ++++++ .../credentials/fixtures/ssh_rsa_plain.pub | 1 + src-tauri/src/credentials/mod.rs | 7 +- src-tauri/src/credentials/ssh.rs | 120 ++++++++++++++++++ src-tauri/src/errors.rs | 12 ++ src-tauri/src/ipc.rs | 9 +- src/ui/FileInput.svelte | 39 ++++++ src/ui/PassphraseInput.svelte | 4 +- src/views/ManageCredentials.svelte | 77 ++++++++--- src/views/credentials/AwsCredential.svelte | 15 ++- src/views/credentials/NewSshKey.svelte | 92 ++++++++++++++ src/views/credentials/SshKey.svelte | 110 ++++++++++++++++ 20 files changed, 555 insertions(+), 28 deletions(-) create mode 100644 src-tauri/src/credentials/fixtures/ssh_ed25519_enc create mode 100644 src-tauri/src/credentials/fixtures/ssh_ed25519_enc.pub create mode 100644 src-tauri/src/credentials/fixtures/ssh_ed25519_plain create mode 100644 src-tauri/src/credentials/fixtures/ssh_ed25519_plain.json create mode 100644 src-tauri/src/credentials/fixtures/ssh_ed25519_plain.pub create mode 100644 src-tauri/src/credentials/fixtures/ssh_rsa_enc create mode 100644 src-tauri/src/credentials/fixtures/ssh_rsa_enc.pub create mode 100644 src-tauri/src/credentials/fixtures/ssh_rsa_plain create mode 100644 src-tauri/src/credentials/fixtures/ssh_rsa_plain.pub create mode 100644 src-tauri/src/credentials/ssh.rs create mode 100644 src/ui/FileInput.svelte create mode 100644 src/views/credentials/NewSshKey.svelte create mode 100644 src/views/credentials/SshKey.svelte diff --git a/src-tauri/src/app.rs b/src-tauri/src/app.rs index f4c4151..c9d1205 100644 --- a/src-tauri/src/app.rs +++ b/src-tauri/src/app.rs @@ -56,6 +56,7 @@ pub fn run() -> tauri::Result<()> { ipc::save_credential, ipc::delete_credential, ipc::list_credentials, + ipc::sshkey_from_file, ipc::get_config, ipc::save_config, ipc::launch_terminal, diff --git a/src-tauri/src/credentials/fixtures/ssh_ed25519_enc b/src-tauri/src/credentials/fixtures/ssh_ed25519_enc new file mode 100644 index 0000000..1b6e994 --- /dev/null +++ b/src-tauri/src/credentials/fixtures/ssh_ed25519_enc @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAWtYanP1 +TBKT8lBL4IzKpYAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIASRxkrR1+nCDZiZ +N2d8Muvl+zW8undCJVCo+qVMAjkjAAAAkI021XFPzB9VnO8uGAQ8f3bwP/ki5fDVuWD7Fc +crN+yfT8Ugjhc7IL2dIt/xj9iJIa9fJDw0pg1Y8issqp9C8HVhasyWpf2iwJIalUHTOekn +WdoxA+/OQBstRBKSv43sI801+9OC8dXCMNM2QzpiGNs0QxdLJpcJQhHEvqq/yDIODF0p7M +h3e9eYGVPOR0CjlQ== +-----END OPENSSH PRIVATE KEY----- diff --git a/src-tauri/src/credentials/fixtures/ssh_ed25519_enc.pub b/src-tauri/src/credentials/fixtures/ssh_ed25519_enc.pub new file mode 100644 index 0000000..084aca8 --- /dev/null +++ b/src-tauri/src/credentials/fixtures/ssh_ed25519_enc.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIASRxkrR1+nCDZiZN2d8Muvl+zW8undCJVCo+qVMAjkj hello world diff --git a/src-tauri/src/credentials/fixtures/ssh_ed25519_plain b/src-tauri/src/credentials/fixtures/ssh_ed25519_plain new file mode 100644 index 0000000..e01c193 --- /dev/null +++ b/src-tauri/src/credentials/fixtures/ssh_ed25519_plain @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACC7sFhGkIp/SBnKab5Q6UZY/W9R0k/+ztZ4Vm1D4d1r8gAAAJAwEcgHMBHI +BwAAAAtzc2gtZWQyNTUxOQAAACC7sFhGkIp/SBnKab5Q6UZY/W9R0k/+ztZ4Vm1D4d1r8g +AAAEB9VXgjePmpl6Q3Y1t2a4DZhsdRf+183vWAJWAonDOneLuwWEaQin9IGcppvlDpRlj9 +b1HST/7O1nhWbUPh3WvyAAAAC2hlbGxvIHdvcmxkAQI= +-----END OPENSSH PRIVATE KEY----- diff --git a/src-tauri/src/credentials/fixtures/ssh_ed25519_plain.json b/src-tauri/src/credentials/fixtures/ssh_ed25519_plain.json new file mode 100644 index 0000000..161c831 --- /dev/null +++ b/src-tauri/src/credentials/fixtures/ssh_ed25519_plain.json @@ -0,0 +1 @@ +{"path":"./src/credentials/fixtures/ssh_ed25519_plain","algorithm":"ssh-ed25519","comment":"hello world","public_key":"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILuwWEaQin9IGcppvlDpRlj9b1HST/7O1nhWbUPh3Wvy hello world","private_key":"-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\nQyNTUxOQAAACC7sFhGkIp/SBnKab5Q6UZY/W9R0k/+ztZ4Vm1D4d1r8gAAAJAwEcgHMBHI\nBwAAAAtzc2gtZWQyNTUxOQAAACC7sFhGkIp/SBnKab5Q6UZY/W9R0k/+ztZ4Vm1D4d1r8g\nAAAEB9VXgjePmpl6Q3Y1t2a4DZhsdRf+183vWAJWAonDOneLuwWEaQin9IGcppvlDpRlj9\nb1HST/7O1nhWbUPh3WvyAAAAC2hlbGxvIHdvcmxkAQI=\n-----END OPENSSH PRIVATE KEY-----\n"} \ No newline at end of file diff --git a/src-tauri/src/credentials/fixtures/ssh_ed25519_plain.pub b/src-tauri/src/credentials/fixtures/ssh_ed25519_plain.pub new file mode 100644 index 0000000..c11665c --- /dev/null +++ b/src-tauri/src/credentials/fixtures/ssh_ed25519_plain.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILuwWEaQin9IGcppvlDpRlj9b1HST/7O1nhWbUPh3Wvy hello world diff --git a/src-tauri/src/credentials/fixtures/ssh_rsa_enc b/src-tauri/src/credentials/fixtures/ssh_rsa_enc new file mode 100644 index 0000000..9023175 --- /dev/null +++ b/src-tauri/src/credentials/fixtures/ssh_rsa_enc @@ -0,0 +1,39 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +bnzyMMiKRuZb16OucNli84FIAuI= +-----END OPENSSH PRIVATE KEY----- diff --git a/src-tauri/src/credentials/fixtures/ssh_rsa_enc.pub b/src-tauri/src/credentials/fixtures/ssh_rsa_enc.pub new file mode 100644 index 0000000..9a8bcc8 --- /dev/null +++ b/src-tauri/src/credentials/fixtures/ssh_rsa_enc.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCwIeD+SUIx511M/JztbfUkEi8OhnF3ELsGYjbRq/ABy0x8tYlk6ZjlOFg2kSMAEpoTNOVJp+9eDsQRXZfgmQOKz7ugrS/l1XT38/8SKpe1n3XYti3Nkh/xpbv9y1XXd3mkGr1GUorviywNqWNw/Ox5OH7tasHAztBBrpecu4gL7GwXkXcRFD8cTQNVSNJzdz0B4/ZDRjgRtzOdn0s/yNH+z3YciHjBNeLmANnSMPEaOtjgQV0akjo5jRCOkEP2MKm3uxMQknyopGRVCW4aJyula28G/uV2TjyKyF6l3kCK7Y7FSb50n7IxwaLMldoANdsAmp37LGIoM6VM/Muf+xcxWQZfMzXG3a+7Uqgs1cMnGYxJbCpEBPGlRNghdfkVlUSSpEiJVLN8eMH4G0Z6BflszCYUbN9RevcWdARpR7Ec6AsOJ3squiORWvEemp+dBXF+Hw7XA0H0cAhVafiNj1y7gXlgWgv4hTeleJMynRXx+Mo1gr42EkEPBlaFM/gBYC8= hello world diff --git a/src-tauri/src/credentials/fixtures/ssh_rsa_plain b/src-tauri/src/credentials/fixtures/ssh_rsa_plain new file mode 100644 index 0000000..bde1a11 --- /dev/null +++ b/src-tauri/src/credentials/fixtures/ssh_rsa_plain @@ -0,0 +1,38 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +PkAC2GY5AmhSkAAAALaGVsbG8gd29ybGQ= +-----END OPENSSH PRIVATE KEY----- diff --git a/src-tauri/src/credentials/fixtures/ssh_rsa_plain.pub b/src-tauri/src/credentials/fixtures/ssh_rsa_plain.pub new file mode 100644 index 0000000..a4883bb --- /dev/null +++ b/src-tauri/src/credentials/fixtures/ssh_rsa_plain.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDEq85taUAJEuutUnczQB4w6/PclDO3nI40PXr74ZqfMJk0giV32YBzRrSNT7BgTQItqCblYkY15M4ZhRql0w39IAfembBK5MLwCCPf/DyN3mLwdIMcH4kDBnyD3M19nO6GQ8k8UpH2tQR/U2RqN8hAmJNP/eWIK13XaWzdxEIcOeKJR2jP1mUM5YXjWj9zmwFWUKpGmr3vxph+Vdr+x9QLQ4hlTtMgXRhSjYgZJ8QsviEMz29JqQYZrW5qy/F2jX7FL/nLhb5ge5QUlhVmKSAWh1FkwcHR+9TDVp1EJKfxnQQ6vN7lBXPfxPx/LCcYqnZSj6ImwN1VMNxwXDCQHhveiP5cw1yuWriCbR5/lw2+0KD36YM8/HMjofEyNSjVzDwArrmBZdZ3yvZL1pcpEyJk2XG1xJHQrq9TqtItA3VrLkN1RQLoRIgRfuu5YszfXfWWgsHpukctWrm4PbKGLn6jgOj9IN6TaMq8u8XJXCM6Ut5d/l6Ry1kBnQC1KccMQwU= hello world diff --git a/src-tauri/src/credentials/mod.rs b/src-tauri/src/credentials/mod.rs index 4fcef3a..b412228 100644 --- a/src-tauri/src/credentials/mod.rs +++ b/src-tauri/src/credentials/mod.rs @@ -14,14 +14,17 @@ use crate::errors::*; mod aws; pub use aws::{AwsBaseCredential, AwsSessionCredential}; +mod crypto; +pub use crypto::Crypto; + mod record; pub use record::CredentialRecord; mod session; pub use session::AppSession; -mod crypto; -pub use crypto::Crypto; +mod ssh; +pub use ssh::SshKey; #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] diff --git a/src-tauri/src/credentials/ssh.rs b/src-tauri/src/credentials/ssh.rs new file mode 100644 index 0000000..9cc8ca8 --- /dev/null +++ b/src-tauri/src/credentials/ssh.rs @@ -0,0 +1,120 @@ +use std::path::PathBuf; + +use serde::{ + Deserialize, + Serialize, + Serializer, + ser::Error, + ser::SerializeStruct, +}; +use ssh_key::{ + Algorithm, + LineEnding, + private::PrivateKey, + public::PublicKey, +}; + +use crate::errors::*; + + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct SshKey { + pub path: String, + pub algorithm: Algorithm, + pub comment: String, + pub public_key: PublicKey, + pub private_key: PrivateKey, +} + +impl SshKey { + pub fn from_file(path: String, passphrase: &str) -> Result { + let mut privkey = PrivateKey::read_openssh_file(path.as_ref())?; + if privkey.is_encrypted() { + privkey = privkey.decrypt(passphrase) + .map_err(|_| LoadSshKeyError::InvalidPassphrase)?; + } + + Ok(SshKey { + path, + algorithm: privkey.algorithm(), + comment: privkey.comment().into(), + public_key: privkey.public_key().clone(), + private_key: privkey, + }) + } +} + + +impl Serialize for SshKey { + fn serialize(&self, s: S) -> Result { + let mut key = s.serialize_struct("SshKey", 5)?; + key.serialize_field("path", &self.path)?; + key.serialize_field("algorithm", self.algorithm.as_str())?; + key.serialize_field("comment", &self.comment)?; + + let pubkey_str = self.public_key.to_openssh() + .map_err(|e| S::Error::custom(format!("Failed to encode SSH public key: {e}")))?; + key.serialize_field("public_key", &pubkey_str)?; + + let privkey_str = self.private_key.to_openssh(LineEnding::LF) + .map_err(|e| S::Error::custom(format!("Failed to encode SSH private key: {e}")))?; + key.serialize_field::("private_key", privkey_str.as_ref())?; + + key.end() + } +} + + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + + fn path(name: &str) -> String { + format!("./src/credentials/fixtures/{name}") + } + + + #[test] + fn test_load_rsa_plain() { + let k = SshKey::from_file(path("ssh_rsa_plain"), "") + .expect("Failed to load SSH key"); + } + + + #[test] + fn test_load_rsa_enc() { + let k = SshKey::from_file( + path("ssh_rsa_enc"), + "correct horse battery staple", + ).expect("Failed to load SSH key"); + } + + + #[test] + fn test_load_ed25519_plain() { + let k = SshKey::from_file(path("ssh_ed25519_plain"), "") + .expect("Failed to load SSH key"); + } + + + #[test] + fn test_load_ed25519_enc() { + let k = SshKey::from_file( + path("ssh_ed25519_enc"), + "correct horse battery staple", + ).expect("Failed to load SSH key"); + } + + + #[test] + fn test_serialize() { + let expected = fs::read_to_string(path("ssh_ed25519_plain.json")).unwrap(); + + let k = SshKey::from_file(path("ssh_ed25519_plain"), "").unwrap(); + let computed = serde_json::to_string(&k) + .expect("Failed to serialize SshKey"); + + assert_eq!(expected, computed); + } +} diff --git a/src-tauri/src/errors.rs b/src-tauri/src/errors.rs index dd802a0..5364d6d 100644 --- a/src-tauri/src/errors.rs +++ b/src-tauri/src/errors.rs @@ -410,6 +410,17 @@ pub enum LaunchTerminalError { } +#[derive(Debug, ThisError, AsRefStr)] +pub enum LoadSshKeyError { + #[error("Passphrase is invalid")] + InvalidPassphrase, + #[error("Could not parse SSH private key data")] + InvalidData(#[from] ssh_key::Error), + #[error(transparent)] + Io(#[from] std::io::Error), +} + + // ========================= // Serialize implementations // ========================= @@ -436,6 +447,7 @@ impl_serialize_basic!(WindowError); impl_serialize_basic!(LockError); impl_serialize_basic!(SaveCredentialsError); impl_serialize_basic!(LoadCredentialsError); +impl_serialize_basic!(LoadSshKeyError); impl Serialize for HandlerError { diff --git a/src-tauri/src/ipc.rs b/src-tauri/src/ipc.rs index 88457e7..0202cfc 100644 --- a/src-tauri/src/ipc.rs +++ b/src-tauri/src/ipc.rs @@ -5,7 +5,8 @@ use tauri::{AppHandle, State}; use crate::config::AppConfig; use crate::credentials::{ AppSession, - CredentialRecord + CredentialRecord, + SshKey, }; use crate::errors::*; use crate::clientinfo::Client; @@ -134,6 +135,12 @@ pub async fn list_credentials(app_state: State<'_, AppState>) -> Result Result { + SshKey::from_file(path, passphrase) +} + + #[tauri::command] pub async fn get_config(app_state: State<'_, AppState>) -> Result { let config = app_state.config.read().await; diff --git a/src/ui/FileInput.svelte b/src/ui/FileInput.svelte new file mode 100644 index 0000000..8e5f659 --- /dev/null +++ b/src/ui/FileInput.svelte @@ -0,0 +1,39 @@ + + + +
+ + value.path = e.target.value} + on:change on:input on:focus on:blur + > +
diff --git a/src/ui/PassphraseInput.svelte b/src/ui/PassphraseInput.svelte index bf7b4a9..47f3934 100644 --- a/src/ui/PassphraseInput.svelte +++ b/src/ui/PassphraseInput.svelte @@ -19,13 +19,13 @@ -
+
value = e.target.value} on:input on:change on:focus on:blur - class="input input-bordered flex-grow join-item placeholder:text-gray-500 {classes}" + class="input input-bordered flex-grow join-item placeholder:text-gray-500 focus:outline-none {classes}" /> - {:else} -
-
You have no saved AWS credentials.
+ {#if awsRecords.length > 0} + {#each awsRecords as record (record.id)} + + {/each} + {:else if records !== null} +
+
You have no saved AWS credentials.
+ +
+ {/if} +
+ +
+
+

SSH Keys

- {/if} + + {#if sshRecords.length > 0} + {#each sshRecords as record (record.id)} + {#if record.isNew} + console.log(e)} /> + {:else} + + {/if} + {/each} + {:else if records !== null} +
+
You have no saved SSH keys.
+ +
+ {/if} +
diff --git a/src/views/credentials/AwsCredential.svelte b/src/views/credentials/AwsCredential.svelte index 790a743..c4aa001 100644 --- a/src/views/credentials/AwsCredential.svelte +++ b/src/views/credentials/AwsCredential.svelte @@ -16,7 +16,6 @@ let showDetails = record.isNew ? true : false; - let localName = name; let local = JSON.parse(JSON.stringify(record)); $: isModified = JSON.stringify(local) !== JSON.stringify(record); @@ -63,7 +62,13 @@ class="rounded-box space-y-4 bg-base-200 {record.is_default ? 'border border-accent' : ''}" >
-

{record.name || ''}

+

+ {#if !record?.isNew && showDetails} + + {:else} + {record.name || ''} + {/if} +

{#if record.is_default} Default @@ -129,9 +134,9 @@ transition:fade={{duration: 100}} type="submit" class="btn btn-primary" - > - Save - + > + Save + {/if}
diff --git a/src/views/credentials/NewSshKey.svelte b/src/views/credentials/NewSshKey.svelte new file mode 100644 index 0000000..a337c43 --- /dev/null +++ b/src/views/credentials/NewSshKey.svelte @@ -0,0 +1,92 @@ + + + +
+
+
+ + +
+
+ + {#if showDetails} +
alert.run(saveCredential)} + > + + +
+ Name + + + File + + + Passphrase + +
+ +
+ {#if file?.path} + + {/if} +
+ + {/if} +
\ No newline at end of file diff --git a/src/views/credentials/SshKey.svelte b/src/views/credentials/SshKey.svelte new file mode 100644 index 0000000..e2febdf --- /dev/null +++ b/src/views/credentials/SshKey.svelte @@ -0,0 +1,110 @@ + + + +
+
+

{record.name || ''}

+ +
+ + +
+
+ + {#if showDetails} +
alert.run(saveCredential)} + > + + +
+ {#if record.isNew} + File + + + Passphrase + + {:else} + Algorithm + {record.credential.algorithm} + + Comment + {record.credential.comment} + + Public key + {record.credential.public_key} + + Private key + {record.credential.private_key} + {/if} +
+ +
+ {#if isModified} + + {/if} +
+ + {/if} +
\ No newline at end of file