use super::windows::wide;
use std::ffi::c_void;
use std::ptr::null_mut;
#[link(name = "advapi32")]
unsafe extern "system" {
fn CredWriteW(credential: *const Credential, flags: u32) -> i32;
fn CredReadW(target: *const u16, kind: u32, flags: u32, credential: *mut *mut Credential) -> i32;
fn CredDeleteW(target: *const u16, kind: u32, flags: u32) -> i32;
fn CredFree(buffer: *mut c_void);
}
unsafe extern "system" {
fn GetLastError() -> u32;
}
#[repr(C)]
struct Credential {
flags: u32,
kind: u32,
target_name: *mut u16,
comment: *mut u16,
last_written: [u32; 2],
blob_size: u32,
blob: *mut u8,
persist: u32,
attribute_count: u32,
attributes: *mut c_void,
target_alias: *mut u16,
user_name: *mut u16,
}
const CRED_TYPE_GENERIC: u32 = 1;
const CRED_PERSIST_LOCAL_MACHINE: u32 = 2;
const ERROR_NOT_FOUND: u32 = 1168;
fn target(service: &str, account: &str) -> Vec<u16> {
wide(format!("{service}/{account}"))
}
fn write_one(target: &[u16], bytes: &[u8]) -> Result<(), String> {
let user = wide("aura");
let credential = Credential {
flags: 0,
kind: CRED_TYPE_GENERIC,
target_name: target.as_ptr().cast_mut(),
comment: null_mut(),
last_written: [0; 2],
blob_size: bytes.len() as u32,
blob: bytes.as_ptr().cast_mut(),
persist: CRED_PERSIST_LOCAL_MACHINE,
attribute_count: 0,
attributes: null_mut(),
target_alias: null_mut(),
user_name: user.as_ptr().cast_mut(),
};
if unsafe { CredWriteW(&credential, 0) } != 0 {
Ok(())
} else {
Err(format!("CredWriteW falló (error {})", unsafe { GetLastError() }))
}
}
fn read_one(target: &[u16]) -> Option<Vec<u8>> {
let mut found: *mut Credential = null_mut();
if unsafe { CredReadW(target.as_ptr(), CRED_TYPE_GENERIC, 0, &mut found) } == 0 || found.is_null() {
return None;
}
unsafe {
let credential = &*found;
let bytes = match credential.blob.is_null() {
true => Vec::new(),
false => std::slice::from_raw_parts(credential.blob, credential.blob_size as usize).to_vec(),
};
CredFree(found.cast());
Some(bytes)
}
}
fn delete_one(target: &[u16]) -> Result<(), String> {
if unsafe { CredDeleteW(target.as_ptr(), CRED_TYPE_GENERIC, 0) } != 0 {
return Ok(());
}
match unsafe { GetLastError() } {
ERROR_NOT_FOUND => Ok(()),
code => Err(format!("CredDeleteW falló (error {code})")),
}
}
pub fn secret_set(service: &str, account: &str, value: &str) -> Result<(), String> {
write_one(&target(service, account), value.as_bytes())
}
pub fn secret_get(service: &str, account: &str) -> Option<String> {
String::from_utf8(read_one(&target(service, account))?).ok()
}
pub fn secret_remove(service: &str, account: &str) -> Result<(), String> {
delete_one(&target(service, account))
}