diff --git a/libkrimes/src/asn1/constants/errors.rs b/libkrimes/src/asn1/constants/errors.rs index 1d352e8..c20198e 100644 --- a/libkrimes/src/asn1/constants/errors.rs +++ b/libkrimes/src/asn1/constants/errors.rs @@ -1,6 +1,6 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; -#[derive(Debug, TryFromPrimitive, IntoPrimitive)] +#[derive(Debug, Clone, TryFromPrimitive, IntoPrimitive)] #[repr(i32)] pub enum KrbErrorCode { KdcErrNone = 0, // No error diff --git a/libkrimes/src/asn1/mod.rs b/libkrimes/src/asn1/mod.rs index 03c1ac3..12a6fc0 100644 --- a/libkrimes/src/asn1/mod.rs +++ b/libkrimes/src/asn1/mod.rs @@ -32,6 +32,5 @@ pub mod tagged_ticket; pub mod ticket_flags; pub mod transited_encoding; -pub use der::asn1::BitString; pub use der::asn1::Ia5String; pub use der::asn1::OctetString; diff --git a/libkrimes/src/lib.rs b/libkrimes/src/lib.rs index bf3dd97..c2be77e 100644 --- a/libkrimes/src/lib.rs +++ b/libkrimes/src/lib.rs @@ -16,7 +16,7 @@ #![deny(clippy::manual_let_else)] #![allow(clippy::unreachable)] -pub mod asn1; +mod asn1; pub mod ccache; pub(crate) mod cksum; pub mod cldap; @@ -35,10 +35,7 @@ use proto::{KerberosReply, KerberosRequest}; use std::io; use tokio_util::codec::{Decoder, Encoder}; -pub struct KdcTcpCodec { - max_size: usize, -} - +// This is the client codec. Decodes into a KerberosReply, encodes into a KerberosRequest pub struct KerberosTcpCodec { max_size: usize, } @@ -137,6 +134,11 @@ impl Encoder for KerberosTcpCodec { } } +// This is the server codec. Decodes into a KerberosRequest, encodes into a KerberosReply +pub struct KdcTcpCodec { + max_size: usize, +} + impl Default for KdcTcpCodec { fn default() -> Self { KdcTcpCodec { @@ -242,7 +244,7 @@ mod tests { use std::time::{Duration, SystemTime}; use super::KerberosTcpCodec; - use crate::proto::{AuthenticationReply, DerivedKey, KerberosRequest, Name, PreauthReply}; + use crate::proto::{AuthenticationReply, DerivedKey, KerberosRequest, Name, PreauthErrorReply}; use futures::StreamExt; use tracing::{trace, warn}; @@ -266,7 +268,7 @@ mod tests { let now = SystemTime::now(); let client_name = Name::principal("testuser", "EXAMPLE.COM"); - let as_req = KerberosRequest::build_as( + let as_req = KerberosRequest::as_builder( &client_name, Name::service_krbtgt("EXAMPLE.COM"), now + Duration::from_secs(3600), @@ -325,7 +327,7 @@ mod tests { let session_key = cleartext.key; let now = SystemTime::now(); - let tgs_req = KerberosRequest::build_tgs( + let tgs_req = KerberosRequest::tgs_builder( Name::service("host", "pepper.example.com", "EXAMPLE.COM"), now, now + Duration::from_secs(3600), @@ -368,7 +370,7 @@ mod tests { let now = SystemTime::now(); let client_name = Name::principal("testuser_preauth", "EXAMPLE.COM"); - let as_req = KerberosRequest::build_as( + let as_req = KerberosRequest::as_builder( &client_name, Name::service_krbtgt("EXAMPLE.COM"), now + Duration::from_secs(3600), @@ -390,7 +392,7 @@ mod tests { assert!(response.is_ok()); let response = response.unwrap(); - let KerberosReply::PA(PreauthReply { + let KerberosReply::PA(PreauthErrorReply { service, pa_data, stime: _, @@ -432,7 +434,7 @@ mod tests { .expect("Failed to convert value"); let client_name = Name::principal("testuser_preauth", "EXAMPLE.COM"); - let as_req = KerberosRequest::build_as( + let as_req = KerberosRequest::as_builder( &client_name, Name::service_krbtgt("EXAMPLE.COM"), now + Duration::from_secs(3600), @@ -452,7 +454,7 @@ mod tests { ) .unwrap(); - let as_req = KerberosRequest::build_asreq( + let as_req = KerberosRequest::as_builder( Name::principal("testuser_preauth", "EXAMPLE.COM"), Name::service_krbtgt("EXAMPLE.COM"), None, diff --git a/libkrimes/src/proto/mod.rs b/libkrimes/src/proto/mod.rs index 9af2d9e..33565f0 100644 --- a/libkrimes/src/proto/mod.rs +++ b/libkrimes/src/proto/mod.rs @@ -3,9 +3,13 @@ mod reply; mod request; mod time; -pub use self::reply::{AuthenticationReply, KerberosReply, PreauthReply, TicketGrantReply}; +pub use self::reply::{ + AuthenticationReply, AuthenticationReplyBuilder, KerberosReply, PreauthErrorReply, + TicketGrantReply, +}; pub use self::request::{ - AuthenticationRequest, KerberosRequest, TicketGrantRequest, TicketGrantRequestUnverified, + AuthenticationRequest, AuthenticationRequestBuilder, KerberosRequest, TicketGrantRequest, + TicketGrantRequestUnverified, }; pub use self::time::{ AuthenticationTimeBound, TicketGrantTimeBound, TicketRenewTimeBound, TimeBoundError, @@ -1520,7 +1524,7 @@ pub async fn get_tgt( let now = SystemTime::now(); let client_name = Name::principal(principal, realm); - let as_req = KerberosRequest::build_as( + let as_req = KerberosRequest::as_builder( &client_name, Name::service_krbtgt(realm), now + Duration::from_secs(3600), diff --git a/libkrimes/src/proto/reply.rs b/libkrimes/src/proto/reply.rs deleted file mode 100644 index dc5c006..0000000 --- a/libkrimes/src/proto/reply.rs +++ /dev/null @@ -1,1090 +0,0 @@ -use super::{ - AuthenticationTimeBound, DerivedKey, EncTicket, EncryptedData, EtypeInfo2, KdcPrimaryKey, - LastRequestItem, Name, PreauthData, SessionKey, Ticket, TicketGrantRequest, - TicketGrantTimeBound, TicketRenewTimeBound, -}; -use crate::asn1::{ - authorization_data::AuthorizationData, - constants::{ - authorization_data_types::AuthorizationDataType, encryption_types::EncryptionType, - errors::KrbErrorCode, message_types::KrbMessageType, pa_data_types::PaDataType, - }, - enc_kdc_rep_part::EncKdcRepPart, - enc_ticket_part::EncTicketPart, - encryption_key::EncryptionKey as KdcEncryptionKey, - etype_info2::ETypeInfo2Entry as KdcETypeInfo2Entry, - kdc_rep::KdcRep, - kerberos_string::KerberosString, - kerberos_time::KerberosTime, - krb_error::{KrbError as KdcKrbError, MethodData}, - krb_kdc_rep::KrbKdcRep, - last_req::LastReqItem, - pa_data::PaData, - ticket_flags::TicketFlags, - transited_encoding::TransitedEncoding, - Ia5String, OctetString, -}; -use crate::constants::PBKDF2_SHA1_ITER; -use crate::error::KrbError; -use crate::proto::ms_pac::AdWin2kPac; -use der::{Decode, Encode}; -use std::time::{Duration, SystemTime}; -use tracing::{error, trace}; - -#[derive(Debug)] -pub enum KerberosReply { - AS(AuthenticationReply), - TGS(TicketGrantReply), - PA(PreauthReply), - ERR(ErrorReply), -} - -#[derive(Debug)] -pub struct AuthenticationReply { - pub name: Name, - pub enc_part: EncryptedData, - pub pa_data: Option, - pub ticket: EncTicket, -} - -#[derive(Debug)] -pub struct TicketGrantReply { - pub client_name: Name, - pub enc_part: EncryptedData, - pub ticket: EncTicket, -} - -#[derive(Debug)] -pub struct PreauthReply { - pub pa_data: PreauthData, - pub service: Name, - pub stime: SystemTime, -} - -#[derive(Debug)] -pub struct ErrorReply { - code: KrbErrorCode, - service: Name, - error_text: Option, - stime: SystemTime, -} - -pub struct KerberosReplyPreauthBuilder { - pa_fx_cookie: Option>, - aes256_cts_hmac_sha1_96_iter_count: u32, - salt: Option, - service: Name, - stime: SystemTime, -} - -pub struct KerberosReplyAuthenticationBuilder { - aes256_cts_hmac_sha1_96_iter_count: u32, - salt: Option, - client: Name, - server: Name, - - nonce: i32, - - time_bounds: AuthenticationTimeBound, - - flags: TicketFlags, -} - -pub struct KerberosReplyTicketGrantBuilder { - nonce: i32, - service_name: Name, - sub_session_key: Option, - - pac: Option, - - time_bounds: TicketGrantTimeBound, - - ticket: Ticket, - - flags: TicketFlags, -} - -pub struct KerberosReplyTicketRenewBuilder { - nonce: i32, - service_name: Name, - sub_session_key: Option, - - time_bounds: TicketRenewTimeBound, - - ticket: Ticket, -} - -impl KerberosReply { - pub fn preauth_builder(service: Name, stime: SystemTime) -> KerberosReplyPreauthBuilder { - let aes256_cts_hmac_sha1_96_iter_count: u32 = PBKDF2_SHA1_ITER; - KerberosReplyPreauthBuilder { - pa_fx_cookie: None, - aes256_cts_hmac_sha1_96_iter_count, - salt: None, - service, - stime, - } - } - - pub fn authentication_builder( - client: Name, - server: Name, - time_bounds: AuthenticationTimeBound, - nonce: i32, - ) -> KerberosReplyAuthenticationBuilder { - let aes256_cts_hmac_sha1_96_iter_count: u32 = PBKDF2_SHA1_ITER; - - let mut flags = TicketFlags::none(); - if time_bounds.renew_until().is_some() { - flags |= TicketFlags::Renewable; - } - - KerberosReplyAuthenticationBuilder { - aes256_cts_hmac_sha1_96_iter_count, - salt: None, - client, - server, - - nonce, - - time_bounds, - flags, - } - } - - pub fn ticket_renew_builder( - ticket_grant_request: TicketGrantRequest, - time_bounds: TicketRenewTimeBound, - ) -> KerberosReplyTicketRenewBuilder { - let TicketGrantRequest { - nonce, - service_name, - from: _, - until: _, - renew: _, - etypes: _, - sub_session_key, - client_time: _, - ticket, - } = ticket_grant_request; - - KerberosReplyTicketRenewBuilder { - nonce, - service_name, - - sub_session_key, - - time_bounds, - - ticket, - } - } - - pub fn ticket_grant_builder( - ticket_grant_request: TicketGrantRequest, - time_bounds: TicketGrantTimeBound, - ) -> KerberosReplyTicketGrantBuilder { - let TicketGrantRequest { - nonce, - service_name, - from: _, - until: _, - renew: _, - etypes: _, - sub_session_key, - client_time: _, - ticket, - } = ticket_grant_request; - - let mut flags = TicketFlags::none(); - if time_bounds.renew_until().is_some() { - flags |= TicketFlags::Renewable; - } - - // From is what the client requested. - // Now is the kdc time. - // ticket.start_time is when the ticket began. - - KerberosReplyTicketGrantBuilder { - nonce, - service_name, - - sub_session_key, - - pac: None, - - time_bounds, - ticket, - - flags, - } - } - - pub fn error_request_invalid(service: Name, stime: SystemTime) -> KerberosReply { - KerberosReply::ERR(ErrorReply { - code: KrbErrorCode::KrbErrGeneric, - service, - error_text: Some( - "The Kerberos Client sent a malformed and invalid request.".to_string(), - ), - stime, - }) - } - - pub fn error_request_failed_validation(service: Name, stime: SystemTime) -> KerberosReply { - KerberosReply::ERR(ErrorReply { - code: KrbErrorCode::KrbErrGeneric, - service, - error_text: Some( - "The Kerberos Client sent a request that was cryptographically invalid." - .to_string(), - ), - stime, - }) - } - - pub fn error_no_etypes(service: Name, stime: SystemTime) -> KerberosReply { - KerberosReply::ERR(ErrorReply { - code: KrbErrorCode::KdcErrEtypeNosupp, - service, - error_text: Some( - "Client and Server do not have overlapping encryption type support.".to_string(), - ), - stime, - }) - } - - pub fn error_preauth_failed(service: Name, stime: SystemTime) -> KerberosReply { - KerberosReply::ERR(ErrorReply { - code: KrbErrorCode::KdcErrPreauthFailed, - service, - error_text: Some( - "Preauthentication Failed - Check your password is correct.".to_string(), - ), - stime, - }) - } - - pub fn error_client_principal(service: Name, stime: SystemTime) -> KerberosReply { - KerberosReply::ERR(ErrorReply { - code: KrbErrorCode::KdcErrPreauthFailed, - service, - error_text: Some( - "Preauthentication Failed - Client Name was not a valid Principal.".to_string(), - ), - stime, - }) - } - - pub fn error_client_realm(service: Name, stime: SystemTime) -> KerberosReply { - KerberosReply::ERR(ErrorReply { - code: KrbErrorCode::KdcErrWrongRealm, - service, - error_text: Some("Preauthentication Failed - Check your realm is correct.".to_string()), - stime, - }) - } - - pub fn error_client_username(service: Name, stime: SystemTime) -> KerberosReply { - KerberosReply::ERR(ErrorReply { - code: KrbErrorCode::KdcErrCPrincipalUnknown, - service, - error_text: Some( - "Preauthentication Failed - Check your username is correct.".to_string(), - ), - stime, - }) - } - - pub fn error_service_name(service: Name, stime: SystemTime) -> KerberosReply { - KerberosReply::ERR(ErrorReply { - code: KrbErrorCode::KdcErrSPrincipalUnknown, - service, - error_text: Some("Ticket Request Failed - Service Name not found.".to_string()), - stime, - }) - } - - pub fn error_as_not_krbtgt(service: Name, stime: SystemTime) -> KerberosReply { - KerberosReply::ERR(ErrorReply { - code: KrbErrorCode::KdcErrSvcUnavailable, - service, - error_text: Some( - "Authentication (ASREQ) must only be for service instance `krbtgt@REALM`." - .to_string(), - ), - stime, - }) - } - - pub fn error_no_key(service: Name, stime: SystemTime) -> KerberosReply { - KerberosReply::ERR(ErrorReply { - code: KrbErrorCode::KrbApErrNokey, - service, - error_text: Some("No Key Available".to_string()), - stime, - }) - } - - pub fn error_clock_skew(service: Name, stime: SystemTime) -> KerberosReply { - KerberosReply::ERR(ErrorReply { - code: KrbErrorCode::KrbApErrSkew, - service, - error_text: Some("Clock Skew too great".to_string()), - stime, - }) - } - - pub fn error_cannot_postdate(service: Name, stime: SystemTime) -> KerberosReply { - KerberosReply::ERR(ErrorReply { - code: KrbErrorCode::KdcErrCannotPostdate, - service, - error_text: Some("Ticket not elegible for postdating".to_string()), - stime, - }) - } - - pub fn error_never_valid(service: Name, stime: SystemTime) -> KerberosReply { - KerberosReply::ERR(ErrorReply { - code: KrbErrorCode::KdcErrNeverValid, - service, - error_text: Some("Requested ticket start time is later than end time".to_string()), - stime, - }) - } - - pub fn error_renew_denied(service: Name, stime: SystemTime) -> KerberosReply { - KerberosReply::ERR(ErrorReply { - code: KrbErrorCode::KdcErrPolicy, - service, - error_text: Some("Requested ticket is unable to be renewed".to_string()), - stime, - }) - } - - pub fn error_inappropiate_checksum(service: Name, stime: SystemTime) -> KerberosReply { - KerberosReply::ERR(ErrorReply { - code: KrbErrorCode::KrbApErrInappCksum, - service, - error_text: Some("Inappropriate type of checksum in message".to_string()), - stime, - }) - } - - pub fn error_unsupported_checksum(service: Name, stime: SystemTime) -> KerberosReply { - KerberosReply::ERR(ErrorReply { - code: KrbErrorCode::KdcErrSumtypeNosupp, - service, - error_text: Some("KDC has no support for checksum type".to_string()), - stime, - }) - } - - pub fn error_internal(service: Name, stime: SystemTime) -> KerberosReply { - KerberosReply::ERR(ErrorReply { - code: KrbErrorCode::KrbErrGeneric, - service, - error_text: Some("Internal Server Error".to_string()), - stime, - }) - } -} - -impl KerberosReplyPreauthBuilder { - pub fn set_key_params(mut self, dk: &DerivedKey) -> Self { - match dk { - DerivedKey::Aes256CtsHmacSha196 { i, s, .. } => { - self.salt = Some(s.clone()); - self.aes256_cts_hmac_sha1_96_iter_count = *i; - self - } - } - } - - pub fn set_pa_fx_cookie(mut self, cookie: Option>) -> Self { - self.pa_fx_cookie = cookie; - self - } - - pub fn build(self) -> KerberosReply { - let aes256_cts_hmac_sha1_96_iter_count = Some( - self.aes256_cts_hmac_sha1_96_iter_count - .to_be_bytes() - .to_vec(), - ); - - KerberosReply::PA(PreauthReply { - pa_data: PreauthData { - // pa_fx_fast: false, - enc_timestamp: true, - pa_fx_cookie: self.pa_fx_cookie, - etype_info2: vec![EtypeInfo2 { - etype: EncryptionType::AES256_CTS_HMAC_SHA1_96, - salt: self.salt, - s2kparams: aes256_cts_hmac_sha1_96_iter_count, - }], - }, - service: self.service, - stime: self.stime, - }) - } -} - -impl KerberosReplyAuthenticationBuilder { - pub fn set_salt(mut self, salt: Option) -> Self { - self.salt = salt; - self - } - - pub fn set_aes256_cts_hmac_sha1_96_iter_count(mut self, iter_count: u32) -> Self { - self.aes256_cts_hmac_sha1_96_iter_count = iter_count; - self - } - - pub fn build( - self, - user_key: &DerivedKey, - primary_key: &KdcPrimaryKey, - ) -> Result { - // Build and encrypt the reply. - let session_key = SessionKey::new(); - let session_key: KdcEncryptionKey = session_key.try_into()?; - - let (cname, crealm) = (&self.client).try_into()?; - let (server_name, server_realm) = (&self.server).try_into()?; - - let auth_time = KerberosTime::from_system_time(self.time_bounds.auth_time()) - .map_err(|_| KrbError::DerEncodeKerberosTime)?; - let start_time = Some( - KerberosTime::from_system_time(self.time_bounds.start_time()) - .map_err(|_| KrbError::DerEncodeKerberosTime)?, - ); - let end_time = KerberosTime::from_system_time(self.time_bounds.end_time()) - .map_err(|_| KrbError::DerEncodeKerberosTime)?; - let renew_till = self - .time_bounds - .renew_until() - .map(KerberosTime::from_system_time) - .transpose() - .map_err(|_| KrbError::DerEncodeKerberosTime)?; - - let last_req: Vec = vec![LastRequestItem::None(SystemTime::UNIX_EPOCH)]; - let last_req = last_req - .iter() - .map(|i| i.try_into()) - .collect::, KrbError>>()?; - - let enc_kdc_rep_part = EncKdcRepPart { - key: session_key.clone(), - last_req, - nonce: self.nonce, - key_expiration: None, - flags: self.flags, - auth_time, - start_time, - end_time, - renew_till, - server_realm, - server_name, - client_addresses: None, - }; - - let (etype_info2, enc_part) = user_key.encrypt_as_rep_part(enc_kdc_rep_part)?; - - let transited = TransitedEncoding { - tr_type: 1, - // Since no transit has occured, we record an empty str. - contents: OctetString::new(b"").map_err(|_| KrbError::DerEncodeOctetString)?, - }; - - let ticket_inner = EncTicketPart { - flags: self.flags, - key: session_key, - crealm, - cname, - transited, - auth_time, - start_time, - end_time, - renew_till, - client_addresses: None, - authorization_data: None, - }; - - let ticket_enc_part = primary_key.encrypt_tgt(ticket_inner)?; - - let ticket = EncTicket { - tkt_vno: 5, - service: self.server, - enc_part: ticket_enc_part, - }; - - let name = self.client; - - let pa_data = Some(PreauthData { - etype_info2: vec![etype_info2], - ..Default::default() - }); - - Ok(KerberosReply::AS(AuthenticationReply { - name, - enc_part, - pa_data, - ticket, - })) - } -} - -impl KerberosReplyTicketGrantBuilder { - pub fn build(mut self, service_key: &DerivedKey) -> Result { - let service_session_key = SessionKey::new(); - let service_session_key: KdcEncryptionKey = service_session_key.try_into()?; - - let (cname, crealm) = (&self.ticket.client_name).try_into()?; - let (server_name, server_realm) = (&self.service_name).try_into()?; - - let auth_time = KerberosTime::from_system_time(self.ticket.auth_time) - .map_err(|_| KrbError::DerEncodeKerberosTime)?; - let start_time = Some( - KerberosTime::from_system_time(self.time_bounds.start_time()) - .map_err(|_| KrbError::DerEncodeKerberosTime)?, - ); - let end_time = KerberosTime::from_system_time(self.time_bounds.end_time()) - .map_err(|_| KrbError::DerEncodeKerberosTime)?; - - let renew_till = if let Some(renew_until) = self.time_bounds.renew_until() { - self.flags |= TicketFlags::Renewable; - Some( - KerberosTime::from_system_time(renew_until) - .map_err(|_| KrbError::DerEncodeKerberosTime)?, - ) - } else { - None - }; - - // TGS_REP The ciphertext is encrypted with the sub-session key - // from the authenticator. - // If absent, the session key is used (with no kvno). - // 5.4.2 reads as though the the clients version number is used here for kvno? - - // KeyUsage == 8 for session key, or == 9 for subkey. - // let enc_part = EncTGSRepPart == EncKDCRepPart; - - let enc_kdc_rep_part = EncKdcRepPart { - key: service_session_key.clone(), - // Not 100% clear on this field. - last_req: Vec::with_capacity(0), - nonce: self.nonce, - key_expiration: None, - flags: self.flags, - auth_time, - start_time, - end_time, - renew_till, - server_realm, - server_name, - client_addresses: None, - }; - - // Encrypt this with the original tickets sub_session_key so that they - // can decrypt this and get the service_session_key out. - let enc_part = if let Some(sub_session_key) = self.sub_session_key { - sub_session_key.encrypt_tgs_rep_part(enc_kdc_rep_part, true)? - } else { - self.ticket - .session_key - .encrypt_tgs_rep_part(enc_kdc_rep_part, false)? - }; - - // An MS-PAC is required for Samba to work. - let authorization_data = if let Some(pac) = self.pac { - // Need to work out the signatures here. - - let pac_data_inner = - OctetString::new(pac.to_bytes()).map_err(|_| KrbError::DerEncodeOctetString)?; - - let pac_data = AuthorizationData { - ad_type: AuthorizationDataType::AdWin2kPac.into(), - ad_data: pac_data_inner, - } - .to_der() - .and_then(OctetString::new) - .map_err(|_| KrbError::DerEncodeOctetString)?; - - Some(vec![AuthorizationData { - ad_type: AuthorizationDataType::AdIfRelevant.into(), - ad_data: pac_data, - }]) - } else { - None - }; - - let transited = TransitedEncoding { - tr_type: 1, - // Since no transit has occured, we record an empty str. - contents: OctetString::new(b"").map_err(|_| KrbError::DerEncodeOctetString)?, - }; - - // EncTicketPart - // Encrypted to the key of the service - this is what the ticket holder - // forwards to the service to that it is aware of it's service session key. - let ticket_inner = EncTicketPart { - flags: self.flags, - key: service_session_key, - crealm, - cname, - transited, - auth_time, - start_time, - end_time, - renew_till, - client_addresses: None, - authorization_data, - }; - - let ticket_enc_part = service_key.encrypt_tgs(ticket_inner)?; - - let ticket = EncTicket { - tkt_vno: 5, - service: self.service_name, - enc_part: ticket_enc_part, - }; - - let client_name = self.ticket.client_name; - - Ok(KerberosReply::TGS(TicketGrantReply { - client_name, - enc_part, - ticket, - })) - } -} - -impl KerberosReplyTicketRenewBuilder { - pub fn build(self, primary_key: &KdcPrimaryKey) -> Result { - let (cname, crealm) = (&self.ticket.client_name).try_into()?; - let (server_name, server_realm) = (&self.service_name).try_into()?; - - let auth_time = KerberosTime::from_system_time(self.ticket.auth_time) - .map_err(|_| KrbError::DerEncodeKerberosTime)?; - let start_time = Some( - KerberosTime::from_system_time(self.time_bounds.start_time()) - .map_err(|_| KrbError::DerEncodeKerberosTime)?, - ); - let end_time = KerberosTime::from_system_time(self.time_bounds.end_time()) - .map_err(|_| KrbError::DerEncodeKerberosTime)?; - let renew_till = Some( - KerberosTime::from_system_time(self.time_bounds.renew_until()) - .map_err(|_| KrbError::DerEncodeKerberosTime)?, - ); - - let session_key: KdcEncryptionKey = self.ticket.session_key.clone().try_into()?; - - // TGS_REP The ciphertext is encrypted with the sub-session key - // from the authenticator. - // If absent, the session key is used (with no kvno). - // 5.4.2 reads as though the the clients version number is used here for kvno? - - // KeyUsage == 8 for session key, or == 9 for subkey. - // let enc_part = EncTGSRepPart == EncKDCRepPart; - - let enc_kdc_rep_part = EncKdcRepPart { - key: session_key.clone(), - // Not 100% clear on this field. - last_req: Vec::with_capacity(0), - nonce: self.nonce, - key_expiration: None, - flags: self.ticket.flags, - auth_time, - start_time, - end_time, - renew_till, - server_realm, - server_name, - client_addresses: None, - }; - - let enc_part = if let Some(sub_session_key) = self.sub_session_key { - sub_session_key.encrypt_tgs_rep_part(enc_kdc_rep_part, true)? - } else { - self.ticket - .session_key - .encrypt_tgs_rep_part(enc_kdc_rep_part, false)? - }; - - let authorization_data = None; - - let transited = TransitedEncoding { - tr_type: 1, - // Since no transit has occured, we record an empty str. - contents: OctetString::new(b"").map_err(|_| KrbError::DerEncodeOctetString)?, - }; - - // EncTicketPart - // Encrypted to the key of the service - let ticket_inner = EncTicketPart { - flags: self.ticket.flags, - key: session_key, - crealm, - cname, - transited, - auth_time, - start_time, - end_time, - renew_till, - client_addresses: None, - authorization_data, - }; - - let ticket_enc_part = primary_key.encrypt_tgs(ticket_inner)?; - - let ticket = EncTicket { - tkt_vno: 5, - service: self.service_name, - enc_part: ticket_enc_part, - }; - - let client_name = self.ticket.client_name; - - Ok(KerberosReply::TGS(TicketGrantReply { - client_name, - enc_part, - ticket, - })) - } -} - -impl TryFrom for KerberosReply { - type Error = KrbError; - - fn try_from(rep: KrbKdcRep) -> Result { - match rep { - KrbKdcRep::AsRep(kdc_rep) | KrbKdcRep::TgsRep(kdc_rep) => { - KerberosReply::try_from(kdc_rep) - } - KrbKdcRep::ErrRep(err_rep) => KerberosReply::try_from(err_rep), - } - } -} - -impl TryFrom for KerberosReply { - type Error = KrbError; - - fn try_from(rep: KdcKrbError) -> Result { - // assert the pvno and msg_type - if rep.pvno != 5 { - return Err(KrbError::InvalidPvno); - } - - let service = Name::try_from((rep.service_name, rep.service_realm))?; - - let msg_type = - KrbMessageType::try_from(rep.msg_type).map_err(|_| KrbError::InvalidMessageType)?; - - if !matches!(msg_type, KrbMessageType::KrbError) { - return Err(KrbError::InvalidMessageDirection); - } - - let error_code = KrbErrorCode::try_from(rep.error_code).map_err(|_| { - error!(?rep.error_code, "Unable to encode error code"); - KrbError::DerEncodeKrbErrorCode - })?; - - let stime = rep.stime.to_system_time(); - let microsecs = Duration::from_micros(rep.susec as u64); - - let stime = stime + microsecs; - - match error_code { - KrbErrorCode::KdcErrPreauthRequired => { - let edata = rep.error_data.ok_or(KrbError::MissingPaData)?; - - let pavec: Vec = MethodData::from_der(edata.as_bytes()) - .map_err(|_| KrbError::DerDecodePaData)?; - - let pa_data = PreauthData::try_from(pavec)?; - - Ok(KerberosReply::PA(PreauthReply { - pa_data, - service, - stime, - })) - } - code => { - let error_text = rep.error_text.as_ref().map(|s| s.to_string()); - - Ok(KerberosReply::ERR(ErrorReply { - code, - service, - error_text, - stime, - })) - } - } - } -} - -impl TryInto for KerberosReply { - type Error = KrbError; - - fn try_into(self) -> Result { - match self { - KerberosReply::AS(AuthenticationReply { - name, - enc_part, - pa_data, - ticket, - }) => { - let pa_data: Option> = match pa_data { - Some(data) => { - let etype_padata_vec = data - .etype_info2 - .iter() - .map(|einfo| { - let salt = einfo - .salt - .as_ref() - .map(Ia5String::new) - .transpose() - .map_err(|_| KrbError::DerEncodeKerberosString)? - .map(KerberosString); - let s2kparams = einfo - .s2kparams - .as_ref() - .map(|data| OctetString::new(data.to_owned())) - .transpose() - .map_err(|_| KrbError::DerEncodeOctetString)?; - Ok(KdcETypeInfo2Entry { - etype: einfo.etype as i32, - salt, - s2kparams, - }) - }) - .collect::, KrbError>>()?; - - let etype_padata_value = etype_padata_vec - .to_der() - .and_then(OctetString::new) - .map_err(|_| KrbError::DerEncodeOctetString)?; - - let pavec = vec![PaData { - padata_type: PaDataType::PaEtypeInfo2 as u32, - padata_value: etype_padata_value, - }]; - Some(pavec) - } - None => None, - }; - - let as_rep = KdcRep { - pvno: 5, - msg_type: KrbMessageType::KrbAsRep as u8, - padata: pa_data, - crealm: (&name).try_into()?, - cname: (&name).try_into()?, - ticket: ticket.try_into()?, - enc_part: enc_part.try_into()?, - }; - - Ok(KrbKdcRep::AsRep(as_rep)) - } - KerberosReply::TGS(TicketGrantReply { - client_name, - enc_part, - ticket, - }) => { - let tgs_rep = KdcRep { - pvno: 5, - msg_type: KrbMessageType::KrbTgsRep as u8, - padata: None, - crealm: (&client_name).try_into()?, - cname: (&client_name).try_into()?, - ticket: ticket.try_into()?, - enc_part: enc_part.try_into()?, - }; - - Ok(KrbKdcRep::TgsRep(tgs_rep)) - } - KerberosReply::PA(PreauthReply { - pa_data, - service, - stime, - }) => { - let error_code = KrbErrorCode::KdcErrPreauthRequired as i32; - // The pre-auth data is stuffed into error_data. Because of course kerberos can't - // do nice things. - let etype_padata_vec = pa_data - .etype_info2 - .iter() - .map(|einfo| { - let salt = einfo - .salt - .as_ref() - .map(Ia5String::new) - .transpose() - .map_err(|_| KrbError::DerEncodeKerberosString)? - .map(KerberosString); - - let s2kparams = einfo - .s2kparams - .as_ref() - .map(|data| OctetString::new(data.to_owned())) - .transpose() - .map_err(|_| KrbError::DerEncodeOctetString)?; - Ok(KdcETypeInfo2Entry { - etype: einfo.etype as i32, - salt, - s2kparams, - }) - }) - .collect::, KrbError>>()?; - - let etype_padata_value = etype_padata_vec - .to_der() - .and_then(OctetString::new) - .map_err(|_| KrbError::DerEncodeOctetString)?; - - let pavec = vec![ - PaData { - padata_type: PaDataType::PaEncTimestamp as u32, - padata_value: OctetString::new([]) - .map_err(|_| KrbError::DerEncodeOctetString)?, - }, - PaData { - padata_type: PaDataType::PaEtypeInfo2 as u32, - padata_value: etype_padata_value, - }, - ]; - - let error_data = pavec - .to_der() - .and_then(OctetString::new) - .map(Some) - .map_err(|_| KrbError::DerEncodeOctetString)?; - - let error_text = Ia5String::new("Preauthentication Required") - .map(KerberosString) - .ok(); - - let stime = stime - .duration_since(SystemTime::UNIX_EPOCH) - // We need to stip the fractional part. - .map(|t| Duration::from_secs(t.as_secs())) - .unwrap_or_default(); - - let stime = KerberosTime::from_unix_duration(stime) - .map_err(|_| KrbError::DerEncodeKerberosTime)?; - - let (service_name, service_realm) = (&service).try_into()?; - - let krb_error = KdcKrbError { - pvno: 5, - msg_type: KrbMessageType::KrbError as u8, - ctime: None, - cusec: None, - stime, - susec: 0, - error_code, - crealm: None, - cname: None, - service_realm, - service_name, - error_text, - error_data, - }; - - Ok(KrbKdcRep::ErrRep(krb_error)) - } - KerberosReply::ERR(ErrorReply { - code, - service, - error_text, - stime, - }) => { - let error_code = code as i32; - - let error_text = error_text - .as_ref() - .and_then(|et| Ia5String::new(&et).map(KerberosString).ok()); - - let stime = stime - .duration_since(SystemTime::UNIX_EPOCH) - // We need to stip the fractional part. - .map(|t| Duration::from_secs(t.as_secs())) - .unwrap_or_default(); - - let stime = KerberosTime::from_unix_duration(stime) - .map_err(|_| KrbError::DerEncodeKerberosTime)?; - - let (service_name, service_realm) = (&service).try_into()?; - - let krb_error = KdcKrbError { - pvno: 5, - msg_type: KrbMessageType::KrbError as u8, - ctime: None, - cusec: None, - stime, - susec: 0, - error_code, - crealm: None, - cname: None, - service_realm, - service_name, - error_text, - error_data: None, - }; - - Ok(KrbKdcRep::ErrRep(krb_error)) - } - } - } -} - -impl TryFrom for KerberosReply { - type Error = KrbError; - - fn try_from(rep: KdcRep) -> Result { - // assert the pvno and msg_type - if rep.pvno != 5 { - return Err(KrbError::InvalidPvno); - } - - let msg_type = - KrbMessageType::try_from(rep.msg_type).map_err(|_| KrbError::InvalidMessageType)?; - - match msg_type { - KrbMessageType::KrbAsRep => { - let enc_part = EncryptedData::try_from(rep.enc_part)?; - trace!(?enc_part); - - let pa_data = rep.padata.map(PreauthData::try_from).transpose()?; - trace!(?pa_data); - - let name = (rep.cname, rep.crealm).try_into()?; - let ticket = EncTicket::try_from(rep.ticket)?; - - Ok(KerberosReply::AS(AuthenticationReply { - name, - pa_data, - enc_part, - ticket, - })) - } - KrbMessageType::KrbTgsRep => { - let enc_part = EncryptedData::try_from(rep.enc_part)?; - trace!(?enc_part); - - let client_name = (rep.cname, rep.crealm).try_into()?; - - let ticket = EncTicket::try_from(rep.ticket)?; - - Ok(KerberosReply::TGS(TicketGrantReply { - client_name, - enc_part, - ticket, - })) - } - _ => Err(KrbError::InvalidMessageDirection), - } - } -} diff --git a/libkrimes/src/proto/reply/as_rep.rs b/libkrimes/src/proto/reply/as_rep.rs new file mode 100644 index 0000000..0ca3b53 --- /dev/null +++ b/libkrimes/src/proto/reply/as_rep.rs @@ -0,0 +1,156 @@ +use crate::asn1::enc_kdc_rep_part::EncKdcRepPart; +use crate::asn1::{last_req::LastReqItem, transited_encoding::TransitedEncoding, OctetString}; +use crate::constants::PBKDF2_SHA1_ITER; +use crate::proto::reply::KerberosReply; +use crate::proto::{ + AuthenticationTimeBound, DerivedKey, EncTicket, EncTicketPart, EncryptedData, KdcEncryptionKey, + KdcPrimaryKey, KerberosTime, KrbError, LastRequestItem, Name, PreauthData, SessionKey, + TicketFlags, +}; +use std::time::SystemTime; + +#[derive(Debug)] +pub struct AuthenticationReply { + pub(crate) name: Name, + pub(crate) enc_part: EncryptedData, + pub(crate) pa_data: Option, + pub(crate) ticket: EncTicket, +} + +pub struct AuthenticationReplyBuilder { + pub(crate) aes256_cts_hmac_sha1_96_iter_count: u32, + pub(crate) salt: Option, + pub(crate) client: Name, + pub(crate) server: Name, + pub(crate) nonce: i32, + pub(crate) time_bounds: AuthenticationTimeBound, + pub(crate) flags: TicketFlags, +} + +impl AuthenticationReplyBuilder { + pub fn new( + client: Name, + server: Name, + time_bounds: AuthenticationTimeBound, + nonce: i32, + ) -> Self { + let aes256_cts_hmac_sha1_96_iter_count: u32 = PBKDF2_SHA1_ITER; + let mut flags = TicketFlags::none(); + if time_bounds.renew_until().is_some() { + flags |= TicketFlags::Renewable; + } + + Self { + aes256_cts_hmac_sha1_96_iter_count, + salt: None, + client, + server, + nonce, + time_bounds, + flags, + } + } + + pub fn set_salt(mut self, salt: Option) -> Self { + self.salt = salt; + self + } + + pub fn set_aes256_cts_hmac_sha1_96_iter_count(mut self, iter_count: u32) -> Self { + self.aes256_cts_hmac_sha1_96_iter_count = iter_count; + self + } + + pub fn build( + self, + user_key: &DerivedKey, + primary_key: &KdcPrimaryKey, + ) -> Result { + // Build and encrypt the reply. + let session_key = SessionKey::new(); + let session_key: KdcEncryptionKey = session_key.try_into()?; + + let (cname, crealm) = (&self.client).try_into()?; + let (server_name, server_realm) = (&self.server).try_into()?; + + let auth_time = KerberosTime::from_system_time(self.time_bounds.auth_time()) + .map_err(|_| KrbError::DerEncodeKerberosTime)?; + let start_time = Some( + KerberosTime::from_system_time(self.time_bounds.start_time()) + .map_err(|_| KrbError::DerEncodeKerberosTime)?, + ); + let end_time = KerberosTime::from_system_time(self.time_bounds.end_time()) + .map_err(|_| KrbError::DerEncodeKerberosTime)?; + let renew_till = self + .time_bounds + .renew_until() + .map(KerberosTime::from_system_time) + .transpose() + .map_err(|_| KrbError::DerEncodeKerberosTime)?; + + let last_req: Vec = vec![LastRequestItem::None(SystemTime::UNIX_EPOCH)]; + let last_req = last_req + .iter() + .map(|i| i.try_into()) + .collect::, KrbError>>()?; + + let enc_kdc_rep_part = EncKdcRepPart { + key: session_key.clone(), + last_req, + nonce: self.nonce, + key_expiration: None, + flags: self.flags, + auth_time, + start_time, + end_time, + renew_till, + server_realm, + server_name, + client_addresses: None, + }; + + let (etype_info2, enc_part) = user_key.encrypt_as_rep_part(enc_kdc_rep_part)?; + + let transited = TransitedEncoding { + tr_type: 1, + // Since no transit has occured, we record an empty str. + contents: OctetString::new(b"").map_err(|_| KrbError::DerEncodeOctetString)?, + }; + + let ticket_inner = EncTicketPart { + flags: self.flags, + key: session_key, + crealm, + cname, + transited, + auth_time, + start_time, + end_time, + renew_till, + client_addresses: None, + authorization_data: None, + }; + + let ticket_enc_part = primary_key.encrypt_tgt(ticket_inner)?; + + let ticket = EncTicket { + tkt_vno: 5, + service: self.server, + enc_part: ticket_enc_part, + }; + + let name = self.client; + + let pa_data = Some(PreauthData { + etype_info2: vec![etype_info2], + ..Default::default() + }); + + Ok(KerberosReply::AS(AuthenticationReply { + name, + enc_part, + pa_data, + ticket, + })) + } +} diff --git a/libkrimes/src/proto/reply/error_rep.rs b/libkrimes/src/proto/reply/error_rep.rs new file mode 100644 index 0000000..caa7f76 --- /dev/null +++ b/libkrimes/src/proto/reply/error_rep.rs @@ -0,0 +1,111 @@ +use super::{DerivedKey, EtypeInfo2, Name, PreauthData}; +use crate::asn1::constants::{encryption_types::EncryptionType, errors::KrbErrorCode}; +use crate::constants::PBKDF2_SHA1_ITER; +use crate::proto::reply::KerberosReply; +use std::time::SystemTime; + +#[derive(Debug)] +pub struct PreauthErrorReply { + pub pa_data: PreauthData, + pub service: Name, + pub stime: SystemTime, +} + +#[derive(Debug)] +pub struct ErrorReply { + code: KrbErrorCode, + service: Name, + error_text: Option, + server_time: SystemTime, +} + +impl ErrorReply { + pub fn new( + code: KrbErrorCode, + service: Name, + error_text: Option, + server_time: SystemTime, + ) -> Self { + Self { + code, + service, + error_text, + server_time, + } + } + + pub fn code(&self) -> &KrbErrorCode { + &self.code + } + + pub fn service(&self) -> &Name { + &self.service + } + + pub fn text(&self) -> &Option { + &self.error_text + } + + pub fn server_time(&self) -> &SystemTime { + &self.server_time + } +} + +pub struct KerberosReplyPreauthBuilder { + pa_fx_cookie: Option>, + aes256_cts_hmac_sha1_96_iter_count: u32, + salt: Option, + service: Name, + stime: SystemTime, +} + +impl KerberosReplyPreauthBuilder { + pub fn new(service: Name, stime: SystemTime) -> Self { + let aes256_cts_hmac_sha1_96_iter_count: u32 = PBKDF2_SHA1_ITER; + Self { + pa_fx_cookie: None, + aes256_cts_hmac_sha1_96_iter_count, + salt: None, + service, + stime, + } + } + + pub fn set_key_params(mut self, dk: &DerivedKey) -> Self { + match dk { + DerivedKey::Aes256CtsHmacSha196 { i, s, .. } => { + self.salt = Some(s.clone()); + self.aes256_cts_hmac_sha1_96_iter_count = *i; + self + } + } + } + + pub fn set_pa_fx_cookie(mut self, cookie: Option>) -> Self { + self.pa_fx_cookie = cookie; + self + } + + pub fn build(self) -> KerberosReply { + let aes256_cts_hmac_sha1_96_iter_count = Some( + self.aes256_cts_hmac_sha1_96_iter_count + .to_be_bytes() + .to_vec(), + ); + + KerberosReply::PA(PreauthErrorReply { + pa_data: PreauthData { + // pa_fx_fast: false, + enc_timestamp: true, + pa_fx_cookie: self.pa_fx_cookie, + etype_info2: vec![EtypeInfo2 { + etype: EncryptionType::AES256_CTS_HMAC_SHA1_96, + salt: self.salt, + s2kparams: aes256_cts_hmac_sha1_96_iter_count, + }], + }, + service: self.service, + stime: self.stime, + }) + } +} diff --git a/libkrimes/src/proto/reply/mod.rs b/libkrimes/src/proto/reply/mod.rs new file mode 100644 index 0000000..c302d5d --- /dev/null +++ b/libkrimes/src/proto/reply/mod.rs @@ -0,0 +1,506 @@ +mod as_rep; +mod error_rep; +mod tgs_rep; + +use super::{ + AuthenticationTimeBound, DerivedKey, EncTicket, EncryptedData, EtypeInfo2, Name, PreauthData, + TicketGrantRequest, TicketGrantTimeBound, TicketRenewTimeBound, +}; +use crate::asn1::{ + constants::{errors::KrbErrorCode, message_types::KrbMessageType, pa_data_types::PaDataType}, + enc_kdc_rep_part::EncKdcRepPart, + etype_info2::ETypeInfo2Entry as KdcETypeInfo2Entry, + kdc_rep::KdcRep, + kerberos_string::KerberosString, + kerberos_time::KerberosTime, + krb_error::{KrbError as KdcKrbError, MethodData}, + krb_kdc_rep::KrbKdcRep, + pa_data::PaData, + transited_encoding::TransitedEncoding, + Ia5String, OctetString, +}; +use crate::error::KrbError; +pub use as_rep::{AuthenticationReply, AuthenticationReplyBuilder}; +use der::{Decode, Encode}; +pub use error_rep::{ErrorReply, KerberosReplyPreauthBuilder, PreauthErrorReply}; +use std::time::{Duration, SystemTime}; +pub use tgs_rep::{ + KerberosReplyTicketGrantBuilder, KerberosReplyTicketRenewBuilder, TicketGrantReply, +}; +use tracing::{error, trace}; + +#[derive(Debug)] +pub enum KerberosReply { + AS(AuthenticationReply), + TGS(TicketGrantReply), + PA(PreauthErrorReply), + ERR(ErrorReply), +} + +impl KerberosReply { + pub fn preauth_builder(service: Name, stime: SystemTime) -> KerberosReplyPreauthBuilder { + KerberosReplyPreauthBuilder::new(service, stime) + } + + pub fn authentication_builder( + client: Name, + server: Name, + time_bounds: AuthenticationTimeBound, + nonce: i32, + ) -> AuthenticationReplyBuilder { + AuthenticationReplyBuilder::new(client, server, time_bounds, nonce) + } + + pub fn ticket_renew_builder( + ticket_grant_request: TicketGrantRequest, + time_bounds: TicketRenewTimeBound, + ) -> KerberosReplyTicketRenewBuilder { + KerberosReplyTicketRenewBuilder::new(ticket_grant_request, time_bounds) + } + + pub fn ticket_grant_builder( + ticket_grant_request: TicketGrantRequest, + time_bounds: TicketGrantTimeBound, + ) -> KerberosReplyTicketGrantBuilder { + KerberosReplyTicketGrantBuilder::new(ticket_grant_request, time_bounds) + } + + pub fn error_request_invalid(service: Name, stime: SystemTime) -> KerberosReply { + let code = KrbErrorCode::KrbErrGeneric; + let error_text = + Some("The Kerberos Client sent a malformed and invalid request.".to_string()); + KerberosReply::ERR(ErrorReply::new(code, service, error_text, stime)) + } + + pub fn error_request_failed_validation(service: Name, stime: SystemTime) -> KerberosReply { + let code = KrbErrorCode::KrbErrGeneric; + let error_text = Some( + "The Kerberos Client sent a request that was cryptographically invalid.".to_string(), + ); + KerberosReply::ERR(ErrorReply::new(code, service, error_text, stime)) + } + + pub fn error_no_etypes(service: Name, stime: SystemTime) -> KerberosReply { + let code = KrbErrorCode::KdcErrEtypeNosupp; + let error_text = + Some("Client and Server do not have overlapping encryption type support.".to_string()); + KerberosReply::ERR(ErrorReply::new(code, service, error_text, stime)) + } + + pub fn error_preauth_failed(service: Name, stime: SystemTime) -> KerberosReply { + let code = KrbErrorCode::KdcErrPreauthFailed; + let error_text = + Some("Preauthentication Failed - Check your password is correct.".to_string()); + KerberosReply::ERR(ErrorReply::new(code, service, error_text, stime)) + } + + pub fn error_client_principal(service: Name, stime: SystemTime) -> KerberosReply { + let code = KrbErrorCode::KdcErrPreauthFailed; + let error_text = + Some("Preauthentication Failed - Client Name was not a valid Principal.".to_string()); + KerberosReply::ERR(ErrorReply::new(code, service, error_text, stime)) + } + + pub fn error_client_realm(service: Name, stime: SystemTime) -> KerberosReply { + let code = KrbErrorCode::KdcErrWrongRealm; + let error_text = + Some("Preauthentication Failed - Check your realm is correct.".to_string()); + KerberosReply::ERR(ErrorReply::new(code, service, error_text, stime)) + } + + pub fn error_client_username(service: Name, stime: SystemTime) -> KerberosReply { + let code = KrbErrorCode::KdcErrCPrincipalUnknown; + let error_text = + Some("Preauthentication Failed - Check your username is correct.".to_string()); + KerberosReply::ERR(ErrorReply::new(code, service, error_text, stime)) + } + + pub fn error_service_name(service: Name, stime: SystemTime) -> KerberosReply { + let code = KrbErrorCode::KdcErrSPrincipalUnknown; + let error_text = Some("Ticket Request Failed - Service Name not found.".to_string()); + KerberosReply::ERR(ErrorReply::new(code, service, error_text, stime)) + } + + pub fn error_as_not_krbtgt(service: Name, stime: SystemTime) -> KerberosReply { + let code = KrbErrorCode::KdcErrSvcUnavailable; + let error_text = Some( + "Authentication (ASREQ) must only be for service instance `krbtgt@REALM`.".to_string(), + ); + KerberosReply::ERR(ErrorReply::new(code, service, error_text, stime)) + } + + pub fn error_no_key(service: Name, stime: SystemTime) -> KerberosReply { + let code = KrbErrorCode::KrbApErrNokey; + let error_text = Some("No Key Available".to_string()); + KerberosReply::ERR(ErrorReply::new(code, service, error_text, stime)) + } + + pub fn error_clock_skew(service: Name, stime: SystemTime) -> KerberosReply { + let code = KrbErrorCode::KrbApErrSkew; + let error_text = Some("Clock Skew too great".to_string()); + KerberosReply::ERR(ErrorReply::new(code, service, error_text, stime)) + } + + pub fn error_cannot_postdate(service: Name, stime: SystemTime) -> KerberosReply { + let code = KrbErrorCode::KdcErrCannotPostdate; + let error_text = Some("Ticket not elegible for postdating".to_string()); + KerberosReply::ERR(ErrorReply::new(code, service, error_text, stime)) + } + + pub fn error_never_valid(service: Name, stime: SystemTime) -> KerberosReply { + let code = KrbErrorCode::KdcErrNeverValid; + let error_text = Some("Requested ticket start time is later than end time".to_string()); + KerberosReply::ERR(ErrorReply::new(code, service, error_text, stime)) + } + + pub fn error_renew_denied(service: Name, stime: SystemTime) -> KerberosReply { + let code = KrbErrorCode::KdcErrPolicy; + let error_text = Some("Requested ticket is unable to be renewed".to_string()); + KerberosReply::ERR(ErrorReply::new(code, service, error_text, stime)) + } + + pub fn error_inappropiate_checksum(service: Name, stime: SystemTime) -> KerberosReply { + let code = KrbErrorCode::KrbApErrInappCksum; + let error_text = Some("Inappropriate type of checksum in message".to_string()); + KerberosReply::ERR(ErrorReply::new(code, service, error_text, stime)) + } + + pub fn error_unsupported_checksum(service: Name, stime: SystemTime) -> KerberosReply { + let code = KrbErrorCode::KdcErrSumtypeNosupp; + let error_text = Some("KDC has no support for checksum type".to_string()); + KerberosReply::ERR(ErrorReply::new(code, service, error_text, stime)) + } + + pub fn error_internal(service: Name, stime: SystemTime) -> KerberosReply { + let code = KrbErrorCode::KrbErrGeneric; + let error_text = Some("Internal Server Error".to_string()); + KerberosReply::ERR(ErrorReply::new(code, service, error_text, stime)) + } +} + +impl TryFrom for KerberosReply { + type Error = KrbError; + + fn try_from(rep: KrbKdcRep) -> Result { + match rep { + KrbKdcRep::AsRep(kdc_rep) | KrbKdcRep::TgsRep(kdc_rep) => { + KerberosReply::try_from(kdc_rep) + } + KrbKdcRep::ErrRep(err_rep) => KerberosReply::try_from(err_rep), + } + } +} + +impl TryFrom for KerberosReply { + type Error = KrbError; + + fn try_from(rep: KdcKrbError) -> Result { + // assert the pvno and msg_type + if rep.pvno != 5 { + return Err(KrbError::InvalidPvno); + } + + let service = Name::try_from((rep.service_name, rep.service_realm))?; + + let msg_type = + KrbMessageType::try_from(rep.msg_type).map_err(|_| KrbError::InvalidMessageType)?; + + if !matches!(msg_type, KrbMessageType::KrbError) { + return Err(KrbError::InvalidMessageDirection); + } + + let error_code = KrbErrorCode::try_from(rep.error_code).map_err(|_| { + error!(?rep.error_code, "Unable to encode error code"); + KrbError::DerEncodeKrbErrorCode + })?; + + let stime = rep.stime.to_system_time(); + let microsecs = Duration::from_micros(rep.susec as u64); + + let stime = stime + microsecs; + + match error_code { + KrbErrorCode::KdcErrPreauthRequired => { + let edata = rep.error_data.ok_or(KrbError::MissingPaData)?; + + let pavec: Vec = MethodData::from_der(edata.as_bytes()) + .map_err(|_| KrbError::DerDecodePaData)?; + + let pa_data = PreauthData::try_from(pavec)?; + + Ok(KerberosReply::PA(PreauthErrorReply { + pa_data, + service, + stime, + })) + } + code => { + let error_text = rep.error_text.as_ref().map(|s| s.to_string()); + let error = ErrorReply::new(code, service, error_text, stime); + Ok(KerberosReply::ERR(error)) + } + } + } +} + +impl TryInto for KerberosReply { + type Error = KrbError; + + fn try_into(self) -> Result { + match self { + KerberosReply::AS(AuthenticationReply { + name, + enc_part, + pa_data, + ticket, + }) => { + let pa_data: Option> = match pa_data { + Some(data) => { + let etype_padata_vec = data + .etype_info2 + .iter() + .map(|einfo| { + let salt = einfo + .salt + .as_ref() + .map(Ia5String::new) + .transpose() + .map_err(|_| KrbError::DerEncodeKerberosString)? + .map(KerberosString); + let s2kparams = einfo + .s2kparams + .as_ref() + .map(|data| OctetString::new(data.to_owned())) + .transpose() + .map_err(|_| KrbError::DerEncodeOctetString)?; + Ok(KdcETypeInfo2Entry { + etype: einfo.etype as i32, + salt, + s2kparams, + }) + }) + .collect::, KrbError>>()?; + + let etype_padata_value = etype_padata_vec + .to_der() + .and_then(OctetString::new) + .map_err(|_| KrbError::DerEncodeOctetString)?; + + let pavec = vec![PaData { + padata_type: PaDataType::PaEtypeInfo2 as u32, + padata_value: etype_padata_value, + }]; + Some(pavec) + } + None => None, + }; + + let as_rep = KdcRep { + pvno: 5, + msg_type: KrbMessageType::KrbAsRep as u8, + padata: pa_data, + crealm: (&name).try_into()?, + cname: (&name).try_into()?, + ticket: ticket.try_into()?, + enc_part: enc_part.try_into()?, + }; + + Ok(KrbKdcRep::AsRep(as_rep)) + } + KerberosReply::TGS(TicketGrantReply { + client_name, + enc_part, + ticket, + }) => { + let tgs_rep = KdcRep { + pvno: 5, + msg_type: KrbMessageType::KrbTgsRep as u8, + padata: None, + crealm: (&client_name).try_into()?, + cname: (&client_name).try_into()?, + ticket: ticket.try_into()?, + enc_part: enc_part.try_into()?, + }; + + Ok(KrbKdcRep::TgsRep(tgs_rep)) + } + KerberosReply::PA(PreauthErrorReply { + pa_data, + service, + stime, + }) => { + let error_code = KrbErrorCode::KdcErrPreauthRequired as i32; + // The pre-auth data is stuffed into error_data. Because of course kerberos can't + // do nice things. + let etype_padata_vec = pa_data + .etype_info2 + .iter() + .map(|einfo| { + let salt = einfo + .salt + .as_ref() + .map(Ia5String::new) + .transpose() + .map_err(|_| KrbError::DerEncodeKerberosString)? + .map(KerberosString); + + let s2kparams = einfo + .s2kparams + .as_ref() + .map(|data| OctetString::new(data.to_owned())) + .transpose() + .map_err(|_| KrbError::DerEncodeOctetString)?; + Ok(KdcETypeInfo2Entry { + etype: einfo.etype as i32, + salt, + s2kparams, + }) + }) + .collect::, KrbError>>()?; + + let etype_padata_value = etype_padata_vec + .to_der() + .and_then(OctetString::new) + .map_err(|_| KrbError::DerEncodeOctetString)?; + + let pavec = vec![ + PaData { + padata_type: PaDataType::PaEncTimestamp as u32, + padata_value: OctetString::new([]) + .map_err(|_| KrbError::DerEncodeOctetString)?, + }, + PaData { + padata_type: PaDataType::PaEtypeInfo2 as u32, + padata_value: etype_padata_value, + }, + ]; + + let error_data = pavec + .to_der() + .and_then(OctetString::new) + .map(Some) + .map_err(|_| KrbError::DerEncodeOctetString)?; + + let error_text = Ia5String::new("Preauthentication Required") + .map(KerberosString) + .ok(); + + let stime = stime + .duration_since(SystemTime::UNIX_EPOCH) + // We need to stip the fractional part. + .map(|t| Duration::from_secs(t.as_secs())) + .unwrap_or_default(); + + let stime = KerberosTime::from_unix_duration(stime) + .map_err(|_| KrbError::DerEncodeKerberosTime)?; + + let (service_name, service_realm) = (&service).try_into()?; + + let krb_error = KdcKrbError { + pvno: 5, + msg_type: KrbMessageType::KrbError as u8, + ctime: None, + cusec: None, + stime, + susec: 0, + error_code, + crealm: None, + cname: None, + service_realm, + service_name, + error_text, + error_data, + }; + + Ok(KrbKdcRep::ErrRep(krb_error)) + } + KerberosReply::ERR(error) => { + let error_code = error.code().clone() as i32; + + let error_text = error + .text() + .as_ref() + .and_then(|et| Ia5String::new(&et).map(KerberosString).ok()); + + let stime = error + .server_time() + .duration_since(SystemTime::UNIX_EPOCH) + // We need to stip the fractional part. + .map(|t| Duration::from_secs(t.as_secs())) + .unwrap_or_default(); + + let stime = KerberosTime::from_unix_duration(stime) + .map_err(|_| KrbError::DerEncodeKerberosTime)?; + + let (service_name, service_realm) = error.service().try_into()?; + + let krb_error = KdcKrbError { + pvno: 5, + msg_type: KrbMessageType::KrbError as u8, + ctime: None, + cusec: None, + stime, + susec: 0, + error_code, + crealm: None, + cname: None, + service_realm, + service_name, + error_text, + error_data: None, + }; + + Ok(KrbKdcRep::ErrRep(krb_error)) + } + } + } +} + +impl TryFrom for KerberosReply { + type Error = KrbError; + + fn try_from(rep: KdcRep) -> Result { + // assert the pvno and msg_type + if rep.pvno != 5 { + return Err(KrbError::InvalidPvno); + } + + let msg_type = + KrbMessageType::try_from(rep.msg_type).map_err(|_| KrbError::InvalidMessageType)?; + + match msg_type { + KrbMessageType::KrbAsRep => { + let enc_part = EncryptedData::try_from(rep.enc_part)?; + trace!(?enc_part); + + let pa_data = rep.padata.map(PreauthData::try_from).transpose()?; + trace!(?pa_data); + + let name = (rep.cname, rep.crealm).try_into()?; + let ticket = EncTicket::try_from(rep.ticket)?; + + Ok(KerberosReply::AS(AuthenticationReply { + name, + pa_data, + enc_part, + ticket, + })) + } + KrbMessageType::KrbTgsRep => { + let enc_part = EncryptedData::try_from(rep.enc_part)?; + trace!(?enc_part); + + let client_name = (rep.cname, rep.crealm).try_into()?; + + let ticket = EncTicket::try_from(rep.ticket)?; + + Ok(KerberosReply::TGS(TicketGrantReply { + client_name, + enc_part, + ticket, + })) + } + _ => Err(KrbError::InvalidMessageDirection), + } + } +} diff --git a/libkrimes/src/proto/reply/tgs_rep.rs b/libkrimes/src/proto/reply/tgs_rep.rs new file mode 100644 index 0000000..c01b5fa --- /dev/null +++ b/libkrimes/src/proto/reply/tgs_rep.rs @@ -0,0 +1,322 @@ +use crate::asn1::{ + authorization_data::AuthorizationData, + constants::authorization_data_types::AuthorizationDataType, enc_ticket_part::EncTicketPart, + encryption_key::EncryptionKey as KdcEncryptionKey, +}; +use crate::proto::ms_pac::AdWin2kPac; +use crate::proto::reply::{EncKdcRepPart, KerberosReply, TransitedEncoding}; +use crate::proto::request::TicketGrantRequest; +use crate::proto::time::{TicketGrantTimeBound, TicketRenewTimeBound}; +use crate::proto::{ + DerivedKey, EncTicket, EncryptedData, KdcPrimaryKey, KerberosTime, KrbError, Name, SessionKey, + Ticket, TicketFlags, +}; +use der::asn1::OctetString; +use der::Encode; + +#[derive(Debug)] +pub struct TicketGrantReply { + pub client_name: Name, + pub enc_part: EncryptedData, + pub ticket: EncTicket, +} + +pub struct KerberosReplyTicketGrantBuilder { + nonce: i32, + service_name: Name, + sub_session_key: Option, + pac: Option, + time_bounds: TicketGrantTimeBound, + ticket: Ticket, + flags: TicketFlags, +} + +impl KerberosReplyTicketGrantBuilder { + pub fn new( + ticket_grant_request: TicketGrantRequest, + time_bounds: TicketGrantTimeBound, + ) -> Self { + let TicketGrantRequest { + nonce, + service_name, + from: _, + until: _, + renew: _, + etypes: _, + sub_session_key, + client_time: _, + ticket, + } = ticket_grant_request; + + let mut flags = TicketFlags::none(); + if time_bounds.renew_until().is_some() { + flags |= TicketFlags::Renewable; + } + // From is what the client requested. + // Now is the kdc time. + // ticket.start_time is when the ticket began. + Self { + nonce, + service_name, + + sub_session_key, + + pac: None, + + time_bounds, + ticket, + + flags, + } + } + pub fn build(mut self, service_key: &DerivedKey) -> Result { + let service_session_key = SessionKey::new(); + let service_session_key: KdcEncryptionKey = service_session_key.try_into()?; + + let (cname, crealm) = (&self.ticket.client_name).try_into()?; + let (server_name, server_realm) = (&self.service_name).try_into()?; + + let auth_time = KerberosTime::from_system_time(self.ticket.auth_time) + .map_err(|_| KrbError::DerEncodeKerberosTime)?; + let start_time = Some( + KerberosTime::from_system_time(self.time_bounds.start_time()) + .map_err(|_| KrbError::DerEncodeKerberosTime)?, + ); + let end_time = KerberosTime::from_system_time(self.time_bounds.end_time()) + .map_err(|_| KrbError::DerEncodeKerberosTime)?; + + let renew_till = if let Some(renew_until) = self.time_bounds.renew_until() { + self.flags |= TicketFlags::Renewable; + Some( + KerberosTime::from_system_time(renew_until) + .map_err(|_| KrbError::DerEncodeKerberosTime)?, + ) + } else { + None + }; + + // TGS_REP The ciphertext is encrypted with the sub-session key + // from the authenticator. + // If absent, the session key is used (with no kvno). + // 5.4.2 reads as though the the clients version number is used here for kvno? + + // KeyUsage == 8 for session key, or == 9 for subkey. + // let enc_part = EncTGSRepPart == EncKDCRepPart; + + let enc_kdc_rep_part = EncKdcRepPart { + key: service_session_key.clone(), + // Not 100% clear on this field. + last_req: Vec::with_capacity(0), + nonce: self.nonce, + key_expiration: None, + flags: self.flags, + auth_time, + start_time, + end_time, + renew_till, + server_realm, + server_name, + client_addresses: None, + }; + + // Encrypt this with the original tickets sub_session_key so that they + // can decrypt this and get the service_session_key out. + let enc_part = if let Some(sub_session_key) = self.sub_session_key { + sub_session_key.encrypt_tgs_rep_part(enc_kdc_rep_part, true)? + } else { + self.ticket + .session_key + .encrypt_tgs_rep_part(enc_kdc_rep_part, false)? + }; + + // An MS-PAC is required for Samba to work. + let authorization_data = if let Some(pac) = self.pac { + // Need to work out the signatures here. + + let pac_data_inner = + OctetString::new(pac.to_bytes()).map_err(|_| KrbError::DerEncodeOctetString)?; + + let pac_data = AuthorizationData { + ad_type: AuthorizationDataType::AdWin2kPac.into(), + ad_data: pac_data_inner, + } + .to_der() + .and_then(OctetString::new) + .map_err(|_| KrbError::DerEncodeOctetString)?; + + Some(vec![AuthorizationData { + ad_type: AuthorizationDataType::AdIfRelevant.into(), + ad_data: pac_data, + }]) + } else { + None + }; + + let transited = TransitedEncoding { + tr_type: 1, + // Since no transit has occured, we record an empty str. + contents: OctetString::new(b"").map_err(|_| KrbError::DerEncodeOctetString)?, + }; + + // EncTicketPart + // Encrypted to the key of the service - this is what the ticket holder + // forwards to the service to that it is aware of it's service session key. + let ticket_inner = EncTicketPart { + flags: self.flags, + key: service_session_key, + crealm, + cname, + transited, + auth_time, + start_time, + end_time, + renew_till, + client_addresses: None, + authorization_data, + }; + + let ticket_enc_part = service_key.encrypt_tgs(ticket_inner)?; + + let ticket = EncTicket { + tkt_vno: 5, + service: self.service_name, + enc_part: ticket_enc_part, + }; + + let client_name = self.ticket.client_name; + + Ok(KerberosReply::TGS(TicketGrantReply { + client_name, + enc_part, + ticket, + })) + } +} + +pub struct KerberosReplyTicketRenewBuilder { + nonce: i32, + service_name: Name, + sub_session_key: Option, + time_bounds: TicketRenewTimeBound, + ticket: Ticket, +} + +impl KerberosReplyTicketRenewBuilder { + pub fn new( + ticket_grant_request: TicketGrantRequest, + time_bounds: TicketRenewTimeBound, + ) -> Self { + let TicketGrantRequest { + nonce, + service_name, + from: _, + until: _, + renew: _, + etypes: _, + sub_session_key, + client_time: _, + ticket, + } = ticket_grant_request; + Self { + nonce, + service_name, + + sub_session_key, + + time_bounds, + + ticket, + } + } + pub fn build(self, primary_key: &KdcPrimaryKey) -> Result { + let (cname, crealm) = (&self.ticket.client_name).try_into()?; + let (server_name, server_realm) = (&self.service_name).try_into()?; + + let auth_time = KerberosTime::from_system_time(self.ticket.auth_time) + .map_err(|_| KrbError::DerEncodeKerberosTime)?; + let start_time = Some( + KerberosTime::from_system_time(self.time_bounds.start_time()) + .map_err(|_| KrbError::DerEncodeKerberosTime)?, + ); + let end_time = KerberosTime::from_system_time(self.time_bounds.end_time()) + .map_err(|_| KrbError::DerEncodeKerberosTime)?; + let renew_till = Some( + KerberosTime::from_system_time(self.time_bounds.renew_until()) + .map_err(|_| KrbError::DerEncodeKerberosTime)?, + ); + + let session_key: KdcEncryptionKey = self.ticket.session_key.clone().try_into()?; + + // TGS_REP The ciphertext is encrypted with the sub-session key + // from the authenticator. + // If absent, the session key is used (with no kvno). + // 5.4.2 reads as though the the clients version number is used here for kvno? + + // KeyUsage == 8 for session key, or == 9 for subkey. + // let enc_part = EncTGSRepPart == EncKDCRepPart; + + let enc_kdc_rep_part = EncKdcRepPart { + key: session_key.clone(), + // Not 100% clear on this field. + last_req: Vec::with_capacity(0), + nonce: self.nonce, + key_expiration: None, + flags: self.ticket.flags, + auth_time, + start_time, + end_time, + renew_till, + server_realm, + server_name, + client_addresses: None, + }; + + let enc_part = if let Some(sub_session_key) = self.sub_session_key { + sub_session_key.encrypt_tgs_rep_part(enc_kdc_rep_part, true)? + } else { + self.ticket + .session_key + .encrypt_tgs_rep_part(enc_kdc_rep_part, false)? + }; + + let authorization_data = None; + + let transited = TransitedEncoding { + tr_type: 1, + // Since no transit has occured, we record an empty str. + contents: OctetString::new(b"").map_err(|_| KrbError::DerEncodeOctetString)?, + }; + + // EncTicketPart + // Encrypted to the key of the service + let ticket_inner = EncTicketPart { + flags: self.ticket.flags, + key: session_key, + crealm, + cname, + transited, + auth_time, + start_time, + end_time, + renew_till, + client_addresses: None, + authorization_data, + }; + + let ticket_enc_part = primary_key.encrypt_tgs(ticket_inner)?; + + let ticket = EncTicket { + tkt_vno: 5, + service: self.service_name, + enc_part: ticket_enc_part, + }; + + let client_name = self.ticket.client_name; + + Ok(KerberosReply::TGS(TicketGrantReply { + client_name, + enc_part, + ticket, + })) + } +} diff --git a/libkrimes/src/proto/request.rs b/libkrimes/src/proto/request.rs deleted file mode 100644 index 87cb20d..0000000 --- a/libkrimes/src/proto/request.rs +++ /dev/null @@ -1,852 +0,0 @@ -use crate::error::KrbError; -use crate::{ - asn1::{ - ap_options::{ApFlags, ApOptions}, - ap_req::{ApReq, ApReqInner}, - authenticator::{Authenticator, AuthenticatorInner}, - authorization_data::AuthorizationData, - constants::{ - encryption_types::EncryptionType, message_types::KrbMessageType, - pa_data_types::PaDataType, - }, - encrypted_data::EncryptedData as KdcEncryptedData, - encryption_key::EncryptionKey, - kdc_req::KdcReq, - kdc_req_body::KdcReqBody, - kerberos_flags::KerberosFlags, - kerberos_time::KerberosTime, - krb_kdc_req::KrbKdcReq, - pa_data::PaData, - pa_enc_ts_enc::PaEncTsEnc, - tagged_ticket::TaggedTicket, - ticket_flags::TicketFlags, - OctetString, - }, - cksum::ChecksumBuilder, -}; -use der::{asn1::Any, Encode}; -use rand::{rng, Rng}; - -use super::{ - DerivedKey, EncTicket, EncryptedData, KdcPrimaryKey, Name, Preauth, PreauthData, SessionKey, - Ticket, -}; -use std::time::{Duration, SystemTime}; -use tracing::trace; - -#[derive(Debug)] -pub enum KerberosRequest { - AS(Box), - TGS(Box), -} - -#[derive(Debug)] -pub struct AuthenticationRequest { - pub nonce: i32, - pub client_name: Name, - pub service_name: Name, - pub from: Option, - pub until: SystemTime, - pub renew: Option, - pub preauth: Preauth, - pub etypes: Vec, - pub kdc_options: KerberosFlags, -} - -#[derive(Debug)] -pub struct TicketGrantRequestUnverified { - pub preauth: Preauth, - // pub(crate) req_body: Vec, - pub(crate) req_body: Any, -} - -#[derive(Debug)] -pub struct TicketGrantRequest { - pub(crate) nonce: i32, - pub(crate) service_name: Name, - pub(crate) etypes: Vec, - pub(crate) sub_session_key: Option, - pub(crate) client_time: SystemTime, - pub(crate) from: Option, - pub(crate) until: SystemTime, - pub(crate) renew: Option, - - pub(crate) ticket: Ticket, -} - -#[derive(Debug)] -pub struct KerberosAuthenticationBuilder { - client_name: Name, - service_name: Name, - from: Option, - until: SystemTime, - renew: Option, - preauth: Option, - etypes: Vec, -} - -#[derive(Debug)] -pub struct ApReqBuilder { - client_name: Name, - ticket: EncTicket, - session_key: SessionKey, -} - -#[derive(Debug)] -pub struct TicketGrantRequestBuilder { - service_name: Name, - client_time: SystemTime, - from: Option, - until: SystemTime, - renew: Option, - // preauth: Option, - etypes: Vec, - ap_req_builder: Option, -} - -impl KerberosRequest { - pub fn build_as( - client_name: &Name, - service_name: Name, - until: SystemTime, - ) -> KerberosAuthenticationBuilder { - let etypes = vec![EncryptionType::AES256_CTS_HMAC_SHA1_96]; - - KerberosAuthenticationBuilder { - client_name: client_name.clone(), - service_name, - from: None, - until, - renew: None, - preauth: None, - etypes, - } - } - - pub fn build_tgs( - service_name: Name, - now: SystemTime, - until: SystemTime, - ) -> TicketGrantRequestBuilder { - let etypes = vec![EncryptionType::AES256_CTS_HMAC_SHA1_96]; - - TicketGrantRequestBuilder { - service_name, - from: None, - until, - renew: None, - // preauth: None, - etypes, - ap_req_builder: None, - client_time: now, - } - } -} - -impl KerberosAuthenticationBuilder { - pub fn from(mut self, from: Option) -> Self { - self.from = from; - self - } - - pub fn renew_until(mut self, renew: Option) -> Self { - self.renew = renew; - self - } - - pub fn preauth_enc_ts( - mut self, - pa_data: &PreauthData, - epoch_seconds: Duration, - user_key: &DerivedKey, - ) -> Result { - // Major TODO: Can we actually use a reasonable amount of iterations? - if !pa_data.enc_timestamp { - return Err(KrbError::PreauthUnsupported); - } - - // Strip any excess time. - let usecs = epoch_seconds.subsec_micros(); - let epoch_seconds = Duration::from_secs(epoch_seconds.as_secs()); - - let patimestamp = KerberosTime::from_unix_duration(epoch_seconds) - .map_err(|_| KrbError::PreauthInvalidUnixTs)?; - - let paenctsenc = PaEncTsEnc { - patimestamp, - pausec: Some(usecs), - }; - - trace!(?paenctsenc); - - let enc_timestamp = user_key.encrypt_pa_enc_timestamp(&paenctsenc)?; - - // fx cookie always has to be sent. - let pa_fx_cookie = pa_data.pa_fx_cookie.clone(); - - self.preauth = Some(Preauth { - enc_timestamp: Some(enc_timestamp), - pa_fx_cookie, - ..Default::default() - }); - - Ok(self) - } - - pub fn build(self) -> KerberosRequest { - let KerberosAuthenticationBuilder { - client_name, - service_name, - from, - until, - renew, - preauth, - etypes, - } = self; - - // BUG IN MIT KRB5 - If the value is greater than i32 max you get: - // Jun 28 03:47:41 3e79497ab6b5 krb5kdc[1](Error): ASN.1 value too large - while dispatching (tcp) - // Heimdal for whatever reason will happily send negative values, so no idea - // how they get away with it when we don't .... - let nonce: i32 = rng().random(); - let nonce = nonce.abs(); - - let preauth = preauth.unwrap_or_default(); - - let mut kdc_options = KerberosFlags::none(); - kdc_options |= KerberosFlags::Renewable; - - KerberosRequest::AS(Box::new(AuthenticationRequest { - nonce, - client_name, - service_name, - from, - until, - renew, - preauth, - etypes, - kdc_options, - })) - } -} - -impl TicketGrantRequestBuilder { - pub fn from(mut self, from: Option) -> Self { - self.from = from; - self - } - - pub fn renew_until(mut self, renew: Option) -> Self { - self.renew = renew; - self - } - - pub fn preauth_ap_req( - mut self, - client: &Name, - ticket: &EncTicket, - session_key: &SessionKey, - ) -> Result { - let ap_req_builder: ApReqBuilder = ApReqBuilder { - client_name: client.clone(), - ticket: ticket.clone(), - session_key: session_key.clone(), - }; - self.ap_req_builder = Some(ap_req_builder); - Ok(self) - } - - pub fn build(self) -> Result { - let TicketGrantRequestBuilder { - service_name, - from, - until, - renew, - // preauth: _, - etypes, - ap_req_builder, - client_time, - } = self; - - let ap_req_builder = ap_req_builder.ok_or( - // This will be removed soon - KrbError::MissingPaData, - )?; - - // BUG IN MIT KRB5 - If the value is greater than i32 max you get: - // Jun 28 03:47:41 3e79497ab6b5 krb5kdc[1](Error): ASN.1 value too large - while dispatching (tcp) - // Heimdal for whatever reason will happily send negative values, so no idea - // how they get away with it when we don't .... - let nonce: i32 = rng().random(); - let nonce = nonce.abs(); - - // So far we don't use preauth-here - // let preauth = preauth.unwrap_or_default(); - - let mut kdc_options = KerberosFlags::none(); - kdc_options |= KerberosFlags::Renewable; - kdc_options |= KerberosFlags::Canonicalize; - - let (_, realm) = (&service_name).try_into()?; - let sname = (&service_name).try_into()?; - - let req_body = KdcReqBody { - kdc_options, - cname: None, - realm, - sname: Some(sname), - from: from - .map(|t| { - KerberosTime::from_system_time(t).map_err(|_| KrbError::DerEncodeKerberosTime) - }) - .transpose()?, - till: KerberosTime::from_system_time(until) - .map_err(|_| KrbError::DerEncodeKerberosTime)?, - rtime: renew - .map(|t| { - KerberosTime::from_system_time(t).map_err(|_| KrbError::DerEncodeKerberosTime) - }) - .transpose()?, - nonce, - etype: etypes.iter().map(|e| *e as i32).collect(), - addresses: None, - enc_authorization_data: None, - additional_tickets: None, - }; - - let req_body = Any::encode_from(&req_body).map_err(|_| KrbError::DerEncodeAny)?; - - // The checksum in the authenticator is to be computed over the KDC-REQ-BODY encoding. - let checksum_builder = - ChecksumBuilder::HmacSha196Aes256(ap_req_builder.session_key.clone()); - let checksum = checksum_builder.compute_kdc_req_body(&req_body)?; - - // The encrypted authenticator is included in the AP-REQ; it certifies - // to a server that the sender has recent knowledge of the encryption - // key in the accompanying ticket, to help the server detect replays. - // It also assists in the selection of a "true session key" to use with - // the particular session. It is encrypted in the ticket's session key, - // with a key usage value of 11 in normal application exchanges, or 7 - // when used as the PA-TGS-REQ PA-DATA field of a TGS-REQ exchange (see - // Section 5.4.1) - let (client_name, client_realm) = (&ap_req_builder.client_name).try_into()?; - let subkey: Option = None; - let sequence_number: Option = None; - let authorization_data: Option = None; - let authenticator: Authenticator = Authenticator::new( - client_name, - client_realm, - client_time, - Some(checksum), - subkey, - sequence_number, - authorization_data, - )?; - let authenticator: EncryptedData = ap_req_builder - .session_key - .encrypt_ap_req_authenticator(&authenticator)?; - let authenticator: KdcEncryptedData = match authenticator { - EncryptedData::Aes256CtsHmacSha196 { kvno, data } => { - let cipher = - OctetString::new(data.clone()).map_err(|_| KrbError::DerEncodeOctetString)?; - KdcEncryptedData { - etype: EncryptionType::AES256_CTS_HMAC_SHA1_96 as i32, - kvno, - cipher, - } - } - }; - - let ap_options: ApOptions = ApFlags::none(); - - let ticket: TaggedTicket = ap_req_builder.ticket.try_into()?; - let ap_req: ApReq = ApReq::new(ap_options, ticket, authenticator); - - let preauth = Preauth { - tgs_req: Some(ap_req), - ..Default::default() - }; - - Ok(KerberosRequest::TGS(Box::new( - TicketGrantRequestUnverified { preauth, req_body }, - ))) - } -} - -impl TicketGrantRequestUnverified { - pub fn validate( - &self, - primary_key: &KdcPrimaryKey, - realm: &str, - ) -> Result { - trace!(?self, "Validating ap-req"); - - // Destructure the ticket grant to make it easier to handle. - let TicketGrantRequestUnverified { - preauth: - Preauth { - tgs_req, - // pa_fx_fast: _, - enc_timestamp: _, - pa_fx_cookie: _, - }, - req_body, - } = self; - - let Some(ap_req) = tgs_req else { - return Err(KrbError::TgsMissingPaApReq); - }; - - let ap_req: &ApReqInner = ap_req.as_ref(); - - if ap_req.pvno != 5 || ap_req.msg_type != 14 { - return Err(KrbError::TgsInvalidPaApReq); - }; - - let ap_req_ticket = &ap_req.ticket.0; - let ap_req_authenticator = EncryptedData::try_from(ap_req.authenticator.clone())?; - // let ap_req_options = ap_req.ap_options; - - trace!(?ap_req_ticket, "ticket"); - trace!(?ap_req_authenticator, "authenticator"); - - // Decrypt the ticket. This should be the TGT and contains the session - // key used for encryption of the authenticator. - if ap_req_ticket.realm.as_str() != realm { - return Err(KrbError::TgsNotForRealm); - } - - let ap_req_ticket_service_name = - Name::try_from((&ap_req_ticket.sname, &ap_req_ticket.realm))?; - - if !ap_req_ticket_service_name.is_service_krbtgt(realm) { - tracing::error!(?ap_req_ticket_service_name, "TgsTicketIsNotTgt"); - return Err(KrbError::TgsTicketIsNotTgt); - } - - let ap_req_ticket_enc = EncryptedData::try_from(ap_req_ticket.enc_part.clone())?; - - let enc_ticket_part = ap_req_ticket_enc.decrypt_enc_tgt(primary_key)?; - - trace!(?enc_ticket_part, "enc-ticket-part"); - - // Get the session Key. - - let session_key = SessionKey::try_from(enc_ticket_part.key)?; - - // Decrypt the authenticator - let authenticator = session_key.decrypt_ap_req_authenticator(ap_req_authenticator)?; - - let authenticator: AuthenticatorInner = authenticator.into(); - - trace!(?authenticator, "authenticator"); - - let Some(his_checksum) = authenticator.cksum else { - return Err(KrbError::TgsAuthMissingChecksum); - }; - - let checksum_builder: ChecksumBuilder = - (his_checksum.checksum_type, Some(session_key.clone())).try_into()?; - let checksum = checksum_builder.compute_kdc_req_body(req_body)?; - - // Validate that the checksum matches what our authenticator contains. - if checksum != his_checksum { - tracing::debug!(?checksum, ?his_checksum); - return Err(KrbError::TgsAuthChecksumFailure); - } - - let req_body = req_body - .decode_as::() - .map_err(|_| KrbError::DerDecodeKdcReqBody)?; - - trace!(?req_body); - - let Some(service_princ) = req_body.sname else { - return Err(KrbError::TgsKdcReqMissingServiceName); - }; - - // ================================================================== - // - // WARNING WARNING WARNING WARNING - // - // Below this line is where we now trust that the inputs are valid and checksummed - // so that we can proceed with the release of the TGS to the KDC. - - let sub_session_key = authenticator - .subkey - .map(SessionKey::try_from) - // Invert the Option to Result