use crate::error::{KeyringError, Result};
use crate::Keyring;
use byteorder::{ByteOrder, LittleEndian};
use std::ffi::OsStr;
use std::iter::once;
use std::mem::MaybeUninit;
use std::os::windows::ffi::OsStrExt;
use std::slice;
use std::str;
use winapi::shared::minwindef::FILETIME;
use winapi::shared::winerror::{ERROR_NOT_FOUND, ERROR_NO_SUCH_LOGON_SESSION};
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::wincred::{
CredDeleteW, CredFree, CredReadW, CredWriteW, CREDENTIALW, CRED_PERSIST_ENTERPRISE,
CRED_TYPE_GENERIC, PCREDENTIALW, PCREDENTIAL_ATTRIBUTEW,
};
pub struct WindowsKeyringManager {
application: String,
}
impl WindowsKeyringManager {
pub fn new(application: &str) -> Result<Self> {
Ok(Self {
application: application.to_owned(),
})
}
pub fn with_keyring<F, T>(&self, service: &str, key: &str, func: F) -> Result<T>
where
F: FnOnce(&mut dyn Keyring) -> Result<T>,
{
let mut kr = WindowsKeyring::new(&self.application, service, key)?;
func(&mut kr)
}
}
pub struct WindowsKeyring<'a> {
application: &'a str,
service: &'a str,
key: &'a str,
}
impl<'a> WindowsKeyring<'a> {
fn new(application: &'a str, service: &'a str, key: &'a str) -> Result<WindowsKeyring<'a>> {
Ok(Self {
application,
service,
key,
})
}
fn get_account_name(&self) -> String {
[
crate::escape(self.application),
crate::escape(self.key),
crate::escape(self.service),
]
.join("\t")
}
}
impl<'a> Keyring for WindowsKeyring<'a> {
fn set_value(&mut self, value: &str) -> Result<()> {
let flags = 0;
let cred_type = CRED_TYPE_GENERIC;
let target_name: String = self.get_account_name();
let mut target_name = to_wstr(&target_name);
let mut empty_str = to_wstr("");
let last_written = FILETIME {
dwLowDateTime: 0,
dwHighDateTime: 0,
};
let blob_u16 = to_wstr_no_null(value);
let mut blob = vec![0; blob_u16.len() * 2];
LittleEndian::write_u16_into(&blob_u16, &mut blob);
let blob_len = blob.len() as u32;
let persist = CRED_PERSIST_ENTERPRISE;
let attribute_count = 0;
let attributes: PCREDENTIAL_ATTRIBUTEW = std::ptr::null_mut();
let mut key = to_wstr(self.key);
let mut credential = CREDENTIALW {
Flags: flags,
Type: cred_type,
TargetName: target_name.as_mut_ptr(),
Comment: empty_str.as_mut_ptr(),
LastWritten: last_written,
CredentialBlobSize: blob_len,
CredentialBlob: blob.as_mut_ptr(),
Persist: persist,
AttributeCount: attribute_count,
Attributes: attributes,
TargetAlias: empty_str.as_mut_ptr(),
UserName: key.as_mut_ptr(),
};
let pcredential: PCREDENTIALW = &mut credential;
match unsafe { CredWriteW(pcredential, 0) } {
0 => Err(KeyringError::WindowsVaultError),
_ => Ok(()),
}
}
fn get_value(&self) -> Result<String> {
let mut pcredential = MaybeUninit::uninit();
let target_name: String = self.get_account_name();
let target_name = to_wstr(&target_name);
let cred_type = CRED_TYPE_GENERIC;
match unsafe { CredReadW(target_name.as_ptr(), cred_type, 0, pcredential.as_mut_ptr()) } {
0 => unsafe {
match GetLastError() {
ERROR_NOT_FOUND => Err(KeyringError::NoPasswordFound),
ERROR_NO_SUCH_LOGON_SESSION => Err(KeyringError::NoBackendFound),
_ => Err(KeyringError::WindowsVaultError),
}
},
_ => {
let pcredential = unsafe { pcredential.assume_init() };
let credential: CREDENTIALW = unsafe { *pcredential };
let blob_pointer: *const u8 = credential.CredentialBlob;
let blob_len: usize = credential.CredentialBlobSize as usize;
let mut blob_u16 = vec![0; blob_len / 2];
if !blob_pointer.is_null() && blob_len > 0 {
let blob: &[u8] = unsafe { slice::from_raw_parts(blob_pointer, blob_len) };
LittleEndian::read_u16_into(&blob, &mut blob_u16);
}
let password = String::from_utf16(&blob_u16)
.map(|pass| pass.to_string())
.map_err(|_| KeyringError::WindowsVaultError);
unsafe {
CredFree(pcredential as *mut _);
}
password
}
}
}
fn delete_value(&mut self) -> Result<()> {
let target_name: String = self.get_account_name();
let cred_type = CRED_TYPE_GENERIC;
let target_name = to_wstr(&target_name);
match unsafe { CredDeleteW(target_name.as_ptr(), cred_type, 0) } {
0 => unsafe {
match GetLastError() {
ERROR_NOT_FOUND => Err(KeyringError::NoPasswordFound),
ERROR_NO_SUCH_LOGON_SESSION => Err(KeyringError::NoBackendFound),
_ => Err(KeyringError::WindowsVaultError),
}
},
_ => Ok(()),
}
}
}
fn to_wstr(s: &str) -> Vec<u16> {
OsStr::new(s).encode_wide().chain(once(0)).collect()
}
fn to_wstr_no_null(s: &str) -> Vec<u16> {
OsStr::new(s).encode_wide().collect()
}