use std::{
fmt::{Debug, Display, Formatter, Result as FmtResult},
marker::PhantomData,
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
};
const WINDOWS_TICKS_PER_SEC: u64 = 10_000_000;
const UNIX_EPOCH_IN_WINDOWS_TICKS: u64 = 11644473600 * WINDOWS_TICKS_PER_SEC;
fn windows_timestamp_to_system_time(windows_ticks: i64) -> SystemTime {
let ticks_since_1601 = windows_ticks as u64;
let ticks_since_1970 = ticks_since_1601.saturating_sub(UNIX_EPOCH_IN_WINDOWS_TICKS);
let secs = ticks_since_1970 / WINDOWS_TICKS_PER_SEC;
let nanos = ((ticks_since_1970 % WINDOWS_TICKS_PER_SEC) * 100) as u32;
UNIX_EPOCH + Duration::new(secs, nanos)
}
use crate::{KERBEROS, NEGOTIATE, cred::handle::CredentialsHandle};
pub use kenobi_core::cred::usage::{Both, Inbound, Outbound};
use kenobi_core::mech::Mechanism;
use windows::{
Win32::Security::{
Authentication::Identity::{
AcquireCredentialsHandleW, SECPKG_CRED, SECPKG_CRED_BOTH, SECPKG_CRED_INBOUND, SECPKG_CRED_OUTBOUND,
},
Credentials::SecHandle,
},
core::PCWSTR,
};
#[derive(Debug)]
pub struct Error(windows_result::Error);
impl std::error::Error for Error {}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
<windows_result::Error as Display>::fmt(&self.0, f)
}
}
mod handle {
use std::ffi::c_void;
use windows::{
Win32::Security::{
Authentication::Identity::{
FreeContextBuffer, FreeCredentialsHandle, QueryCredentialsAttributesW, SECPKG_CRED_ATTR_NAMES,
SecPkgCredentials_NamesW,
},
Credentials::SecHandle,
},
core::PCWSTR,
};
pub struct CredentialsHandle(SecHandle);
impl CredentialsHandle {
pub unsafe fn from_raw(sec: SecHandle) -> Self {
Self(sec)
}
pub fn as_raw_handle(&self) -> &SecHandle {
&self.0
}
pub fn get_identity(&self) -> windows_result::Result<String> {
let mut names = SecPkgCredentials_NamesW::default();
unsafe {
QueryCredentialsAttributesW(
self.as_raw_handle(),
SECPKG_CRED_ATTR_NAMES,
std::ptr::from_mut(&mut names) as *mut c_void,
)?
};
let name = PCWSTR(names.sUserName);
let rust_st = unsafe { name.to_string() }.unwrap();
unsafe { FreeContextBuffer(names.sUserName as *mut c_void)? };
Ok(rust_st)
}
}
impl Drop for CredentialsHandle {
fn drop(&mut self) {
let _ = unsafe { FreeCredentialsHandle(&self.0) };
}
}
impl PartialEq for CredentialsHandle {
fn eq(&self, other: &Self) -> bool {
matches!((self.get_identity(), other.get_identity()), (Ok(v), Ok(u)) if v == u)
}
}
}
pub struct Credentials<Usage> {
handle: handle::CredentialsHandle,
mechanism: Mechanism,
valid_until: Instant,
_usage: PhantomData<Usage>,
}
impl<Usage: CredentialsUsage> Credentials<Usage> {
pub fn acquire_negotiate(principal: Option<&str>) -> Result<Credentials<Usage>, Error> {
Credentials::acquire(principal, Mechanism::Spnego)
}
pub fn acquire_pure_kerberos(principal: Option<&str>) -> Result<Credentials<Usage>, Error> {
Credentials::acquire(principal, Mechanism::KerberosV5)
}
pub fn acquire(principal: Option<&str>, mechanism: Mechanism) -> Result<Credentials<Usage>, Error> {
let mut handle = SecHandle::default();
let mut expiry_ticks = 0;
let princ_wide = principal.map(crate::to_wide);
let princ_ref = princ_wide.as_ref().map(|b| b.as_ptr());
let mech = match mechanism {
Mechanism::KerberosV5 => KERBEROS,
Mechanism::Spnego => NEGOTIATE,
};
let res = unsafe {
AcquireCredentialsHandleW(
PCWSTR(princ_ref.unwrap_or_default()),
mech,
Usage::to_usage(),
None,
None,
None,
None,
&mut handle,
Some(&mut expiry_ticks),
)
};
let expiry = windows_timestamp_to_system_time(expiry_ticks);
let valid_until = Instant::now() + expiry.duration_since(SystemTime::now()).unwrap_or(Duration::ZERO);
match res {
Ok(()) => {
let handle = unsafe { CredentialsHandle::from_raw(handle) };
Ok(Self {
handle,
mechanism,
valid_until,
_usage: PhantomData,
})
}
Err(e) => Err(Error(e)),
}
}
pub fn mechanism(&self) -> Mechanism {
self.mechanism
}
pub fn valid_until(&self) -> Instant {
self.valid_until
}
}
impl Credentials<Inbound> {
pub fn inbound(principal: Option<&str>, mechanism: Mechanism) -> Result<Self, Error> {
Credentials::acquire(principal, mechanism)
}
}
impl Credentials<Outbound> {
pub fn outbound(principal: Option<&str>, mechanism: Mechanism) -> Result<Self, Error> {
Credentials::acquire(principal, mechanism)
}
}
impl Credentials<Both> {
pub fn both(principal: Option<&str>, mechanism: Mechanism) -> Result<Self, Error> {
Credentials::acquire(principal, mechanism)
}
}
impl<Usage> Credentials<Usage> {
pub(crate) fn as_raw_handle(&self) -> &SecHandle {
self.handle.as_raw_handle()
}
}
impl<Usage> AsRef<Credentials<Usage>> for Credentials<Usage> {
fn as_ref(&self) -> &Credentials<Usage> {
self
}
}
impl<U> PartialEq for Credentials<U> {
fn eq(&self, other: &Self) -> bool {
self.handle == other.handle
}
}
impl<U> Debug for Credentials<U> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.debug_struct("Credentials")
.field("mechanism", &self.mechanism)
.field("valid_until", &self.valid_until)
.finish()
}
}
pub trait CredentialsUsage {
fn to_usage() -> SECPKG_CRED;
}
impl CredentialsUsage for Inbound {
fn to_usage() -> SECPKG_CRED {
SECPKG_CRED_INBOUND
}
}
impl CredentialsUsage for Outbound {
fn to_usage() -> SECPKG_CRED {
SECPKG_CRED_OUTBOUND
}
}
impl CredentialsUsage for Both {
fn to_usage() -> SECPKG_CRED {
SECPKG_CRED(SECPKG_CRED_BOTH)
}
}
impl From<Credentials<Both>> for Credentials<Inbound> {
fn from(
Credentials {
handle,
valid_until,
mechanism,
..
}: Credentials<Both>,
) -> Self {
Credentials {
valid_until,
mechanism,
handle,
_usage: PhantomData,
}
}
}
impl From<Credentials<Both>> for Credentials<Outbound> {
fn from(
Credentials {
handle,
valid_until,
mechanism,
..
}: Credentials<Both>,
) -> Self {
Credentials {
valid_until,
mechanism,
handle,
_usage: PhantomData,
}
}
}