From e7d5cde1057133f24f9133852bf891976ee9cfdc Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 9 Jul 2025 16:52:47 +0200 Subject: [PATCH] [BEEEP/PM-22958] Update russh version, and add sessionbind information (#14602) * Update russh version, and add sessionbind information * Cargo fmt * Clean up to fix lint * Attempt to fix windows * Use expect instead of unwrap * Fix cargo toml --- apps/desktop/desktop_native/Cargo.lock | 159 +++++++++++++++++- apps/desktop/desktop_native/Cargo.toml | 2 +- .../desktop_native/core/src/ssh_agent/mod.rs | 75 +++++++-- .../core/src/ssh_agent/peerinfo/models.rs | 16 +- .../desktop_native/core/src/ssh_agent/unix.rs | 4 +- .../core/src/ssh_agent/windows.rs | 4 +- apps/desktop/desktop_native/napi/src/lib.rs | 3 +- 7 files changed, 241 insertions(+), 22 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index eadd75e598..d02ffb9b02 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -377,6 +377,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.22.1" @@ -427,11 +433,17 @@ checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bitwarden-russh" version = "0.1.0" -source = "git+https://github.com/bitwarden/bitwarden-russh.git?rev=3d48f140fd506412d186203238993163a8c4e536#3d48f140fd506412d186203238993163a8c4e536" +source = "git+https://github.com/bitwarden/bitwarden-russh.git?rev=a641316227227f8777fdf56ac9fa2d6b5f7fe662#a641316227227f8777fdf56ac9fa2d6b5f7fe662" dependencies = [ "anyhow", "byteorder", + "ecdsa", + "ed25519-dalek", "futures", + "p256", + "p384", + "p521", + "rsa", "russh-cryptovec", "ssh-encoding", "ssh-key", @@ -707,6 +719,18 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -750,6 +774,7 @@ dependencies = [ "fiat-crypto", "rustc_version", "subtle", + "zeroize", ] [[package]] @@ -1018,6 +1043,20 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + [[package]] name = "ed25519" version = "2.2.3" @@ -1036,8 +1075,32 @@ checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", + "serde", "sha2", + "signature", "subtle", + "zeroize", +] + +[[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 0.6.4", + "sec1", + "subtle", + "zeroize", ] [[package]] @@ -1122,6 +1185,16 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -1274,6 +1347,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1342,6 +1416,17 @@ dependencies = [ "scroll", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -2112,6 +2197,44 @@ dependencies = [ "log", ] +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p521" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primeorder", + "rand_core 0.6.4", + "sha2", +] + [[package]] name = "parking" version = "2.2.1" @@ -2348,6 +2471,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "3.3.0" @@ -2504,6 +2636,16 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rsa" version = "0.9.6" @@ -2640,6 +2782,20 @@ dependencies = [ "sha2", ] +[[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 = "3.1.0" @@ -2854,6 +3010,7 @@ dependencies = [ "num-bigint-dig", "rand_core 0.6.4", "rsa", + "sec1", "sha2", "signature", "ssh-cipher", diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index b1516ecfbc..1aa6f784ec 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -16,7 +16,7 @@ argon2 = "=0.5.3" ashpd = "=0.11.0" base64 = "=0.22.1" bindgen = "=0.72.0" -bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", rev = "3d48f140fd506412d186203238993163a8c4e536" } +bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", rev = "a641316227227f8777fdf56ac9fa2d6b5f7fe662" } byteorder = "=1.5.0" bytes = "=1.10.1" cbc = "=0.1.2" diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs b/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs index 63348904e4..33076071a1 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs @@ -3,10 +3,14 @@ use std::sync::{ Arc, }; +use base64::{engine::general_purpose::STANDARD, Engine as _}; use tokio::sync::Mutex; use tokio_util::sync::CancellationToken; -use bitwarden_russh::ssh_agent::{self, Key}; +use bitwarden_russh::{ + session_bind::SessionBindResult, + ssh_agent::{self, SshKey}, +}; #[cfg_attr(target_os = "windows", path = "windows.rs")] #[cfg_attr(target_os = "macos", path = "unix.rs")] @@ -20,8 +24,8 @@ pub mod peerinfo; mod request_parser; #[derive(Clone)] -pub struct BitwardenDesktopAgent { - keystore: ssh_agent::KeyStore, +pub struct BitwardenDesktopAgent { + keystore: ssh_agent::KeyStore, cancellation_token: CancellationToken, show_ui_request_tx: tokio::sync::mpsc::Sender, get_ui_response_rx: Arc>>, @@ -40,8 +44,47 @@ pub struct SshAgentUIRequest { pub is_forwarding: bool, } -impl ssh_agent::Agent for BitwardenDesktopAgent { - async fn confirm(&self, ssh_key: Key, data: &[u8], info: &peerinfo::models::PeerInfo) -> bool { +#[derive(Clone)] +pub struct BitwardenSshKey { + pub private_key: Option, + pub name: String, + pub cipher_uuid: String, +} + +impl SshKey for BitwardenSshKey { + fn name(&self) -> &str { + &self.name + } + + fn public_key_bytes(&self) -> Vec { + if let Some(ref private_key) = self.private_key { + private_key + .public_key() + .to_bytes() + .expect("Cipher private key is always correctly parsed") + } else { + Vec::new() + } + } + + fn private_key(&self) -> Option> { + if let Some(ref private_key) = self.private_key { + Some(Box::new(private_key.clone())) + } else { + None + } + } +} + +impl ssh_agent::Agent + for BitwardenDesktopAgent +{ + async fn confirm( + &self, + ssh_key: BitwardenSshKey, + data: &[u8], + info: &peerinfo::models::PeerInfo, + ) -> bool { if !self.is_running() { println!("[BitwardenDesktopAgent] Agent is not running, but tried to call confirm"); return false; @@ -63,10 +106,11 @@ impl ssh_agent::Agent for BitwardenDesktopAgent { }; println!( - "[SSH Agent] Confirming request from application: {}, is_forwarding: {}, namespace: {}", + "[SSH Agent] Confirming request from application: {}, is_forwarding: {}, namespace: {}, host_key: {}", info.process_name(), info.is_forwarding(), namespace.clone().unwrap_or_default(), + STANDARD.encode(info.host_key()) ); let mut rx_channel = self.get_ui_response_rx.lock().await.resubscribe(); @@ -117,19 +161,24 @@ impl ssh_agent::Agent for BitwardenDesktopAgent { false } - async fn set_is_forwarding( + async fn set_sessionbind_info( &self, - is_forwarding: bool, + session_bind_info_result: &SessionBindResult, connection_info: &peerinfo::models::PeerInfo, ) { - // is_forwarding can only be added but never removed from a connection - if is_forwarding { - connection_info.set_forwarding(is_forwarding); + match session_bind_info_result { + SessionBindResult::Success(session_bind_info) => { + connection_info.set_forwarding(session_bind_info.is_forwarding); + connection_info.set_host_key(session_bind_info.host_key.clone()); + } + SessionBindResult::SignatureFailure => { + println!("[BitwardenDesktopAgent] Session bind failure: Signature failure"); + } } } } -impl BitwardenDesktopAgent { +impl BitwardenDesktopAgent { pub fn stop(&self) { if !self.is_running() { println!("[BitwardenDesktopAgent] Tried to stop agent while it is not running"); @@ -170,7 +219,7 @@ impl BitwardenDesktopAgent { .expect("Cipher private key is always correctly parsed"); keystore.0.write().expect("RwLock is not poisoned").insert( public_key_bytes, - Key { + BitwardenSshKey { private_key: Some(private_key), name: name.clone(), cipher_uuid: cipher_id.clone(), diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/models.rs b/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/models.rs index 35a5a50826..fad535cb80 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/models.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/models.rs @@ -1,15 +1,16 @@ -use std::sync::{atomic::AtomicBool, Arc}; +use std::sync::{atomic::AtomicBool, Arc, Mutex}; /** * Peerinfo represents the information of a peer process connecting over a socket. * This can be later extended to include more information (icon, app name) for the corresponding application. */ -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct PeerInfo { uid: u32, pid: u32, process_name: String, is_forwarding: Arc, + host_key: Arc>>, } impl PeerInfo { @@ -19,6 +20,7 @@ impl PeerInfo { pid, process_name, is_forwarding: Arc::new(AtomicBool::new(false)), + host_key: Arc::new(Mutex::new(Vec::new())), } } @@ -28,6 +30,7 @@ impl PeerInfo { pid: 0, process_name: "Unknown application".to_string(), is_forwarding: Arc::new(AtomicBool::new(false)), + host_key: Arc::new(Mutex::new(Vec::new())), } } @@ -52,4 +55,13 @@ impl PeerInfo { self.is_forwarding .store(value, std::sync::atomic::Ordering::Relaxed); } + + pub fn set_host_key(&self, host_key: Vec) { + let mut host_key_lock = self.host_key.lock().expect("Mutex is not poisoned"); + *host_key_lock = host_key; + } + + pub fn host_key(&self) -> Vec { + self.host_key.lock().expect("Mutex is not poisoned").clone() + } } diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs b/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs index ed297a9002..05d07cfee4 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs @@ -15,9 +15,9 @@ use tokio_util::sync::CancellationToken; use crate::ssh_agent::peercred_unix_listener_stream::PeercredUnixListenerStream; -use super::{BitwardenDesktopAgent, SshAgentUIRequest}; +use super::{BitwardenDesktopAgent, BitwardenSshKey, SshAgentUIRequest}; -impl BitwardenDesktopAgent { +impl BitwardenDesktopAgent { pub async fn start_server( auth_request_tx: tokio::sync::mpsc::Sender, auth_response_rx: Arc>>, diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/windows.rs b/apps/desktop/desktop_native/core/src/ssh_agent/windows.rs index bc63ef552b..aeb20aefd6 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/windows.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/windows.rs @@ -11,9 +11,9 @@ use std::{ use tokio::sync::Mutex; use tokio_util::sync::CancellationToken; -use super::{BitwardenDesktopAgent, SshAgentUIRequest}; +use super::{BitwardenDesktopAgent, BitwardenSshKey, SshAgentUIRequest}; -impl BitwardenDesktopAgent { +impl BitwardenDesktopAgent { pub async fn start_server( auth_request_tx: tokio::sync::mpsc::Sender, auth_response_rx: Arc>>, diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index fb80ec451a..49f653d480 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -166,6 +166,7 @@ pub mod clipboards { pub mod sshagent { use std::sync::Arc; + use desktop_core::ssh_agent::BitwardenSshKey; use napi::{ bindgen_prelude::Promise, threadsafe_function::{ErrorStrategy::CalleeHandled, ThreadsafeFunction}, @@ -174,7 +175,7 @@ pub mod sshagent { #[napi] pub struct SshAgentState { - state: desktop_core::ssh_agent::BitwardenDesktopAgent, + state: desktop_core::ssh_agent::BitwardenDesktopAgent, } #[napi(object)]