Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 90 additions & 52 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4206,9 +4206,7 @@ async fn handle_control_command(
}
ControlAction::UnlockPassword { password } => {
let password = zeroize::Zeroizing::new(password);
if !vault_locked.load(std::sync::atomic::Ordering::Relaxed) {
return sshwarden_agent::ControlResponse::ok("Vault is already unlocked");
}
let was_locked = vault_locked.load(std::sync::atomic::Ordering::Relaxed);

// Get email from vault file or config
let email = {
Expand Down Expand Up @@ -4251,11 +4249,17 @@ async fn handle_control_command(
if let Err(e) = sync_managed_ssh_config_with_bindings(&keys) {
tracing::warn!("Failed to sync managed SSH config: {}", e);
}
public_key_identity_tuples
.write()
.await
.set(key_tuples.clone());
cached_key_tuples.write().await.set(key_tuples.clone());
reconcile_synced_key_state(
&key_tuples,
cached_key_tuples,
public_key_identity_tuples,
local_key_cache_data,
local_cache_key_state,
authorization_memory,
key_material_fingerprints,
notification_state,
)
.await;

// Update key_names
{
Expand Down Expand Up @@ -4306,11 +4310,13 @@ async fn handle_control_command(
)
.await;

info!("Vault unlocked via master password, {} keys loaded", count);
sshwarden_agent::ControlResponse::ok(&format!(
"Vault unlocked, {} SSH keys loaded",
count
))
let message = if was_locked {
format!("Vault unlocked, {} SSH keys loaded", count)
} else {
format!("Bitwarden session refreshed, {} SSH keys loaded", count)
};
info!("{}", message);
sshwarden_agent::ControlResponse::ok(&message)
}
Err(e) => sshwarden_agent::ControlResponse::err(&format!(
"Sync failed after login: {}",
Expand Down Expand Up @@ -4728,53 +4734,21 @@ async fn try_restore_api_session_hello(
}
}

/// Sync SSH keys from the Bitwarden API and reload into the agent.
#[allow(clippy::too_many_arguments)]
async fn do_sync(
api_client: &Arc<RwLock<Option<sshwarden_api::BitwardenClient>>>,
async fn reconcile_synced_key_state(
key_tuples: &[(String, String, String)],
cached_key_tuples: &CachedKeyTuples,
public_key_identity_tuples: &CachedKeyTuples,
local_key_cache_data: &Arc<RwLock<Option<sshwarden_config::cache::LocalKeyCacheFile>>>,
local_cache_key_state: &LocalCacheKeyHandle,
authorization_memory: &AuthorizationMemorySet,
key_material_fingerprints: &KeyMaterialFingerprints,
vault_locked: &Arc<std::sync::atomic::AtomicBool>,
agent: &mut sshwarden_agent::SshWardenAgent,
key_names: &Arc<RwLock<std::collections::HashMap<String, String>>>,
notification_state: &Arc<RwLock<NotificationRuntimeState>>,
) -> Result<usize, String> {
let client_guard = api_client.read().await;
let client = match *client_guard {
Some(ref c) => c,
None => return Err("Not authenticated. Run 'sshwarden login'.".to_string()),
};

let keys = client
.sync_ssh_keys()
.await
.map_err(|e| format!("Sync failed: {}", e))?;

let key_tuples: Vec<(String, String, String)> = keys
.iter()
.map(|k| {
(
(*k.private_key_pem).clone(),
k.name.clone(),
k.cipher_id.clone(),
)
})
.collect();
let count = key_tuples.len();
if let Err(e) = write_key_selector_files(&keys) {
tracing::warn!("Failed to write key selector files: {}", e);
}
if let Err(e) = sync_managed_ssh_config_with_bindings(&keys) {
tracing::warn!("Failed to sync managed SSH config: {}", e);
}
) {
let old_fingerprints = key_material_fingerprints.read().await.clone();
let (cleared_memory, new_fingerprints) = clear_authorization_memory_for_changed_keys_async(
&old_fingerprints,
&key_tuples,
key_tuples,
authorization_memory,
)
.await;
Expand All @@ -4788,17 +4762,18 @@ async fn do_sync(
public_key_identity_tuples
.write()
.await
.set(key_tuples.clone());
cached_key_tuples.write().await.set(key_tuples.clone());
.set(key_tuples.to_vec());
cached_key_tuples.write().await.set(key_tuples.to_vec());
*key_material_fingerprints.write().await = new_fingerprints;

if let (Some(existing_cache), Some(local_cache_key)) = (
local_key_cache_data.read().await.as_ref().cloned(),
local_cache_key_state.read().await.clone_key(),
) {
match refresh_envelope_local_key_cache(&key_tuples, &existing_cache, &local_cache_key) {
match refresh_envelope_local_key_cache(key_tuples, &existing_cache, &local_cache_key) {
Ok(cache) => {
*local_key_cache_data.write().await = Some(cache);
local_cache_key_state.write().await.set(local_cache_key);
tracing::info!("Local key cache refreshed after sync");
}
Err(e) => {
Expand All @@ -4815,6 +4790,69 @@ async fn do_sync(
}
}
}
}

/// Sync SSH keys from the Bitwarden API and reload into the agent.
#[allow(clippy::too_many_arguments)]
async fn do_sync(
api_client: &Arc<RwLock<Option<sshwarden_api::BitwardenClient>>>,
cached_key_tuples: &CachedKeyTuples,
public_key_identity_tuples: &CachedKeyTuples,
local_key_cache_data: &Arc<RwLock<Option<sshwarden_config::cache::LocalKeyCacheFile>>>,
local_cache_key_state: &LocalCacheKeyHandle,
authorization_memory: &AuthorizationMemorySet,
key_material_fingerprints: &KeyMaterialFingerprints,
vault_locked: &Arc<std::sync::atomic::AtomicBool>,
agent: &mut sshwarden_agent::SshWardenAgent,
key_names: &Arc<RwLock<std::collections::HashMap<String, String>>>,
notification_state: &Arc<RwLock<NotificationRuntimeState>>,
) -> Result<usize, String> {
let client_guard = api_client.read().await;
let client = match *client_guard {
Some(ref c) => c,
None => return Err("Not authenticated. Run 'sshwarden login'.".to_string()),
};

if !client.has_user_key() {
return Err(
"Bitwarden session cannot decrypt vault data. Run `sshwarden login` to re-authenticate with your master password."
.to_string(),
);
}

let keys = client
.sync_ssh_keys()
.await
.map_err(|e| format!("Sync failed: {}", e))?;

let key_tuples: Vec<(String, String, String)> = keys
.iter()
.map(|k| {
(
(*k.private_key_pem).clone(),
k.name.clone(),
k.cipher_id.clone(),
)
})
.collect();
let count = key_tuples.len();
if let Err(e) = write_key_selector_files(&keys) {
tracing::warn!("Failed to write key selector files: {}", e);
}
if let Err(e) = sync_managed_ssh_config_with_bindings(&keys) {
tracing::warn!("Failed to sync managed SSH config: {}", e);
}
reconcile_synced_key_state(
&key_tuples,
cached_key_tuples,
public_key_identity_tuples,
local_key_cache_data,
local_cache_key_state,
authorization_memory,
key_material_fingerprints,
notification_state,
)
.await;

// Update key_names
{
Expand Down
Loading