use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt as _;
use tracing::{debug, error};
use crate::error::SynqroError;
use crate::keychain::KeychainProvider;
#[cfg(target_os = "windows")]
use winapi::{
shared::winerror::{ERROR_NOT_FOUND, HRESULT},
um::{
wincred::{
CredDeleteW, CredFree, CredReadW, CredWriteW, CREDENTIALW, CRED_TYPE_GENERIC,
PCREDENTIALW,
},
winnt::LPWSTR,
},
};
fn credential_target_name(service: &str, account: &str) -> String {
format!("synqro/{}/{}", service, account)
}
fn to_wide_null(s: &str) -> Vec<u16> {
s.encode_utf16().chain(std::iter::once(0u16)).collect()
}
pub struct WindowsKeychain {
}
impl WindowsKeychain {
pub fn new() -> Result<Self, SynqroError> {
debug!("Windows Credential Manager backend initialised");
Ok(Self {})
}
}
impl KeychainProvider for WindowsKeychain {
#[cfg(target_os = "windows")]
fn load_secret(&self, service: &str, account: &str) -> Result<Vec<u8>, SynqroError> {
let target = credential_target_name(service, account);
let target_wide = to_wide_null(&target);
let secret_bytes = unsafe {
let mut cred_ptr: PCREDENTIALW = std::ptr::null_mut();
let success = CredReadW(
target_wide.as_ptr(),
CRED_TYPE_GENERIC,
0, &mut cred_ptr,
);
if success == 0 {
let last_err = winapi::um::errhandlingapi::GetLastError();
if last_err == ERROR_NOT_FOUND {
return Err(SynqroError::Keychain(format!(
"Windows Credential Manager: credential '{}' not found",
target
)));
}
error!(
last_error = last_err,
target = %target,
"CredReadW failed"
);
return Err(SynqroError::Permission(format!(
"CredReadW failed for target `{}`",
target
)));
}
if cred_ptr.is_null() {
return Err(SynqroError::Keychain(
"CredReadW returned null credential pointer".to_owned(),
));
}
let blob_size = (*cred_ptr).CredentialBlobSize as usize;
let blob_ptr = (*cred_ptr).CredentialBlob;
let bytes = if blob_ptr.is_null() || blob_size == 0 {
Vec::new()
} else {
std::slice::from_raw_parts(blob_ptr, blob_size).to_vec()
};
CredFree(cred_ptr as *mut _);
bytes
};
debug!(
service = service,
account = account,
"Windows Credential Manager: secret loaded"
);
Ok(secret_bytes)
}
#[cfg(target_os = "windows")]
fn store_secret(&self, service: &str, account: &str, secret: &[u8]) -> Result<(), SynqroError> {
let target = credential_target_name(service, account);
let target_wide = to_wide_null(&target);
let username_wide = to_wide_null(account);
let comment_wide = to_wide_null("Synqro managed credential");
unsafe {
let blob_ptr = secret.as_ptr() as *mut u8;
let mut cred = winapi::um::wincred::CREDENTIALW {
Flags: 0,
Type: CRED_TYPE_GENERIC,
TargetName: target_wide.as_ptr() as LPWSTR,
Comment: comment_wide.as_ptr() as LPWSTR,
LastWritten: winapi::shared::minwindef::FILETIME {
dwLowDateTime: 0,
dwHighDateTime: 0,
},
CredentialBlobSize: secret.len() as u32,
CredentialBlob: blob_ptr,
Persist: winapi::um::wincred::CRED_PERSIST_LOCAL_MACHINE,
AttributeCount: 0,
Attributes: std::ptr::null_mut(),
TargetAlias: std::ptr::null_mut(),
UserName: username_wide.as_ptr() as LPWSTR,
};
let success = CredWriteW(&mut cred, 0);
if success == 0 {
let last_err = winapi::um::errhandlingapi::GetLastError();
error!(
last_error = last_err,
target = %target,
"CredWriteW failed"
);
return Err(SynqroError::Keychain(format!(
"Windows Credential Manager: CredWriteW failed (error {})",
last_err
)));
}
}
debug!(
service = service,
account = account,
"Windows Credential Manager: secret stored"
);
Ok(())
}
#[cfg(target_os = "windows")]
fn delete_secret(&self, service: &str, account: &str) -> Result<(), SynqroError> {
let target = credential_target_name(service, account);
let target_wide = to_wide_null(&target);
unsafe {
let success = CredDeleteW(
target_wide.as_ptr(),
CRED_TYPE_GENERIC,
0, );
if success == 0 {
let last_err = winapi::um::errhandlingapi::GetLastError();
if last_err == ERROR_NOT_FOUND {
debug!(
target = %target,
"Windows Credential Manager: credential not found during delete (already absent)"
);
return Ok(());
}
error!(
last_error = last_err,
target = %target,
"CredDeleteW failed"
);
return Err(SynqroError::Keychain(format!(
"Windows Credential Manager: CredDeleteW failed (error {})",
last_err
)));
}
}
debug!(
service = service,
account = account,
"Windows Credential Manager: secret deleted"
);
Ok(())
}
#[cfg(not(target_os = "windows"))]
fn load_secret(&self, _service: &str, _account: &str) -> Result<Vec<u8>, SynqroError> {
Err(SynqroError::Permission(format!(
"Windows Keychain `load_secret` stub called on a non-Windows build"
)))
}
#[cfg(not(target_os = "windows"))]
fn store_secret(
&self,
_service: &str,
_account: &str,
_secret: &[u8],
) -> Result<(), SynqroError> {
Err(SynqroError::Permission(format!(
"Windows Keychain `store_secret` stub called on a non-Windows build"
)))
}
#[cfg(not(target_os = "windows"))]
fn delete_secret(&self, _service: &str, _account: &str) -> Result<(), SynqroError> {
Err(SynqroError::Permission(format!(
"Windows Keychain `delete_secret` stub called on a non-Windows build"
)))
}
}