use std::collections::HashMap;
use std::time::{SystemTime};
use crate::{Result, QsshError};
const GSSAPI_NOT_IMPLEMENTED: &str = "GSSAPI authentication is not implemented. \
Use public key or password authentication instead.";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GssapiMechanism {
Kerberos5,
Ntlm,
Spnego,
}
impl GssapiMechanism {
pub fn oid(&self) -> &[u8] {
match self {
Self::Kerberos5 => &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02],
Self::Ntlm => &[0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x02, 0x0a],
Self::Spnego => &[0x2b, 0x06, 0x01, 0x05, 0x05, 0x02],
}
}
pub fn from_oid(oid: &[u8]) -> Option<Self> {
if oid == Self::Kerberos5.oid() {
Some(Self::Kerberos5)
} else if oid == Self::Ntlm.oid() {
Some(Self::Ntlm)
} else if oid == Self::Spnego.oid() {
Some(Self::Spnego)
} else {
None
}
}
}
#[allow(dead_code)]
pub struct GssapiContext {
mechanism: GssapiMechanism,
state: ContextState,
service_name: String,
client_name: Option<String>,
session_key: Option<Vec<u8>>,
flags: ContextFlags,
delegated_creds: Option<DelegatedCredentials>,
mic_token: Option<Vec<u8>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum ContextState {
Initial,
WaitingForServer,
WaitingForClient,
Established,
Error(String),
}
#[derive(Debug, Clone, Default)]
pub struct ContextFlags {
pub mutual_auth: bool,
pub confidentiality: bool,
pub integrity: bool,
pub anonymity: bool,
pub delegation: bool,
pub replay_detection: bool,
pub sequence_detection: bool,
}
#[derive(Debug, Clone)]
pub struct DelegatedCredentials {
pub cache: Vec<u8>,
pub expiration: SystemTime,
}
impl GssapiContext {
pub fn new_client(service_name: String, mechanism: GssapiMechanism) -> Self {
Self {
mechanism,
state: ContextState::Initial,
service_name,
client_name: None,
session_key: None,
flags: ContextFlags::default(),
delegated_creds: None,
mic_token: None,
}
}
pub fn new_server(mechanism: GssapiMechanism) -> Self {
Self {
mechanism,
state: ContextState::Initial,
service_name: String::new(),
client_name: None,
session_key: None,
flags: ContextFlags::default(),
delegated_creds: None,
mic_token: None,
}
}
pub fn init_sec_context(&mut self, _input_token: Option<&[u8]>) -> Result<GssapiToken> {
Err(QsshError::Protocol(GSSAPI_NOT_IMPLEMENTED.into()))
}
pub fn accept_sec_context(&mut self, _input_token: &[u8]) -> Result<GssapiToken> {
Err(QsshError::Protocol(GSSAPI_NOT_IMPLEMENTED.into()))
}
pub fn is_established(&self) -> bool {
self.state == ContextState::Established
}
pub fn client_name(&self) -> Option<&str> {
self.client_name.as_deref()
}
pub fn delegated_credentials(&self) -> Option<&DelegatedCredentials> {
self.delegated_creds.as_ref()
}
pub fn get_mic(&self, _message: &[u8]) -> Result<Vec<u8>> {
Err(QsshError::Protocol(GSSAPI_NOT_IMPLEMENTED.into()))
}
pub fn verify_mic(&self, _message: &[u8], _mic: &[u8]) -> Result<bool> {
Err(QsshError::Protocol(GSSAPI_NOT_IMPLEMENTED.into()))
}
pub fn wrap(&self, _message: &[u8], _encrypt: bool) -> Result<Vec<u8>> {
Err(QsshError::Protocol(GSSAPI_NOT_IMPLEMENTED.into()))
}
pub fn unwrap(&self, _wrapped: &[u8]) -> Result<Vec<u8>> {
Err(QsshError::Protocol(GSSAPI_NOT_IMPLEMENTED.into()))
}
}
#[derive(Debug, Clone)]
pub struct GssapiToken {
pub data: Vec<u8>,
pub complete: bool,
pub mechanism: GssapiMechanism,
}
#[allow(dead_code)]
pub struct GssapiAuthenticator {
mechanisms: Vec<GssapiMechanism>,
contexts: HashMap<String, GssapiContext>,
service_name: String,
}
impl GssapiAuthenticator {
pub fn new(service_name: String) -> Self {
Self {
mechanisms: vec![
GssapiMechanism::Kerberos5,
GssapiMechanism::Ntlm,
GssapiMechanism::Spnego,
],
contexts: HashMap::new(),
service_name,
}
}
pub fn start_auth(&mut self, _mechanism: GssapiMechanism, _client: bool) -> Result<String> {
Err(QsshError::Protocol(GSSAPI_NOT_IMPLEMENTED.into()))
}
pub fn continue_auth(&mut self, _context_id: &str, _input: Option<&[u8]>) -> Result<GssapiToken> {
Err(QsshError::Protocol(GSSAPI_NOT_IMPLEMENTED.into()))
}
pub fn accept_auth(&mut self, _context_id: &str, _input: &[u8]) -> Result<GssapiToken> {
Err(QsshError::Protocol(GSSAPI_NOT_IMPLEMENTED.into()))
}
pub fn get_context(&self, context_id: &str) -> Option<&GssapiContext> {
self.contexts.get(context_id)
}
pub fn available_mechanisms(&self) -> &[GssapiMechanism] {
&self.mechanisms
}
}
impl ContextFlags {
pub fn to_bits(&self) -> u32 {
let mut bits = 0;
if self.mutual_auth { bits |= 0x01; }
if self.confidentiality { bits |= 0x02; }
if self.integrity { bits |= 0x04; }
if self.anonymity { bits |= 0x08; }
if self.delegation { bits |= 0x10; }
if self.replay_detection { bits |= 0x20; }
if self.sequence_detection { bits |= 0x40; }
bits
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mechanism_oids() {
assert_eq!(GssapiMechanism::Kerberos5.oid().len(), 9);
assert_eq!(GssapiMechanism::from_oid(GssapiMechanism::Kerberos5.oid()), Some(GssapiMechanism::Kerberos5));
}
#[test]
fn test_context_creation() {
let client = GssapiContext::new_client("host/server.example.com".to_string(), GssapiMechanism::Kerberos5);
assert!(!client.is_established());
let server = GssapiContext::new_server(GssapiMechanism::Kerberos5);
assert!(!server.is_established());
}
#[test]
fn test_authenticator_returns_error() {
let mut auth = GssapiAuthenticator::new("host/server.example.com".to_string());
let result = auth.start_auth(GssapiMechanism::Kerberos5, true);
assert!(result.is_err());
assert_eq!(auth.available_mechanisms().len(), 3);
}
#[test]
fn test_init_sec_context_returns_error() {
let mut context = GssapiContext::new_client("host/server.example.com".to_string(), GssapiMechanism::Kerberos5);
let result = context.init_sec_context(None);
assert!(result.is_err());
}
#[test]
fn test_accept_sec_context_returns_error() {
let mut context = GssapiContext::new_server(GssapiMechanism::Kerberos5);
let result = context.accept_sec_context(&[0u8; 16]);
assert!(result.is_err());
}
#[test]
fn test_context_flags() {
let mut flags = ContextFlags::default();
flags.mutual_auth = true;
flags.integrity = true;
assert_eq!(flags.to_bits() & 0x01, 0x01);
assert_eq!(flags.to_bits() & 0x04, 0x04);
}
}