diff --git a/apps/desktop/desktop_native/core/Cargo.toml b/apps/desktop/desktop_native/core/Cargo.toml index 2fd7b805850..4f8934f3ee0 100644 --- a/apps/desktop/desktop_native/core/Cargo.toml +++ b/apps/desktop/desktop_native/core/Cargo.toml @@ -55,20 +55,6 @@ tracing = { workspace = true } typenum = { workspace = true } zeroizing-alloc = { workspace = true } -[target.'cfg(target_os = "linux")'.dependencies] -ashpd = { workspace = true } -libc = { workspace = true } -oo7 = { workspace = true } - -zbus = { workspace = true, optional = true } -zbus_polkit = { workspace = true, optional = true } - -[target.'cfg(target_os = "macos")'.dependencies] -core-foundation = { workspace = true, optional = true } -desktop_objc = { path = "../objc" } -security-framework = { workspace = true, optional = true } -security-framework-sys = { workspace = true, optional = true } - [target.'cfg(windows)'.dependencies] widestring = { workspace = true, optional = true } windows = { workspace = true, features = [ @@ -88,5 +74,19 @@ windows-future = { workspace = true } [target.'cfg(windows)'.dev-dependencies] keytar = { workspace = true } +[target.'cfg(target_os = "linux")'.dependencies] +ashpd = { workspace = true } +libc = { workspace = true } +oo7 = { workspace = true } + +zbus = { workspace = true, optional = true } +zbus_polkit = { workspace = true, optional = true } + +[target.'cfg(target_os = "macos")'.dependencies] +core-foundation = { workspace = true, optional = true } +desktop_objc = { path = "../objc" } +security-framework = { workspace = true, optional = true } +security-framework-sys = { workspace = true, optional = true } + [lints] workspace = true diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index cdf3c294f11..aac56a4c186 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -1072,6 +1072,8 @@ pub mod sshagent_v2 { use tokio::{self, sync::Mutex}; use tracing::{error, info}; + /// Wrapper struct to hold the SSH agent state. This is exposed via NAPI for which the + /// macro generates all the necessary boilerplate. #[napi] pub struct SshAgentState { agent: BitwardenDesktopAgent, @@ -1100,105 +1102,118 @@ pub mod sshagent_v2 { pub namespace: Option, } - #[allow(clippy::unused_async)] // FIXME: Remove unused async! #[napi] - pub async fn serve( + pub fn serve( callback: ThreadsafeFunction, ) -> napi::Result { - let (auth_request_tx, mut auth_request_rx) = - tokio::sync::mpsc::channel::(32); + // The size is arbitrary, as the channel responses are expected to be read immediately, + // a smaller or larger buffer would also work. + const BUFFER_SIZE: usize = 32; + + let (auth_request_tx, mut auth_request_rx) = tokio::sync::mpsc::channel::< + ssh_agent::agent::ui_requester::UiRequestMessage, + >(BUFFER_SIZE); let (auth_response_tx, auth_response_rx) = - tokio::sync::broadcast::channel::<(u32, bool)>(32); + tokio::sync::broadcast::channel::<(u32, bool)>(BUFFER_SIZE); let auth_response_tx_arc = Arc::new(Mutex::new(auth_response_tx)); let ui_requester = ui_requester::UiRequester::new(auth_request_tx, Arc::new(Mutex::new(auth_response_rx))); tokio::spawn(async move { - let _ = ui_requester; - while let Some(request) = auth_request_rx.recv().await { - let cloned_response_tx_arc = auth_response_tx_arc.clone(); - let cloned_callback = callback.clone(); - tokio::spawn(async move { - let auth_response_tx_arc = cloned_response_tx_arc; - let callback = cloned_callback; - - let js_request = match request.clone() { - UiRequestMessage::ListRequest { - request_id: _, - connection_info, - } => SshUIRequest { - cipher_id: None, - is_list: true, - process_name: connection_info.peer_info().process_name().to_string(), - is_forwarding: connection_info.is_forwarding(), - namespace: None, - }, - UiRequestMessage::AuthRequest { - request_id: _, - connection_info, - cipher_id, - } => SshUIRequest { - cipher_id: Some(cipher_id), - is_list: false, - process_name: connection_info.peer_info().process_name().to_string(), - is_forwarding: connection_info.is_forwarding(), - namespace: None, - }, - UiRequestMessage::SignRequest { - request_id: _, - connection_info, - cipher_id, - namespace, - } => SshUIRequest { - cipher_id: Some(cipher_id), - is_list: false, - process_name: connection_info.peer_info().process_name().to_string(), - is_forwarding: connection_info.is_forwarding(), - namespace: Some(namespace), - }, - }; - - let promise_result: Result, napi::Error> = - callback.call_async(Ok(js_request)).await; - match promise_result { - Ok(promise_result) => match promise_result.await { - Ok(result) => { - let _ = auth_response_tx_arc - .lock() - .await - .send((request.id(), result)) - .expect("should be able to send auth response to agent"); - } - Err(e) => { - error!(error = %e, "Calling UI callback promise was rejected"); - let _ = auth_response_tx_arc - .lock() - .await - .send((request.id(), false)) - .expect("should be able to send auth response to agent"); - } - }, - Err(e) => { - error!(error = %e, "Calling UI callback could not create promise"); - let _ = auth_response_tx_arc - .lock() - .await - .send((request.id(), false)) - .expect("should be able to send auth response to agent"); - } - } - }); + handle_ui_request(request, auth_response_tx_arc.clone(), callback.clone()); } }); let agent = BitwardenDesktopAgent::new(ui_requester); let agent_copy = agent.clone(); - PlatformListener::spawn_listeners(agent_copy); + if let Err(e) = PlatformListener::spawn_listeners(agent_copy) { + return Err(napi::Error::from_reason(format!( + "Failed to start SSH Agent platform listeners - Error: {e} - {e:?}" + ))); + } Ok(SshAgentState { agent }) } + fn handle_ui_request( + request_message: UiRequestMessage, + auth_response_tx_arc: Arc>>, + callback: ThreadsafeFunction, + ) { + tokio::spawn(async move { + let mut ui_request = SshUIRequest { + cipher_id: None, + is_list: false, + process_name: request_message + .connection_info() + .peer_info() + .process_name() + .to_string(), + is_forwarding: request_message.connection_info().is_forwarding(), + namespace: None, + }; + + let js_request = match request_message.clone() { + UiRequestMessage::ListRequest { + request_id: _, + connection_info: _, + } => { + ui_request.is_list = true; + ui_request + } + UiRequestMessage::AuthRequest { + request_id: _, + connection_info: _, + cipher_id, + } => { + ui_request.cipher_id = Some(cipher_id); + ui_request + } + UiRequestMessage::SignRequest { + request_id: _, + connection_info: _, + cipher_id, + namespace, + } => { + ui_request.cipher_id = Some(cipher_id); + ui_request.namespace = Some(namespace); + ui_request + } + }; + + let promise_result: Result, napi::Error> = + callback.call_async(Ok(js_request)).await; + match promise_result { + Ok(promise_result) => match promise_result.await { + Ok(result) => { + let _ = auth_response_tx_arc + .lock() + .await + .send((request_message.id(), result)) + .expect("should be able to send auth response to agent"); + } + Err(e) => { + error!(error = %e, "Calling UI callback promise was rejected"); + let _ = auth_response_tx_arc + .lock() + .await + .send((request_message.id(), false)) + .expect("should be able to send auth response to agent"); + } + }, + Err(e) => { + error!(error = %e, "Calling UI callback could not create promise"); + let _ = auth_response_tx_arc + .lock() + .await + .send((request_message.id(), false)) + .expect("should be able to send auth response to agent"); + } + } + }); + } + #[napi] pub fn stop(agent_state: &mut SshAgentState) -> napi::Result<()> { info!("Stopping SSH Agent"); diff --git a/apps/desktop/desktop_native/ssh_agent/src/agent/desktop_agent.rs b/apps/desktop/desktop_native/ssh_agent/src/agent/desktop_agent.rs index 04374d5720f..17be99df42a 100644 --- a/apps/desktop/desktop_native/ssh_agent/src/agent/desktop_agent.rs +++ b/apps/desktop/desktop_native/ssh_agent/src/agent/desktop_agent.rs @@ -5,15 +5,21 @@ use tokio_util::sync::CancellationToken; use crate::{ agent::ui_requester::UiRequester, - memory::UnlockedSshItem, - protocol::{self, agent_listener::serve_listener, key_store::Agent, types::PublicKeyWithName}, + memory::{KeyStore, UnlockedSshItem}, + protocol::{ + self, + agent_listener::serve_listener, + key_store::Agent, + requests::{ParsedSignRequest, SshSignRequest}, + types::PublicKeyWithName, + }, transport::peer_info::PeerInfo, }; #[derive(Clone)] pub struct BitwardenDesktopAgent { cancellation_token: CancellationToken, - key_store: Arc>, + key_store: Arc>, ui_requester: UiRequester, } @@ -21,7 +27,7 @@ impl BitwardenDesktopAgent { pub fn new(ui_requester: UiRequester) -> Self { Self { cancellation_token: CancellationToken::new(), - key_store: Arc::new(Mutex::new(crate::memory::KeyStore::new())), + key_store: Arc::new(Mutex::new(KeyStore::new())), ui_requester, } } @@ -31,10 +37,7 @@ impl BitwardenDesktopAgent { S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + Sync + Unpin + 'static, L: Stream> + Unpin, { - let err = serve_listener(listener, self.cancellation_token.clone(), self).await; - if let Err(e) = err { - tracing::error!("Error in agent listener: {e}"); - } + serve_listener(listener, self.cancellation_token.clone(), self).await; } pub fn stop(&self) { @@ -58,10 +61,6 @@ impl BitwardenDesktopAgent { pub fn is_running(&self) -> bool { !self.cancellation_token.is_cancelled() } - - pub fn cancellation_token(&self) -> CancellationToken { - self.cancellation_token.clone() - } } impl Agent for &BitwardenDesktopAgent { @@ -95,6 +94,7 @@ impl Agent for &BitwardenDesktopAgent { &self, public_key: &protocol::types::PublicKey, connection_info: &protocol::connection::ConnectionInfo, + sign_request: &ParsedSignRequest, ) -> Result { let id = self .key_store @@ -102,12 +102,16 @@ impl Agent for &BitwardenDesktopAgent { .expect("Failed to lock key store") .get_cipher_id(public_key); if let Some(cipher_id) = id { + let namespace = match &sign_request { + ParsedSignRequest::SshSigRequest { namespace } => Some(namespace.clone()), + ParsedSignRequest::SignRequest {} => None, + }; + return Ok(self .ui_requester - .request_sign(connection_info, cipher_id, "unknown".to_string()) + .request_sign(connection_info, cipher_id, namespace) .await); - } else { - Ok(false) } + Ok(false) } } diff --git a/apps/desktop/desktop_native/ssh_agent/src/agent/platform.rs b/apps/desktop/desktop_native/ssh_agent/src/agent/platform.rs index 395aed55787..49726bb20d9 100644 --- a/apps/desktop/desktop_native/ssh_agent/src/agent/platform.rs +++ b/apps/desktop/desktop_native/ssh_agent/src/agent/platform.rs @@ -1,89 +1,76 @@ use crate::agent::BitwardenDesktopAgent; +#[cfg(any(target_os = "linux", target_os = "macos"))] +const SSH_AGENT_SOCK_NAME: &str = ".bitwarden-ssh-agent.sock"; +#[cfg(target_os = "linux")] +const FLATPAK_SSH_AGENT_SOCK_NAME: &str = + ".var/app/com.bitwarden.desktop/data/.bitwarden-ssh-agent.sock"; #[cfg(target_os = "windows")] -use crate::transport::named_pipe_listener_stream::NamedPipeServerStream; -#[cfg(any(target_os = "linux", target_os = "macos"))] -use crate::transport::unix_listener_stream::UnixListenerStream; -#[cfg(any(target_os = "linux", target_os = "macos"))] -use homedir::my_home; -#[cfg(any(target_os = "linux", target_os = "macos"))] -use tracing::info; +const WINDOWS_NAMED_PIPE_NAME: &str = r"\\.\pipe\openssh-ssh-agent"; pub struct PlatformListener {} impl PlatformListener { - /// Spawns all listeners for the current platform. A platform may have a single listener, or multiple. - pub fn spawn_listeners(agent: BitwardenDesktopAgent) { - #[cfg(target_os = "linux")] - { - Self::spawn_linux_listeners(agent); - } - - #[cfg(target_os = "macos")] - { - Self::spawn_macos_listeners(agent); - } - - #[cfg(target_os = "windows")] - { - Self::spawn_windows_listeners(agent); - } - } - #[cfg(target_os = "linux")] - fn spawn_linux_listeners(agent: BitwardenDesktopAgent) { - let ssh_agent_directory = match my_home() { - Ok(Some(home)) => home, - _ => { - info!("Could not determine home directory"); - return; - } + pub fn spawn_listeners(agent: BitwardenDesktopAgent) -> Result<(), anyhow::Error> { + use crate::transport::unix_listener_stream::UnixListenerStream; + use homedir::my_home; + let ssh_agent_directory = if let Ok(Some(home)) = my_home() { + home + } else { + return Err(anyhow::anyhow!("Could not determine home directory")); }; let is_flatpak = std::env::var("container") == Ok("flatpak".to_string()); let path = if !is_flatpak { ssh_agent_directory - .join(".bitwarden-ssh-agent.sock") + .join(SSH_AGENT_SOCK_NAME) .to_str() .expect("Path should be valid") .to_owned() } else { ssh_agent_directory - .join(".var/app/com.bitwarden.desktop/data/.bitwarden-ssh-agent.sock") + .join(FLATPAK_SSH_AGENT_SOCK_NAME) .to_str() .expect("Path should be valid") .to_owned() }; tokio::spawn(UnixListenerStream::listen(path, agent)); + Ok(()) } #[cfg(target_os = "macos")] - fn spawn_macos_listeners(agent: BitwardenDesktopAgent) { - let ssh_agent_directory = match my_home() { - Ok(Some(home)) => home, - _ => { - info!("Could not determine home directory"); - return; - } + pub fn spawn_listeners(agent: BitwardenDesktopAgent) -> Result<(), anyhow::Error> { + use crate::transport::unix_listener_stream::UnixListenerStream; + use homedir::my_home; + let ssh_agent_directory = if let Ok(Some(home)) = my_home() { + home + } else { + return Err(anyhow::anyhow!("Could not determine home directory")); }; let path = ssh_agent_directory - .join(".bitwarden-ssh-agent.sock") + .join(SSH_AGENT_SOCK_NAME) .to_str() .expect("Path should be valid") .to_owned(); tokio::spawn(UnixListenerStream::listen(path, agent)); + Ok(()) } #[cfg(target_os = "windows")] - pub fn spawn_windows_listeners(agent: BitwardenDesktopAgent) { + pub fn spawn_listeners(agent: BitwardenDesktopAgent) -> Result<(), anyhow::Error> { + use crate::transport::named_pipe_listener_stream::NamedPipeServerStream; tokio::spawn(async move { // Windows by default uses the named pipe \\.\pipe\openssh-ssh-agent. It also supports external SSH auth sock variables, which are // not supported here. Windows also supports putty (not implemented here) and unix sockets for WSL (not implemented here). - const PIPE_NAME: &str = r"\\.\pipe\openssh-ssh-agent"; - tokio::spawn(NamedPipeServerStream::listen(PIPE_NAME.to_string(), agent)); + tokio::spawn(NamedPipeServerStream::listen( + WINDOWS_NAMED_PIPE_NAME.to_string(), + agent, + )); }); + Ok(()) } } diff --git a/apps/desktop/desktop_native/ssh_agent/src/agent/ui_requester.rs b/apps/desktop/desktop_native/ssh_agent/src/agent/ui_requester.rs index 3265cd93dec..c08cdf0cf08 100644 --- a/apps/desktop/desktop_native/ssh_agent/src/agent/ui_requester.rs +++ b/apps/desktop/desktop_native/ssh_agent/src/agent/ui_requester.rs @@ -4,7 +4,8 @@ use tokio::sync::Mutex; use crate::protocol::connection::ConnectionInfo; -const TIMEOUT: std::time::Duration = std::time::Duration::from_secs(60); +/// Determines how long to wait for a UI response before timing out. +const SSH_UI_REQUEST_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(60); static REQUEST_ID_COUNTER: AtomicU32 = AtomicU32::new(0); @@ -59,7 +60,7 @@ impl UiRequester { &self, connection_info: &ConnectionInfo, cipher_id: String, - namespace: String, + namespace: Option, ) -> bool { let request_id = REQUEST_ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst); self.request(UiRequestMessage::SignRequest { @@ -78,7 +79,7 @@ impl UiRequester { .await .expect("Should send request to ui"); - tokio::time::timeout(TIMEOUT, async move { + tokio::time::timeout(SSH_UI_REQUEST_TIMEOUT, async move { while let Ok((id, response)) = rx_channel.recv().await { if id == request.id() { return response; @@ -106,16 +107,30 @@ pub enum UiRequestMessage { request_id: u32, connection_info: ConnectionInfo, cipher_id: String, - namespace: String, + namespace: Option, }, } impl UiRequestMessage { + pub fn connection_info(&self) -> &ConnectionInfo { + match self { + UiRequestMessage::ListRequest { + connection_info, .. + } + | UiRequestMessage::AuthRequest { + connection_info, .. + } + | UiRequestMessage::SignRequest { + connection_info, .. + } => connection_info, + } + } + pub fn id(&self) -> u32 { match self { - UiRequestMessage::ListRequest { request_id, .. } => *request_id, - UiRequestMessage::AuthRequest { request_id, .. } => *request_id, - UiRequestMessage::SignRequest { request_id, .. } => *request_id, + UiRequestMessage::ListRequest { request_id, .. } + | UiRequestMessage::AuthRequest { request_id, .. } + | UiRequestMessage::SignRequest { request_id, .. } => *request_id, } } } diff --git a/apps/desktop/desktop_native/ssh_agent/src/knownhosts/mod.rs b/apps/desktop/desktop_native/ssh_agent/src/knownhosts/mod.rs index 0c19362bbc0..069503e533b 100644 --- a/apps/desktop/desktop_native/ssh_agent/src/knownhosts/mod.rs +++ b/apps/desktop/desktop_native/ssh_agent/src/knownhosts/mod.rs @@ -1,7 +1,7 @@ use std::fs; use std::path::Path; -use tracing::info; +use tracing::warn; use crate::protocol::types::PublicKey; @@ -75,7 +75,7 @@ impl KnownHostsReader { // Split by the first space let first_space_index = line.find(' '); let Some(first_space_index) = first_space_index else { - info!("Invalid known_hosts line (no spaces): {}", line); + warn!("Invalid known_hosts line (no spaces): {}", line); continue; }; let (hostnames, rest) = line.split_at(first_space_index); diff --git a/apps/desktop/desktop_native/ssh_agent/src/protocol/agent_listener.rs b/apps/desktop/desktop_native/ssh_agent/src/protocol/agent_listener.rs index 740bf8f5de8..28e96d458a0 100644 --- a/apps/desktop/desktop_native/ssh_agent/src/protocol/agent_listener.rs +++ b/apps/desktop/desktop_native/ssh_agent/src/protocol/agent_listener.rs @@ -24,8 +24,7 @@ pub async fn serve_listener( mut listener: Listener, cancellation_token: CancellationToken, agent: impl Agent, -) -> Result<(), anyhow::Error> -where +) where PeerStream: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static, Listener: Stream> + Unpin, { @@ -44,7 +43,6 @@ where } } } - Ok(()) } async fn handle_connection( @@ -90,7 +88,11 @@ async fn handle_connection( span.in_scope(|| info!("Received SignRequest {:?}", sign_request)); let Ok(true) = agent - .request_can_sign(sign_request.public_key(), connection) + .request_can_sign( + sign_request.public_key(), + connection, + sign_request.parsed_payload(), + ) .await else { span.in_scope(|| error!("Sign request denied by UI")); diff --git a/apps/desktop/desktop_native/ssh_agent/src/protocol/key_store.rs b/apps/desktop/desktop_native/ssh_agent/src/protocol/key_store.rs index 22227a56569..cb26e87629c 100644 --- a/apps/desktop/desktop_native/ssh_agent/src/protocol/key_store.rs +++ b/apps/desktop/desktop_native/ssh_agent/src/protocol/key_store.rs @@ -2,6 +2,7 @@ use crate::{ memory::UnlockedSshItem, protocol::{ connection::ConnectionInfo, + requests::ParsedSignRequest, types::{PublicKey, PublicKeyWithName}, }, }; @@ -16,6 +17,7 @@ pub(crate) trait Agent: Send + Sync { &self, public_key: &PublicKey, connection_info: &ConnectionInfo, + parsed_sign_request: &ParsedSignRequest, ) -> Result; async fn find_ssh_item( &self, diff --git a/apps/desktop/desktop_native/ssh_agent/src/protocol/replies.rs b/apps/desktop/desktop_native/ssh_agent/src/protocol/replies.rs index a7326ace171..b53b4c00e0c 100644 --- a/apps/desktop/desktop_native/ssh_agent/src/protocol/replies.rs +++ b/apps/desktop/desktop_native/ssh_agent/src/protocol/replies.rs @@ -22,7 +22,7 @@ pub enum ReplyType { /// `https://www.ietf.org/archive/id/draft-miller-ssh-agent-11.html#name-requesting-a-list-of-keys` /// Response to `RequestType::SSH_AGENTC_REQUEST_IDENTITIES` SSH_AGENT_IDENTITIES_ANSWER = 12, - /// `https://www.ietf.org/archive/id/draft-miller-ssh-agent-11.html#name-private-key-operations`` + /// `https://www.ietf.org/archive/id/draft-miller-ssh-agent-11.html#name-private-key-operations` /// Response to `RequestType::SSH_AGENTC_SIGN_REQUEST` SSH_AGENT_SIGN_RESPONSE = 14, /// Invalid reply type @@ -39,12 +39,12 @@ pub struct ReplyFrame { } impl ReplyFrame { - pub fn new(reply: ReplyType, payload: Vec) -> Self { + pub fn new(reply: ReplyType, payload: &[u8]) -> Self { let mut raw_frame = Vec::new(); Into::::into(reply) .encode(&mut raw_frame) .expect("Encoding into Vec cannot fail"); - raw_frame.extend_from_slice(&payload); + raw_frame.extend_from_slice(payload); Self { raw_frame } } } @@ -75,14 +75,15 @@ impl IdentitiesReply { /// ... (nkeys times) /// ] pub fn encode(&self) -> Result { + let mut reply_message = Vec::new(); Ok(ReplyFrame::new(ReplyType::SSH_AGENT_IDENTITIES_ANSWER, { - let mut reply_message = Vec::new(); + reply_message.clear(); (self.keys.len() as u32).encode(&mut reply_message)?; for key in &self.keys { key.key.encode(&mut reply_message)?; key.name.encode(&mut reply_message)?; } - reply_message + &reply_message })) } } @@ -107,10 +108,10 @@ impl SshSignReply { /// byte SSH_AGENT_SIGN_RESPONSE /// string signature blob pub fn encode(&self) -> Result { + let mut reply_payload = Vec::new(); Ok(ReplyFrame::new(ReplyType::SSH_AGENT_SIGN_RESPONSE, { - let mut reply_payload = Vec::new(); self.0.encode()?.encode(&mut reply_payload)?; - reply_payload + &reply_payload })) } } @@ -124,7 +125,7 @@ impl AgentExtensionFailure { impl From for ReplyFrame { fn from(_value: AgentExtensionFailure) -> Self { - ReplyFrame::new(ReplyType::SSH_AGENT_EXTENSION_FAILURE, Vec::new()) + ReplyFrame::new(ReplyType::SSH_AGENT_EXTENSION_FAILURE, &[]) } } @@ -137,7 +138,7 @@ impl AgentFailure { impl From for ReplyFrame { fn from(_value: AgentFailure) -> Self { - ReplyFrame::new(ReplyType::SSH_AGENT_FAILURE, Vec::new()) + ReplyFrame::new(ReplyType::SSH_AGENT_FAILURE, &[]) } } @@ -150,6 +151,6 @@ impl AgentSuccess { impl From for ReplyFrame { fn from(_value: AgentSuccess) -> Self { - ReplyFrame::new(ReplyType::SSH_AGENT_SUCCESS, Vec::new()) + ReplyFrame::new(ReplyType::SSH_AGENT_SUCCESS, &[]) } } diff --git a/apps/desktop/desktop_native/ssh_agent/src/protocol/requests.rs b/apps/desktop/desktop_native/ssh_agent/src/protocol/requests.rs index f7204010c5f..1423ca746f4 100644 --- a/apps/desktop/desktop_native/ssh_agent/src/protocol/requests.rs +++ b/apps/desktop/desktop_native/ssh_agent/src/protocol/requests.rs @@ -135,7 +135,6 @@ impl SshSignRequest { &self.payload_to_sign } - #[allow(unused)] pub fn parsed_payload(&self) -> &ParsedSignRequest { &self.parsed_sign_request } @@ -154,7 +153,7 @@ impl TryFrom<&[u8]> for SshSignRequest { /// /// In this case, the message already has the leading byte stripped off by the previous parsing code. fn try_from(mut message: &[u8]) -> Result { - let public_key_blob = read_bytes(&mut message)?.to_vec(); + let public_key_blob = read_bytes(&mut message)?.clone(); let data = read_bytes(&mut message)?; let flags = message .read_u32::() @@ -170,11 +169,8 @@ impl TryFrom<&[u8]> for SshSignRequest { } #[derive(Debug)] -pub(crate) enum ParsedSignRequest { - #[allow(unused)] - SshSigRequest { - namespace: String, - }, +pub enum ParsedSignRequest { + SshSigRequest { namespace: String }, SignRequest {}, } diff --git a/apps/desktop/src/autofill/services/ssh-agent.service.ts b/apps/desktop/src/autofill/services/ssh-agent.service.ts index ebbc4f81a5c..1e31f0d27c7 100644 --- a/apps/desktop/src/autofill/services/ssh-agent.service.ts +++ b/apps/desktop/src/autofill/services/ssh-agent.service.ts @@ -38,6 +38,13 @@ import { ApproveSshRequestComponent } from "../../platform/components/approve-ss import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; import { SshAgentPromptType } from "../models/ssh-agent-setting"; +// Note: There are two implementations of the SSH agent, for a +// transition phase, V1, and V2. The version is selected +// via a feature flag when the agent is initialized. Once the feature +// flag is rolled out, V1 can be removed. +const SSH_AGENT_MODULE_VERSION_V1 = 1; +const SSH_AGENT_MODULE_VERSION_V2 = 2; + @Injectable({ providedIn: "root", }) @@ -63,7 +70,7 @@ export class SshAgentService implements OnDestroy { private desktopSettingsService: DesktopSettingsService, private accountService: AccountService, private configService: ConfigService, - ) {} + ) { } async init() { this.desktopSettingsService.sshAgentEnabled$ @@ -73,7 +80,7 @@ export class SshAgentService implements OnDestroy { const isV2FeatureFlagEnabled = await this.configService.getFeatureFlag( FeatureFlag.SshAgentV2, ); - await ipc.platform.sshAgent.init(isV2FeatureFlagEnabled ? 2 : 1); + await ipc.platform.sshAgent.init(isV2FeatureFlagEnabled ? SSH_AGENT_MODULE_VERSION_V2 : SSH_AGENT_MODULE_VERSION_V1); } if (!enabled) {