mirror of
https://github.com/bitwarden/browser
synced 2026-02-11 22:13:32 +00:00
Address feedback
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<String>,
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_async)] // FIXME: Remove unused async!
|
||||
#[napi]
|
||||
pub async fn serve(
|
||||
pub fn serve(
|
||||
callback: ThreadsafeFunction<SshUIRequest, CalleeHandled>,
|
||||
) -> napi::Result<SshAgentState> {
|
||||
let (auth_request_tx, mut auth_request_rx) =
|
||||
tokio::sync::mpsc::channel::<ssh_agent::agent::ui_requester::UiRequestMessage>(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<Promise<bool>, 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<Mutex<tokio::sync::broadcast::Sender<(u32, bool)>>>,
|
||||
callback: ThreadsafeFunction<SshUIRequest, CalleeHandled>,
|
||||
) {
|
||||
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<Promise<bool>, 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");
|
||||
|
||||
@@ -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<Mutex<crate::memory::KeyStore>>,
|
||||
key_store: Arc<Mutex<KeyStore>>,
|
||||
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<Item = tokio::io::Result<(S, PeerInfo)>> + 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<bool, anyhow::Error> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String>,
|
||||
) -> 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<String>,
|
||||
},
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -24,8 +24,7 @@ pub async fn serve_listener<PeerStream, Listener>(
|
||||
mut listener: Listener,
|
||||
cancellation_token: CancellationToken,
|
||||
agent: impl Agent,
|
||||
) -> Result<(), anyhow::Error>
|
||||
where
|
||||
) where
|
||||
PeerStream: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static,
|
||||
Listener: Stream<Item = tokio::io::Result<(PeerStream, PeerInfo)>> + 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"));
|
||||
|
||||
@@ -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<bool, anyhow::Error>;
|
||||
async fn find_ssh_item(
|
||||
&self,
|
||||
|
||||
@@ -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<u8>) -> Self {
|
||||
pub fn new(reply: ReplyType, payload: &[u8]) -> Self {
|
||||
let mut raw_frame = Vec::new();
|
||||
Into::<u8>::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<ReplyFrame, ssh_encoding::Error> {
|
||||
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<ReplyFrame, ssh_encoding::Error> {
|
||||
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<AgentExtensionFailure> 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<AgentFailure> 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<AgentSuccess> for ReplyFrame {
|
||||
fn from(_value: AgentSuccess) -> Self {
|
||||
ReplyFrame::new(ReplyType::SSH_AGENT_SUCCESS, Vec::new())
|
||||
ReplyFrame::new(ReplyType::SSH_AGENT_SUCCESS, &[])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Self, Self::Error> {
|
||||
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::<byteorder::BigEndian>()
|
||||
@@ -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 {},
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user