#[cfg(feature = "tsssp")]
pub mod sspi_cred_ssp;
mod ts_request;
use std::io;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::{FromPrimitive, ToPrimitive};
use rand::rngs::{StdRng, SysRng};
use rand::{RngCore, SeedableRng};
use ts_request::{NONCE_SIZE, TS_REQUEST_VERSION};
pub use ts_request::{NStatusCode, TsRequest, read_ts_credentials, write_ts_credentials};
#[cfg(feature = "tsssp")]
use self::sspi_cred_ssp::SspiCredSsp;
use crate::builders::{AcquireCredentialsHandle, ChangePassword, WithoutCredentialUse};
use crate::crypto::compute_sha256;
use crate::generator::{
Generator, GeneratorAcceptSecurityContext, GeneratorChangePassword, GeneratorInitSecurityContext, NetworkRequest,
YieldPointLocal,
};
use crate::kerberos::{self, Kerberos};
use crate::ntlm::{self, Ntlm, NtlmConfig, SIGNATURE_SIZE};
use crate::pku2u::{self, Pku2u, Pku2uConfig};
use crate::{
AcceptSecurityContextResult, AcquireCredentialsHandleResult, AuthIdentity, AuthIdentityBuffers, BufferType,
CertContext, CertTrustStatus, ClientRequestFlags, ConnectionInfo, ContextNames, ContextSizes, CredentialUse,
Credentials, CredentialsBuffers, DataRepresentation, DecryptionFlags, EncryptionFlags, Error, ErrorKind,
FilledAcceptSecurityContext, FilledAcquireCredentialsHandle, FilledInitializeSecurityContext,
InitializeSecurityContextResult, Negotiate, NegotiateConfig, PackageInfo, SecurityBuffer, SecurityBufferRef,
SecurityStatus, ServerRequestFlags, Sspi, SspiEx, SspiImpl, StreamSizes, Username, negotiate,
};
pub const EARLY_USER_AUTH_RESULT_PDU_SIZE: usize = 4;
const HASH_MAGIC_LEN: usize = 38;
const SERVER_CLIENT_HASH_MAGIC: &[u8; HASH_MAGIC_LEN] = b"CredSSP Server-To-Client Binding Hash\0";
const CLIENT_SERVER_HASH_MAGIC: &[u8; HASH_MAGIC_LEN] = b"CredSSP Client-To-Server Binding Hash\0";
pub trait CredentialsProxy {
type AuthenticationData;
fn auth_data_by_user(&mut self, username: &Username) -> io::Result<Self::AuthenticationData>;
fn auth_data_candidates_by_user(&mut self, username: &Username) -> io::Result<Vec<Self::AuthenticationData>> {
Ok(vec![self.auth_data_by_user(username)?])
}
fn auth_data(&mut self) -> io::Result<Vec<Self::AuthenticationData>>;
}
macro_rules! try_cred_ssp_server {
($e:expr, $ts_request:ident) => {
match $e {
Ok(v) => v,
Err(e) => {
let error = crate::Error::from(e);
$ts_request.error_code = Some(construct_error(&error));
return Err(ServerError {
ts_request: Some(Box::new($ts_request)),
error,
});
}
}
};
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum CredSspMode {
WithCredentials,
CredentialLess,
}
#[derive(Debug, Clone)]
pub enum ClientState {
ReplyNeeded(TsRequest),
FinalMessage(TsRequest),
}
#[derive(Debug, Clone)]
pub enum ServerState {
ReplyNeeded(TsRequest),
Finished(AuthIdentity),
}
#[derive(Debug, Clone)]
pub struct ServerError {
pub ts_request: Option<Box<TsRequest>>,
pub error: Error,
}
#[derive(Debug, Copy, Clone, FromPrimitive, ToPrimitive)]
#[repr(u32)]
pub enum EarlyUserAuthResult {
Success = 0,
AccessDenied = 5,
}
impl EarlyUserAuthResult {
pub fn from_buffer(mut stream: impl io::Read) -> Result<Self, io::Error> {
let result = stream.read_u32::<LittleEndian>()?;
EarlyUserAuthResult::from_u32(result).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("got invalid Early User Authorization Result: {result:x}"),
)
})
}
pub fn to_buffer(self, mut stream: impl io::Write) -> Result<(), io::Error> {
stream.write_u32::<LittleEndian>(self.to_u32().unwrap())
}
pub fn buffer_len(self) -> usize {
EARLY_USER_AUTH_RESULT_PDU_SIZE
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum CredSspState {
NegoToken,
AuthInfo,
Final,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum EndpointType {
Client,
Server,
}
#[derive(Debug, Clone)]
pub enum ClientMode {
Negotiate(NegotiateConfig),
Pku2u(Box<Pku2uConfig>),
Ntlm(NtlmConfig),
}
#[derive(Debug)]
pub struct CredSspClient {
state: CredSspState,
context: Option<CredSspContext>,
credentials: Credentials,
public_key: Vec<u8>,
cred_ssp_mode: CredSspMode,
client_nonce: [u8; NONCE_SIZE],
credentials_handle: Option<CredentialsBuffers>,
ts_request_version: u32,
client_mode: Option<ClientMode>,
service_principal_name: String,
}
impl CredSspClient {
pub fn new(
public_key: Vec<u8>,
credentials: Credentials,
cred_ssp_mode: CredSspMode,
client_mode: ClientMode,
service_principal_name: String,
) -> crate::Result<Self> {
let mut rng = StdRng::try_from_rng(&mut SysRng)?;
let mut client_nonce = [0; NONCE_SIZE];
rng.fill_bytes(&mut client_nonce);
Ok(Self {
state: CredSspState::NegoToken,
context: None,
credentials,
public_key,
cred_ssp_mode,
client_nonce,
credentials_handle: None,
ts_request_version: TS_REQUEST_VERSION,
client_mode: Some(client_mode),
service_principal_name,
})
}
pub fn new_with_version(
public_key: Vec<u8>,
credentials: Credentials,
cred_ssp_mode: CredSspMode,
ts_request_version: u32,
client_mode: ClientMode,
service_principal_name: String,
) -> crate::Result<Self> {
let mut rng = StdRng::try_from_rng(&mut SysRng)?;
let mut client_nonce = [0; NONCE_SIZE];
rng.fill_bytes(&mut client_nonce);
Ok(Self {
state: CredSspState::NegoToken,
context: None,
credentials,
public_key,
cred_ssp_mode,
client_nonce,
credentials_handle: None,
ts_request_version,
client_mode: Some(client_mode),
service_principal_name,
})
}
#[instrument(fields(state = ?self.state), skip_all)]
pub fn process(
&mut self,
ts_request: TsRequest,
) -> Generator<'_, NetworkRequest, crate::Result<Vec<u8>>, crate::Result<ClientState>> {
Generator::<NetworkRequest, crate::Result<Vec<u8>>, crate::Result<ClientState>>::new(
move |mut yield_point| async move { self.process_impl(&mut yield_point, ts_request).await },
)
}
async fn process_impl(
&mut self,
yield_point: &mut YieldPointLocal,
mut ts_request: TsRequest,
) -> crate::Result<ClientState> {
ts_request.check_error()?;
if let Some(ref mut context) = self.context {
context.check_peer_version(ts_request.version)?;
} else {
self.context = match self
.client_mode
.take()
.expect("CredSsp client mode should never be empty")
{
ClientMode::Negotiate(negotiate_config) => Some(CredSspContext::new(SspiContext::Negotiate(
Negotiate::new_client(negotiate_config)?,
))),
ClientMode::Pku2u(pku2u) => Some(CredSspContext::new(SspiContext::Pku2u(
Pku2u::new_client_from_config(*pku2u)?,
))),
ClientMode::Ntlm(ntlm) => Some(CredSspContext::new(SspiContext::Ntlm(Ntlm::with_config(ntlm)))),
};
let sspi_context = &mut self
.context
.as_mut()
.expect("Should not panic because the CredSSP context is set before")
.sspi_context;
let builder = AcquireCredentialsHandle::<'_, _, _, WithoutCredentialUse>::new();
let AcquireCredentialsHandleResult { credentials_handle, .. } = builder
.with_auth_data(&self.credentials)
.with_credential_use(CredentialUse::Outbound)
.execute(sspi_context)?;
self.credentials_handle = credentials_handle;
}
ts_request.version = self.ts_request_version;
match self.state {
CredSspState::NegoToken => {
let mut input_token = [SecurityBuffer::new(
ts_request.nego_tokens.take().unwrap_or_default(),
BufferType::Token,
)];
let mut output_token = vec![SecurityBuffer::new(Vec::with_capacity(1024), BufferType::Token)];
let mut credentials_handle = self.credentials_handle.take();
let cred_ssp_context = self
.context
.as_mut()
.expect("Should not panic because the CredSSP context is set before");
let mut builder = cred_ssp_context
.sspi_context
.initialize_security_context()
.with_credentials_handle(&mut credentials_handle)
.with_context_requirements(
ClientRequestFlags::MUTUAL_AUTH
| ClientRequestFlags::USE_SESSION_KEY
| ClientRequestFlags::INTEGRITY
| ClientRequestFlags::CONFIDENTIALITY,
)
.with_target_data_representation(DataRepresentation::Native)
.with_target_name(&self.service_principal_name)
.with_input(&mut input_token)
.with_output(&mut output_token);
let result = cred_ssp_context
.sspi_context
.initialize_security_context_impl(yield_point, &mut builder)
.await?;
self.credentials_handle = credentials_handle;
ts_request.nego_tokens = Some(output_token.remove(0).buffer);
if result.status == SecurityStatus::Ok {
debug!("CredSSp finished NLA stage.");
let peer_version =
self.context.as_ref().unwrap().peer_version.expect(
"An encrypt public key client function cannot be fired without any incoming TSRequest",
);
ts_request.pub_key_auth = Some(
self.context
.as_mut()
.expect("Should not panic because the CredSSP context is set before")
.encrypt_public_key(
self.public_key.as_ref(),
EndpointType::Client,
&Some(self.client_nonce),
peer_version,
)?,
);
ts_request.client_nonce = Some(self.client_nonce);
if let Some(nego_tokens) = &ts_request.nego_tokens
&& nego_tokens.is_empty()
{
ts_request.nego_tokens = None;
}
self.state = CredSspState::AuthInfo;
}
Ok(ClientState::ReplyNeeded(ts_request))
}
CredSspState::AuthInfo => {
ts_request.nego_tokens = None;
let pub_key_auth = ts_request.pub_key_auth.take().ok_or_else(|| {
Error::new(
ErrorKind::InvalidToken,
String::from("CredSSP client expected an encrypted public key"),
)
})?;
let peer_version = self
.context
.as_ref()
.expect("Should not panic because the CredSSP context is set before")
.peer_version
.expect("An decrypt public key client function cannot be fired without any incoming TSRequest");
self.context.as_mut().unwrap().decrypt_public_key(
self.public_key.as_ref(),
pub_key_auth.as_ref(),
EndpointType::Client,
&Some(self.client_nonce),
peer_version,
)?;
ts_request.auth_info = Some(
self.context
.as_mut()
.unwrap()
.encrypt_ts_credentials(self.credentials_handle.as_ref().unwrap(), self.cred_ssp_mode)?,
);
debug!("tscredentials has been written");
self.state = CredSspState::Final;
Ok(ClientState::FinalMessage(ts_request))
}
CredSspState::Final => Err(Error::new(
ErrorKind::OutOfSequence,
"CredSSP client's 'process' method must not be fired after the 'Finished' state",
)),
}
}
}
#[derive(Debug, Clone)]
pub enum ServerMode {
Negotiate(NegotiateConfig),
Pku2u(Box<Pku2uConfig>),
Ntlm(NtlmConfig),
}
#[derive(Debug)]
pub struct CredSspServer<C: CredentialsProxy<AuthenticationData = AuthIdentity>> {
credentials: C,
state: CredSspState,
context: Option<CredSspContext>,
public_key: Vec<u8>,
credentials_handle: Option<CredentialsBuffers>,
ts_request_version: u32,
context_config: Option<ServerMode>,
}
impl<C: CredentialsProxy<AuthenticationData = AuthIdentity> + Send> CredSspServer<C> {
pub fn new(public_key: Vec<u8>, credentials: C, client_mode: ServerMode) -> crate::Result<Self> {
Ok(Self {
state: CredSspState::NegoToken,
context: None,
credentials,
public_key,
credentials_handle: None,
ts_request_version: TS_REQUEST_VERSION,
context_config: Some(client_mode),
})
}
pub fn new_with_version(
public_key: Vec<u8>,
credentials: C,
ts_request_version: u32,
client_mode: ServerMode,
) -> crate::Result<Self> {
Ok(Self {
state: CredSspState::NegoToken,
context: None,
credentials,
public_key,
credentials_handle: None,
ts_request_version,
context_config: Some(client_mode),
})
}
#[instrument(fields(state = ?self.state), skip_all)]
pub fn process(
&mut self,
ts_request: TsRequest,
) -> Generator<'_, NetworkRequest, crate::Result<Vec<u8>>, Result<ServerState, ServerError>> {
Generator::<NetworkRequest, crate::Result<Vec<u8>>, Result<ServerState, ServerError>>::new(
move |mut yield_point| async move { self.process_impl(&mut yield_point, ts_request).await },
)
}
async fn process_impl(
&mut self,
yield_point: &mut YieldPointLocal,
mut ts_request: TsRequest,
) -> Result<ServerState, ServerError> {
if self.context.is_none() {
self.context = match self
.context_config
.take()
.expect("CredSsp client mode should never be empty")
{
ServerMode::Negotiate(neg_config) => {
Some(CredSspContext::new(SspiContext::Negotiate(try_cred_ssp_server!(
Negotiate::new_server(
neg_config,
try_cred_ssp_server!(self.credentials.auth_data(), ts_request)
),
ts_request
))))
}
ServerMode::Ntlm(ntlm) => Some(CredSspContext::new(SspiContext::Ntlm(Ntlm::with_config(ntlm)))),
ServerMode::Pku2u(pku2u) => Some(CredSspContext::new(SspiContext::Pku2u(try_cred_ssp_server!(
Pku2u::new_server_from_config(*pku2u),
ts_request
)))),
};
let AcquireCredentialsHandleResult { credentials_handle, .. } = try_cred_ssp_server!(
AcquireCredentialsHandle::<'_, _, _, WithoutCredentialUse>::new()
.with_credential_use(CredentialUse::Inbound)
.execute(
#[allow(clippy::panicking_unwrap)]
&mut self
.context
.as_mut()
.expect("Should not panic because the CredSSP context is set before")
.sspi_context
),
ts_request
);
self.credentials_handle = credentials_handle;
}
try_cred_ssp_server!(
self.context
.as_mut()
.expect("Should not panic because the CredSSP context is set before")
.check_peer_version(ts_request.version),
ts_request
);
ts_request.version = self.ts_request_version;
match self.state {
CredSspState::AuthInfo => {
let auth_info = try_cred_ssp_server!(
ts_request.auth_info.take().ok_or_else(|| {
Error::new(
ErrorKind::InvalidToken,
String::from("expected an encrypted ts credentials"),
)
}),
ts_request
);
let read_credentials = try_cred_ssp_server!(
self.context.as_mut().unwrap().decrypt_ts_credentials(&auth_info),
ts_request
);
self.state = CredSspState::Final;
let auth_identity = try_cred_ssp_server!(
AuthIdentity::try_from(read_credentials.into_auth_identity().unwrap())
.map_err(|e| Error::new(ErrorKind::InvalidParameter, e)),
ts_request
);
Ok(ServerState::Finished(auth_identity))
}
CredSspState::NegoToken => {
let input = ts_request.nego_tokens.take().unwrap_or_default();
let mut input_token = vec![SecurityBuffer::new(input, BufferType::Token)];
let mut output_token = vec![SecurityBuffer::new(Vec::with_capacity(1024), BufferType::Token)];
let mut credentials_handle = self.credentials_handle.take();
let sspi_context = &mut self.context.as_mut().unwrap().sspi_context;
let builder = sspi_context
.accept_security_context()
.with_credentials_handle(&mut credentials_handle)
.with_context_requirements(ServerRequestFlags::empty())
.with_target_data_representation(DataRepresentation::Native)
.with_input(&mut input_token)
.with_output(&mut output_token);
match try_cred_ssp_server!(
sspi_context.accept_security_context_impl(yield_point, builder).await,
ts_request
) {
AcceptSecurityContextResult {
status: SecurityStatus::ContinueNeeded,
..
} => {
ts_request.nego_tokens = Some(output_token.remove(0).buffer);
}
AcceptSecurityContextResult {
status: SecurityStatus::CompleteNeeded | SecurityStatus::Ok,
..
} => {
let ContextNames { username } = try_cred_ssp_server!(
self.context.as_mut().unwrap().sspi_context.query_context_names(),
ts_request
);
let candidates = try_cred_ssp_server!(
self.credentials
.auth_data_candidates_by_user(&username)
.map_err(|e| Error::new(ErrorKind::LogonDenied, e.to_string())),
ts_request
);
let cred_candidates = candidates.into_iter().map(Credentials::AuthIdentity).collect();
try_cred_ssp_server!(
self.context
.as_mut()
.unwrap()
.sspi_context
.custom_set_auth_identities(cred_candidates),
ts_request
);
try_cred_ssp_server!(
self.context.as_mut().unwrap().sspi_context.complete_auth_token(&mut []),
ts_request
);
ts_request.nego_tokens = None;
let pub_key_auth = try_cred_ssp_server!(
ts_request.pub_key_auth.take().ok_or_else(|| {
Error::new(
ErrorKind::InvalidToken,
String::from("CredSSP server expected an encrypted public key"),
)
}),
ts_request
);
let peer_version = self.context.as_ref().unwrap().peer_version.expect(
"an decrypt public key server function cannot be fired without any incoming TSRequest",
);
try_cred_ssp_server!(
self.context.as_mut().unwrap().decrypt_public_key(
self.public_key.as_ref(),
pub_key_auth.as_ref(),
EndpointType::Server,
&ts_request.client_nonce,
peer_version,
),
ts_request
);
let pub_key_auth = try_cred_ssp_server!(
self.context.as_mut().unwrap().encrypt_public_key(
self.public_key.as_ref(),
EndpointType::Server,
&ts_request.client_nonce,
peer_version,
),
ts_request
);
ts_request.pub_key_auth = Some(pub_key_auth);
self.state = CredSspState::AuthInfo;
}
result => {
try_cred_ssp_server!(
Err(Error::new(
ErrorKind::InternalError,
format!("SSPI returned unexpected status: {:?}", result.status)
)),
ts_request
)
}
};
self.credentials_handle = credentials_handle;
Ok(ServerState::ReplyNeeded(ts_request))
}
CredSspState::Final => Err(ServerError {
ts_request: Some(Box::new(ts_request)),
error: Error::new(
ErrorKind::UnsupportedFunction,
"CredSSP server's 'process' method must not be fired after the 'Finished' state",
),
}),
}
}
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum SspiContext {
Ntlm(Ntlm),
Kerberos(Kerberos),
Negotiate(Negotiate),
Pku2u(Pku2u),
#[cfg(feature = "tsssp")]
CredSsp(SspiCredSsp),
}
impl SspiContext {
pub fn package_name(&self) -> &str {
match self {
SspiContext::Ntlm(_) => ntlm::PKG_NAME,
SspiContext::Kerberos(_) => kerberos::PKG_NAME,
SspiContext::Negotiate(_) => negotiate::PKG_NAME,
SspiContext::Pku2u(_) => pku2u::PKG_NAME,
#[cfg(feature = "tsssp")]
SspiContext::CredSsp(_) => sspi_cred_ssp::PKG_NAME,
}
}
}
impl SspiImpl for SspiContext {
type CredentialsHandle = Option<CredentialsBuffers>;
type AuthenticationData = Credentials;
#[instrument(ret, level = "debug", fields(security_package = self.package_name()), skip_all)]
fn acquire_credentials_handle_impl(
&mut self,
builder: FilledAcquireCredentialsHandle<'_, Self::CredentialsHandle, Self::AuthenticationData>,
) -> crate::Result<AcquireCredentialsHandleResult<Self::CredentialsHandle>> {
Ok(match self {
SspiContext::Ntlm(ntlm) => {
let auth_identity = match builder.auth_data {
Some(Credentials::AuthIdentity(identity)) => Some(identity),
#[cfg(feature = "scard")]
Some(Credentials::SmartCard(_identity)) => {
return Err(Error::new(
ErrorKind::UnknownCredentials,
"smart card auth is not supported in NTLM",
));
}
None => None,
};
builder
.full_transform(auth_identity)
.execute(ntlm)?
.transform_credentials_handle(&|a: Option<AuthIdentityBuffers>| {
a.map(CredentialsBuffers::AuthIdentity)
})
}
SspiContext::Kerberos(kerberos) => builder.execute(kerberos)?,
SspiContext::Negotiate(negotiate) => builder.execute(negotiate)?,
SspiContext::Pku2u(pku2u) => {
let auth_identity = if let Some(Credentials::AuthIdentity(identity)) = builder.auth_data {
identity
} else {
return Err(Error::new(
ErrorKind::NoCredentials,
"auth identity is not provided for the Pku2u",
));
};
builder
.full_transform(Some(auth_identity))
.execute(pku2u)?
.transform_credentials_handle(&|a: Option<AuthIdentityBuffers>| {
a.map(CredentialsBuffers::AuthIdentity)
})
}
#[cfg(feature = "tsssp")]
SspiContext::CredSsp(credssp) => builder.execute(credssp)?,
})
}
#[instrument(ret, level = "debug", fields(security_package = self.package_name()), skip_all)]
fn accept_security_context_impl<'a>(
&'a mut self,
builder: FilledAcceptSecurityContext<'a, Self::CredentialsHandle>,
) -> crate::Result<GeneratorAcceptSecurityContext<'a>> {
Ok(GeneratorAcceptSecurityContext::new(move |mut yield_point| async move {
self.accept_security_context_impl(&mut yield_point, builder).await
}))
}
fn initialize_security_context_impl<'ctx, 'b, 'g>(
&'ctx mut self,
builder: &'b mut FilledInitializeSecurityContext<'ctx, 'ctx, Self::CredentialsHandle>,
) -> crate::Result<GeneratorInitSecurityContext<'g>>
where
'ctx: 'g,
'b: 'g,
{
Ok(Generator::new(move |mut yield_point| async move {
self.initialize_security_context_impl(&mut yield_point, builder).await
}))
}
}
impl<'a> SspiContext {
#[instrument(ret, level = "debug", fields(security_package = self.package_name()), skip_all)]
async fn change_password_impl(
&mut self,
yield_point: &mut YieldPointLocal,
change_password: ChangePassword<'a>,
) -> crate::Result<()> {
match self {
SspiContext::Kerberos(kerberos) => kerberos.change_password(yield_point, change_password).await,
SspiContext::Negotiate(negotiate) => negotiate.change_password(yield_point, change_password).await,
_ => Err(Error::new(
ErrorKind::UnsupportedFunction,
"change password not supported for this protocol",
)),
}
}
#[cfg(feature = "network_client")]
pub fn initialize_security_context_sync(
&mut self,
builder: &mut FilledInitializeSecurityContext<'_, '_, <Self as SspiImpl>::CredentialsHandle>,
) -> crate::Result<InitializeSecurityContextResult> {
Generator::new(move |mut yield_point| async move {
self.initialize_security_context_impl(&mut yield_point, builder).await
})
.resolve_with_default_network_client()
}
#[cfg(feature = "network_client")]
pub fn accept_security_context_sync(
&mut self,
builder: FilledAcceptSecurityContext<'_, <Self as SspiImpl>::CredentialsHandle>,
) -> crate::Result<AcceptSecurityContextResult> {
Generator::new(move |mut yield_point| async move {
self.accept_security_context_impl(&mut yield_point, builder).await
})
.resolve_with_default_network_client()
}
#[cfg(feature = "network_client")]
pub fn change_password_sync(&mut self, builder: ChangePassword<'_>) -> crate::Result<()> {
Generator::new(move |mut yield_point| async move { self.change_password_impl(&mut yield_point, builder).await })
.resolve_with_default_network_client()
}
pub(crate) async fn accept_security_context_impl(
&mut self,
yield_point: &mut YieldPointLocal,
builder: FilledAcceptSecurityContext<'a, <Self as SspiImpl>::CredentialsHandle>,
) -> crate::Result<AcceptSecurityContextResult> {
match self {
SspiContext::Ntlm(ntlm) => {
let mut auth_identity = match builder.credentials_handle {
Some(Some(CredentialsBuffers::AuthIdentity(identity))) => Some(identity.clone()),
#[cfg(feature = "scard")]
Some(Some(CredentialsBuffers::SmartCard(_identity))) => {
return Err(Error::new(
ErrorKind::UnknownCredentials,
"smart card auth is not supported in NTLM",
));
}
Some(None) => None,
None => {
return Err(Error::new(
ErrorKind::NoCredentials,
"credentials handle is not provided for the NTLM",
));
}
};
let new_builder = builder.full_transform(Some(&mut auth_identity));
ntlm.accept_security_context_impl(new_builder)
}
SspiContext::Kerberos(kerberos) => kerberos.accept_security_context_impl(yield_point, builder).await,
SspiContext::Negotiate(negotiate) => negotiate.accept_security_context_impl(yield_point, builder).await,
SspiContext::Pku2u(pku2u) => {
let mut creds_handle = builder
.credentials_handle
.as_ref()
.and_then(|creds| (*creds).clone())
.and_then(|creds_handle| creds_handle.into_auth_identity());
let new_builder = builder.full_transform(Some(&mut creds_handle));
pku2u.accept_security_context_impl(yield_point, new_builder).await
}
#[cfg(feature = "tsssp")]
SspiContext::CredSsp(credssp) => credssp.accept_security_context_impl(yield_point, builder).await,
}
}
#[instrument(ret, level = "debug", fields(security_package = self.package_name()), skip_all)]
async fn initialize_security_context_impl(
&'a mut self,
yield_point: &mut YieldPointLocal,
builder: &'a mut FilledInitializeSecurityContext<'_, '_, <Self as SspiImpl>::CredentialsHandle>,
) -> crate::Result<InitializeSecurityContextResult> {
match self {
SspiContext::Ntlm(ntlm) => {
let mut auth_identity =
if let Some(Some(CredentialsBuffers::AuthIdentity(identity))) = builder.credentials_handle_mut() {
Some(identity.clone())
} else {
None
};
let mut new_builder = builder.full_transform(Some(&mut auth_identity));
ntlm.initialize_security_context_impl(&mut new_builder)
}
SspiContext::Kerberos(kerberos) => kerberos.initialize_security_context_impl(yield_point, builder).await,
SspiContext::Negotiate(negotiate) => negotiate.initialize_security_context_impl(yield_point, builder).await,
SspiContext::Pku2u(pku2u) => {
let mut auth_identity =
if let Some(Some(CredentialsBuffers::AuthIdentity(identity))) = builder.credentials_handle_mut() {
Some(identity.clone())
} else {
None
};
let mut new_builder = builder.full_transform(Some(&mut auth_identity));
pku2u.initialize_security_context_impl(&mut new_builder)
}
#[cfg(feature = "tsssp")]
SspiContext::CredSsp(credssp) => credssp.initialize_security_context_impl(yield_point, builder).await,
}
}
}
impl Sspi for SspiContext {
#[instrument(ret, level = "debug", fields(security_package = self.package_name()), skip(self))]
fn complete_auth_token(&mut self, token: &mut [SecurityBuffer]) -> crate::Result<SecurityStatus> {
match self {
SspiContext::Ntlm(ntlm) => ntlm.complete_auth_token(token),
SspiContext::Kerberos(kerberos) => kerberos.complete_auth_token(token),
SspiContext::Negotiate(negotiate) => negotiate.complete_auth_token(token),
SspiContext::Pku2u(pku2u) => pku2u.complete_auth_token(token),
#[cfg(feature = "tsssp")]
SspiContext::CredSsp(credssp) => credssp.complete_auth_token(token),
}
}
#[instrument(ret, level = "debug", fields(security_package = self.package_name()), skip(self))]
fn encrypt_message(
&mut self,
flags: EncryptionFlags,
message: &mut [SecurityBufferRef<'_>],
) -> crate::Result<SecurityStatus> {
match self {
SspiContext::Ntlm(ntlm) => ntlm.encrypt_message(flags, message),
SspiContext::Kerberos(kerberos) => kerberos.encrypt_message(flags, message),
SspiContext::Negotiate(negotiate) => negotiate.encrypt_message(flags, message),
SspiContext::Pku2u(pku2u) => pku2u.encrypt_message(flags, message),
#[cfg(feature = "tsssp")]
SspiContext::CredSsp(credssp) => credssp.encrypt_message(flags, message),
}
}
#[instrument(ret, level = "debug", fields(security_package = self.package_name()), skip(self))]
fn decrypt_message(&mut self, message: &mut [SecurityBufferRef<'_>]) -> crate::Result<DecryptionFlags> {
match self {
SspiContext::Ntlm(ntlm) => ntlm.decrypt_message(message),
SspiContext::Kerberos(kerberos) => kerberos.decrypt_message(message),
SspiContext::Negotiate(negotiate) => negotiate.decrypt_message(message),
SspiContext::Pku2u(pku2u) => pku2u.decrypt_message(message),
#[cfg(feature = "tsssp")]
SspiContext::CredSsp(credssp) => credssp.decrypt_message(message),
}
}
#[instrument(ret, level = "debug", fields(security_package = self.package_name()), skip(self))]
fn query_context_sizes(&mut self) -> crate::Result<ContextSizes> {
match self {
SspiContext::Ntlm(ntlm) => ntlm.query_context_sizes(),
SspiContext::Kerberos(kerberos) => kerberos.query_context_sizes(),
SspiContext::Negotiate(negotiate) => negotiate.query_context_sizes(),
SspiContext::Pku2u(pku2u) => pku2u.query_context_sizes(),
#[cfg(feature = "tsssp")]
SspiContext::CredSsp(credssp) => credssp.query_context_sizes(),
}
}
#[instrument(ret, level = "debug", fields(security_package = self.package_name()), skip(self))]
fn query_context_names(&mut self) -> crate::Result<ContextNames> {
match self {
SspiContext::Ntlm(ntlm) => ntlm.query_context_names(),
SspiContext::Kerberos(kerberos) => kerberos.query_context_names(),
SspiContext::Negotiate(negotiate) => negotiate.query_context_names(),
SspiContext::Pku2u(pku2u) => pku2u.query_context_names(),
#[cfg(feature = "tsssp")]
SspiContext::CredSsp(credssp) => credssp.query_context_names(),
}
}
#[instrument(ret, level = "debug", fields(security_package = self.package_name()), skip(self))]
fn query_context_stream_sizes(&mut self) -> crate::Result<StreamSizes> {
match self {
SspiContext::Ntlm(ntlm) => ntlm.query_context_stream_sizes(),
SspiContext::Kerberos(kerberos) => kerberos.query_context_stream_sizes(),
SspiContext::Negotiate(negotiate) => negotiate.query_context_stream_sizes(),
SspiContext::Pku2u(pku2u) => pku2u.query_context_stream_sizes(),
#[cfg(feature = "tsssp")]
SspiContext::CredSsp(credssp) => credssp.query_context_stream_sizes(),
}
}
#[instrument(ret, level = "debug", fields(security_package = self.package_name()), skip(self))]
fn query_context_package_info(&mut self) -> crate::Result<PackageInfo> {
match self {
SspiContext::Ntlm(ntlm) => ntlm.query_context_package_info(),
SspiContext::Kerberos(kerberos) => kerberos.query_context_package_info(),
SspiContext::Negotiate(negotiate) => negotiate.query_context_package_info(),
SspiContext::Pku2u(pku2u) => pku2u.query_context_package_info(),
#[cfg(feature = "tsssp")]
SspiContext::CredSsp(credssp) => credssp.query_context_package_info(),
}
}
#[instrument(ret, level = "debug", fields(security_package = self.package_name()), skip(self))]
fn query_context_cert_trust_status(&mut self) -> crate::Result<CertTrustStatus> {
match self {
SspiContext::Ntlm(ntlm) => ntlm.query_context_cert_trust_status(),
SspiContext::Kerberos(kerberos) => kerberos.query_context_cert_trust_status(),
SspiContext::Negotiate(negotiate) => negotiate.query_context_cert_trust_status(),
SspiContext::Pku2u(pku2u) => pku2u.query_context_cert_trust_status(),
#[cfg(feature = "tsssp")]
SspiContext::CredSsp(credssp) => credssp.query_context_cert_trust_status(),
}
}
#[instrument(ret, level = "debug", fields(security_package = self.package_name()), skip(self))]
fn query_context_remote_cert(&mut self) -> crate::Result<CertContext> {
match self {
SspiContext::Ntlm(ntlm) => ntlm.query_context_remote_cert(),
SspiContext::Kerberos(kerberos) => kerberos.query_context_remote_cert(),
SspiContext::Negotiate(negotiate) => negotiate.query_context_remote_cert(),
SspiContext::Pku2u(pku2u) => pku2u.query_context_remote_cert(),
#[cfg(feature = "tsssp")]
SspiContext::CredSsp(credssp) => credssp.query_context_remote_cert(),
}
}
#[instrument(ret, level = "debug", fields(security_package = self.package_name()), skip(self))]
fn query_context_negotiation_package(&mut self) -> crate::Result<PackageInfo> {
match self {
SspiContext::Ntlm(ntlm) => ntlm.query_context_negotiation_package(),
SspiContext::Kerberos(kerberos) => kerberos.query_context_negotiation_package(),
SspiContext::Negotiate(negotiate) => negotiate.query_context_negotiation_package(),
SspiContext::Pku2u(pku2u) => pku2u.query_context_negotiation_package(),
#[cfg(feature = "tsssp")]
SspiContext::CredSsp(credssp) => credssp.query_context_negotiation_package(),
}
}
#[instrument(ret, level = "debug", fields(security_package = self.package_name()), skip(self))]
fn query_context_connection_info(&mut self) -> crate::Result<ConnectionInfo> {
match self {
SspiContext::Ntlm(ntlm) => ntlm.query_context_connection_info(),
SspiContext::Kerberos(kerberos) => kerberos.query_context_connection_info(),
SspiContext::Negotiate(negotiate) => negotiate.query_context_connection_info(),
SspiContext::Pku2u(pku2u) => pku2u.query_context_connection_info(),
#[cfg(feature = "tsssp")]
SspiContext::CredSsp(credssp) => credssp.query_context_connection_info(),
}
}
#[instrument(fields(security_package = self.package_name()), skip(self))]
fn query_context_session_key(&self) -> crate::Result<crate::SessionKeys> {
match self {
SspiContext::Ntlm(ntlm) => ntlm.query_context_session_key(),
SspiContext::Kerberos(kerberos) => kerberos.query_context_session_key(),
SspiContext::Negotiate(negotiate) => negotiate.query_context_session_key(),
SspiContext::Pku2u(pku2u) => pku2u.query_context_session_key(),
#[cfg(feature = "tsssp")]
SspiContext::CredSsp(credssp) => credssp.query_context_session_key(),
}
}
fn change_password<'a>(
&'a mut self,
change_password: ChangePassword<'a>,
) -> crate::Result<GeneratorChangePassword<'a>> {
Ok(GeneratorChangePassword::new(move |mut yield_point| async move {
self.change_password_impl(&mut yield_point, change_password).await
}))
}
fn make_signature(
&mut self,
flags: u32,
message: &mut [SecurityBufferRef<'_>],
sequence_number: u32,
) -> crate::Result<()> {
match self {
SspiContext::Ntlm(ntlm) => ntlm.make_signature(flags, message, sequence_number),
SspiContext::Kerberos(kerberos) => kerberos.make_signature(flags, message, sequence_number),
SspiContext::Negotiate(negotiate) => negotiate.make_signature(flags, message, sequence_number),
SspiContext::Pku2u(pku2u) => pku2u.make_signature(flags, message, sequence_number),
#[cfg(feature = "tsssp")]
SspiContext::CredSsp(credssp) => credssp.make_signature(flags, message, sequence_number),
}
}
fn verify_signature(&mut self, message: &mut [SecurityBufferRef<'_>], sequence_number: u32) -> crate::Result<u32> {
match self {
SspiContext::Ntlm(ntlm) => ntlm.verify_signature(message, sequence_number),
SspiContext::Kerberos(kerberos) => kerberos.verify_signature(message, sequence_number),
SspiContext::Negotiate(negotiate) => negotiate.verify_signature(message, sequence_number),
SspiContext::Pku2u(pku2u) => pku2u.verify_signature(message, sequence_number),
#[cfg(feature = "tsssp")]
SspiContext::CredSsp(credssp) => credssp.verify_signature(message, sequence_number),
}
}
}
impl SspiEx for SspiContext {
fn custom_set_auth_identity(&mut self, identity: Self::AuthenticationData) -> crate::Result<()> {
match self {
SspiContext::Ntlm(ntlm) => ntlm.custom_set_auth_identity(identity.auth_identity().ok_or_else(|| {
Error::new(
ErrorKind::IncompleteCredentials,
"provided credentials are not password-based",
)
})?),
SspiContext::Kerberos(kerberos) => kerberos.custom_set_auth_identity(identity),
SspiContext::Negotiate(negotiate) => negotiate.custom_set_auth_identity(identity),
SspiContext::Pku2u(pku2u) => pku2u.custom_set_auth_identity(identity.auth_identity().ok_or_else(|| {
Error::new(
ErrorKind::IncompleteCredentials,
"provided credentials are not password-based",
)
})?),
#[cfg(feature = "tsssp")]
SspiContext::CredSsp(credssp) => credssp.custom_set_auth_identity(identity),
}
}
fn custom_set_auth_identities(&mut self, identities: Vec<Self::AuthenticationData>) -> crate::Result<()> {
match self {
SspiContext::Ntlm(ntlm) => {
let auth_identities: Vec<AuthIdentity> =
identities.into_iter().filter_map(|c| c.auth_identity()).collect();
ntlm.custom_set_auth_identities(auth_identities)
}
SspiContext::Negotiate(negotiate) => negotiate.custom_set_auth_identities(identities),
_ => match identities.into_iter().next() {
Some(identity) => self.custom_set_auth_identity(identity),
None => Err(Error::new(ErrorKind::NoCredentials, "no credentials provided")),
},
}
}
}
#[derive(Debug)]
struct CredSspContext {
peer_version: Option<u32>,
sspi_context: SspiContext,
}
impl CredSspContext {
fn new(sspi_context: SspiContext) -> Self {
Self {
peer_version: None,
sspi_context,
}
}
fn check_peer_version(&mut self, other_peer_version: u32) -> crate::Result<()> {
if let Some(peer_version) = self.peer_version {
if peer_version != other_peer_version {
Err(Error::new(
ErrorKind::MessageAltered,
format!("CredSSP peer changed protocol version from {peer_version} to {other_peer_version}"),
))
} else {
Ok(())
}
} else {
self.peer_version = Some(other_peer_version);
Ok(())
}
}
fn encrypt_public_key(
&mut self,
public_key: &[u8],
endpoint: EndpointType,
client_nonce: &Option<[u8; NONCE_SIZE]>,
peer_version: u32,
) -> crate::Result<Vec<u8>> {
let hash_magic = match endpoint {
EndpointType::Client => CLIENT_SERVER_HASH_MAGIC,
EndpointType::Server => SERVER_CLIENT_HASH_MAGIC,
};
if peer_version < 5 {
self.encrypt_public_key_echo(public_key, endpoint)
} else {
self.encrypt_public_key_hash(
public_key,
hash_magic,
&client_nonce.ok_or(Error::new(
ErrorKind::InvalidToken,
String::from("client nonce from the TSRequest is empty, but a peer version is >= 5"),
))?,
)
}
}
fn decrypt_public_key(
&mut self,
public_key: &[u8],
encrypted_public_key: &[u8],
endpoint: EndpointType,
client_nonce: &Option<[u8; NONCE_SIZE]>,
peer_version: u32,
) -> crate::Result<()> {
let hash_magic = match endpoint {
EndpointType::Client => SERVER_CLIENT_HASH_MAGIC,
EndpointType::Server => CLIENT_SERVER_HASH_MAGIC,
};
if peer_version < 5 {
self.decrypt_public_key_echo(public_key, encrypted_public_key, endpoint)
} else {
self.decrypt_public_key_hash(
public_key,
encrypted_public_key,
hash_magic,
&client_nonce.ok_or(Error::new(
ErrorKind::InvalidToken,
String::from("client nonce from the TSRequest is empty, but a peer version is >= 5"),
))?,
)
}
}
fn encrypt_public_key_echo(&mut self, public_key: &[u8], endpoint: EndpointType) -> crate::Result<Vec<u8>> {
let mut public_key = public_key.to_vec();
if let SspiContext::Ntlm(_) = self.sspi_context
&& endpoint == EndpointType::Server
{
integer_increment_le(&mut public_key);
}
self.encrypt_message(&public_key)
}
fn encrypt_public_key_hash(
&mut self,
public_key: &[u8],
hash_magic: &[u8],
client_nonce: &[u8],
) -> crate::Result<Vec<u8>> {
let mut data = hash_magic.to_vec();
data.extend(client_nonce);
data.extend(public_key);
self.encrypt_message(&compute_sha256(&data))
}
fn decrypt_public_key_echo(
&mut self,
public_key: &[u8],
encrypted_public_key: &[u8],
endpoint: EndpointType,
) -> crate::Result<()> {
let mut decrypted_public_key = self.decrypt_message(encrypted_public_key)?;
if endpoint == EndpointType::Client {
integer_decrement_le(&mut decrypted_public_key);
}
if public_key != decrypted_public_key.as_slice() {
error!("Expected and decrypted public key are not the same");
return Err(Error::new(
ErrorKind::MessageAltered,
String::from("could not verify a public key echo"),
));
}
Ok(())
}
fn decrypt_public_key_hash(
&mut self,
public_key: &[u8],
encrypted_public_key: &[u8],
hash_magic: &[u8],
client_nonce: &[u8],
) -> crate::Result<()> {
let decrypted_public_key = self.decrypt_message(encrypted_public_key)?;
let mut data = hash_magic.to_vec();
data.extend(client_nonce);
data.extend(public_key);
let expected_public_key = compute_sha256(&data);
if expected_public_key.as_ref() != decrypted_public_key.as_slice() {
error!("Expected and decrypted public key hash are not the same");
return Err(Error::new(
ErrorKind::MessageAltered,
String::from("could not verify a public key hash"),
));
}
Ok(())
}
fn encrypt_ts_credentials(
&mut self,
credentials: &CredentialsBuffers,
cred_ssp_mode: CredSspMode,
) -> crate::Result<Vec<u8>> {
self.encrypt_message(&write_ts_credentials(credentials, cred_ssp_mode)?)
}
fn decrypt_ts_credentials(&mut self, auth_info: &[u8]) -> crate::Result<CredentialsBuffers> {
let ts_credentials_buffer = self.decrypt_message(auth_info)?;
read_ts_credentials(ts_credentials_buffer.as_slice())
}
fn encrypt_message(&mut self, input: &[u8]) -> crate::Result<Vec<u8>> {
let mut token = [0; 1024];
let mut data = input.to_vec();
let mut buffers = vec![
SecurityBufferRef::token_buf(token.as_mut_slice()),
SecurityBufferRef::data_buf(data.as_mut_slice()),
];
self.sspi_context
.encrypt_message(EncryptionFlags::empty(), &mut buffers)?;
let mut output = SecurityBufferRef::find_buffer(&buffers, BufferType::Token)?
.data()
.to_vec();
output.extend_from_slice(SecurityBufferRef::find_buffer_mut(&mut buffers, BufferType::Data)?.data());
Ok(output)
}
fn decrypt_message(&mut self, input: &[u8]) -> crate::Result<Vec<u8>> {
let mut input = input.to_vec();
let (signature, data) = input.split_at_mut(SIGNATURE_SIZE);
let mut buffers = vec![
SecurityBufferRef::data_buf(data),
SecurityBufferRef::token_buf(signature),
];
self.sspi_context.decrypt_message(&mut buffers)?;
let output = SecurityBufferRef::buf_data(&buffers, BufferType::Data)?.to_vec();
Ok(output)
}
}
fn integer_decrement_le(buffer: &mut [u8]) {
for elem in buffer.iter_mut() {
let (value, overflow) = elem.overflowing_sub(1);
*elem = value;
if !overflow {
break;
}
}
}
fn integer_increment_le(buffer: &mut [u8]) {
for elem in buffer.iter_mut() {
let (value, overflow) = elem.overflowing_add(1);
*elem = value;
if !overflow {
break;
}
}
}
fn construct_error(e: &Error) -> NStatusCode {
let code = ((e.error_type as i64 & 0x0000_FFFF) | (0x7 << 16) | 0xC000_0000) as u32;
NStatusCode(code)
}
#[cfg(test)]
mod tests {
use static_assertions::assert_impl_one;
use super::CredSspClient;
#[test]
fn cred_sspi_client_is_send() {
assert_impl_one!(CredSspClient: Send);
}
#[test]
fn cred_sspi_client_is_sync() {
assert_impl_one!(CredSspClient: Sync);
}
}