use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum KerberosEtype {
DesCbcCrc,
DesCbcMd4,
DesCbcMd5,
Des3CbcSha1Kd,
Aes128CtsHmacSha1,
Aes256CtsHmacSha1,
Aes128CtsHmacSha256,
Aes256CtsHmacSha384,
Rc4Hmac,
Rc4HmacExp,
Other(i32),
}
impl KerberosEtype {
#[inline]
pub fn from_raw(value: i32) -> Self {
match value {
1 => Self::DesCbcCrc,
2 => Self::DesCbcMd4,
3 => Self::DesCbcMd5,
16 => Self::Des3CbcSha1Kd,
17 => Self::Aes128CtsHmacSha1,
18 => Self::Aes256CtsHmacSha1,
19 => Self::Aes128CtsHmacSha256,
20 => Self::Aes256CtsHmacSha384,
23 => Self::Rc4Hmac,
24 => Self::Rc4HmacExp,
other => Self::Other(other),
}
}
#[inline]
pub fn as_raw(&self) -> i32 {
match self {
Self::DesCbcCrc => 1,
Self::DesCbcMd4 => 2,
Self::DesCbcMd5 => 3,
Self::Des3CbcSha1Kd => 16,
Self::Aes128CtsHmacSha1 => 17,
Self::Aes256CtsHmacSha1 => 18,
Self::Aes128CtsHmacSha256 => 19,
Self::Aes256CtsHmacSha384 => 20,
Self::Rc4Hmac => 23,
Self::Rc4HmacExp => 24,
Self::Other(v) => *v,
}
}
#[inline]
pub fn is_aes(&self) -> bool {
matches!(
self,
Self::Aes128CtsHmacSha1
| Self::Aes256CtsHmacSha1
| Self::Aes128CtsHmacSha256
| Self::Aes256CtsHmacSha384
)
}
#[inline]
pub fn is_rc4(&self) -> bool {
matches!(self, Self::Rc4Hmac | Self::Rc4HmacExp)
}
#[inline]
pub fn is_des(&self) -> bool {
matches!(
self,
Self::DesCbcCrc | Self::DesCbcMd4 | Self::DesCbcMd5 | Self::Des3CbcSha1Kd
)
}
#[inline]
pub fn is_weak(&self) -> bool {
self.is_rc4() || self.is_des()
}
}
impl From<i32> for KerberosEtype {
#[inline]
fn from(value: i32) -> Self {
Self::from_raw(value)
}
}
impl From<KerberosEtype> for i32 {
#[inline]
fn from(value: KerberosEtype) -> Self {
value.as_raw()
}
}
impl fmt::Display for KerberosEtype {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::DesCbcCrc => "des-cbc-crc",
Self::DesCbcMd4 => "des-cbc-md4",
Self::DesCbcMd5 => "des-cbc-md5",
Self::Des3CbcSha1Kd => "des3-cbc-sha1-kd",
Self::Aes128CtsHmacSha1 => "aes128-cts-hmac-sha1-96",
Self::Aes256CtsHmacSha1 => "aes256-cts-hmac-sha1-96",
Self::Aes128CtsHmacSha256 => "aes128-cts-hmac-sha256-128",
Self::Aes256CtsHmacSha384 => "aes256-cts-hmac-sha384-192",
Self::Rc4Hmac => "rc4-hmac",
Self::Rc4HmacExp => "rc4-hmac-exp",
Self::Other(v) => return write!(f, "etype-{v}"),
};
f.write_str(s)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum KerberosMessageKind {
AsReq,
AsRep,
TgsReq,
TgsRep,
ApReq,
ApRep,
KrbError,
Unknown(u8),
}
impl KerberosMessageKind {
pub fn as_str(&self) -> &'static str {
match self {
KerberosMessageKind::AsReq => "as_req",
KerberosMessageKind::AsRep => "as_rep",
KerberosMessageKind::TgsReq => "tgs_req",
KerberosMessageKind::TgsRep => "tgs_rep",
KerberosMessageKind::ApReq => "ap_req",
KerberosMessageKind::ApRep => "ap_rep",
KerberosMessageKind::KrbError => "krb_error",
KerberosMessageKind::Unknown(_) => "unknown",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct KerberosMessage {
pub kind: KerberosMessageKind,
pub pvno: u32,
pub realm: String,
pub cname: Option<String>,
pub sname: Option<String>,
pub etypes: Vec<KerberosEtype>,
pub padata_types: Vec<i32>,
pub kerberoast_suspect: bool,
pub error_code: Option<KerberosErrorCode>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum KerberosErrorCode {
KdcErrCPrincipalUnknown,
KdcErrSPrincipalUnknown,
KdcErrClientRevoked,
KdcErrPreauthFailed,
KdcErrPreauthRequired,
KdcErrTgtRevoked,
KdcErrTgsRevoked,
KrbApErrTktNyv,
Other(i32),
}
impl KerberosErrorCode {
pub fn from_raw(raw: i32) -> Self {
match raw {
6 => Self::KdcErrCPrincipalUnknown,
7 => Self::KdcErrSPrincipalUnknown,
18 => Self::KdcErrClientRevoked,
24 => Self::KdcErrPreauthFailed,
25 => Self::KdcErrPreauthRequired,
28 => Self::KdcErrTgtRevoked,
51 => Self::KdcErrTgsRevoked,
52 => Self::KrbApErrTktNyv,
other => Self::Other(other),
}
}
pub fn as_raw(&self) -> i32 {
match self {
Self::KdcErrCPrincipalUnknown => 6,
Self::KdcErrSPrincipalUnknown => 7,
Self::KdcErrClientRevoked => 18,
Self::KdcErrPreauthFailed => 24,
Self::KdcErrPreauthRequired => 25,
Self::KdcErrTgtRevoked => 28,
Self::KdcErrTgsRevoked => 51,
Self::KrbApErrTktNyv => 52,
Self::Other(v) => *v,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::KdcErrCPrincipalUnknown => "kdc_err_c_principal_unknown",
Self::KdcErrSPrincipalUnknown => "kdc_err_s_principal_unknown",
Self::KdcErrClientRevoked => "kdc_err_client_revoked",
Self::KdcErrPreauthFailed => "kdc_err_preauth_failed",
Self::KdcErrPreauthRequired => "kdc_err_preauth_required",
Self::KdcErrTgtRevoked => "kdc_err_tgt_revoked",
Self::KdcErrTgsRevoked => "kdc_err_tgs_revoked",
Self::KrbApErrTktNyv => "krb_ap_err_tkt_nyv",
Self::Other(_) => "other",
}
}
pub fn is_brute_force_signal(&self) -> bool {
matches!(
self,
Self::KdcErrPreauthFailed | Self::KdcErrClientRevoked | Self::KdcErrCPrincipalUnknown
)
}
}
impl std::fmt::Display for KerberosErrorCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl KerberosMessage {
pub(crate) fn new(kind: KerberosMessageKind, pvno: u32, realm: String) -> Self {
Self {
kind,
pvno,
realm,
cname: None,
sname: None,
etypes: Vec::new(),
padata_types: Vec::new(),
kerberoast_suspect: false,
error_code: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_code_round_trip_named() {
let named = [6, 7, 18, 24, 25, 28, 51, 52];
for raw in named {
let code = KerberosErrorCode::from_raw(raw);
assert_eq!(code.as_raw(), raw);
assert!(!matches!(code, KerberosErrorCode::Other(_)));
}
}
#[test]
fn error_code_other_preserves_value() {
let oddballs = [0i32, -1, 9999, i32::MAX, i32::MIN];
for raw in oddballs {
let code = KerberosErrorCode::from_raw(raw);
assert!(matches!(code, KerberosErrorCode::Other(_)));
assert_eq!(code.as_raw(), raw);
}
}
#[test]
fn brute_force_signal_predicate() {
assert!(KerberosErrorCode::KdcErrPreauthFailed.is_brute_force_signal());
assert!(KerberosErrorCode::KdcErrClientRevoked.is_brute_force_signal());
assert!(KerberosErrorCode::KdcErrCPrincipalUnknown.is_brute_force_signal());
assert!(!KerberosErrorCode::KdcErrPreauthRequired.is_brute_force_signal());
assert!(!KerberosErrorCode::Other(0).is_brute_force_signal());
}
}