1use std::env;
2use std::path::Path;
3use std::str::FromStr;
4
5use bitflags;
6use md5::{Digest, Md5};
7use picky_asn1::bit_string::BitString;
8use picky_asn1::date::GeneralizedTime;
9use picky_asn1::restricted_string::IA5String;
10use picky_asn1::wrapper::{
11 Asn1SequenceOf, ExplicitContextTag0, ExplicitContextTag1, ExplicitContextTag11, ExplicitContextTag2,
12 ExplicitContextTag3, ExplicitContextTag4, ExplicitContextTag5, ExplicitContextTag6, ExplicitContextTag7,
13 ExplicitContextTag8, ExplicitContextTag9, GeneralizedTimeAsn1, IntegerAsn1, ObjectIdentifierAsn1, OctetStringAsn1,
14 Optional,
15};
16use picky_asn1_der::Asn1RawDer;
17use picky_asn1_x509::oids;
18use picky_krb::constants::gss_api::{
19 ACCEPT_COMPLETE, ACCEPT_INCOMPLETE, AP_REQ_TOKEN_ID, AUTHENTICATOR_CHECKSUM_TYPE, TGT_REQ_TOKEN_ID,
20};
21use picky_krb::constants::key_usages::{
22 AP_REP_ENC, AP_REQ_AUTHENTICATOR, KRB_PRIV_ENC_PART, TGS_REQ_PA_DATA_AP_REQ_AUTHENTICATOR,
23};
24use picky_krb::constants::types::{
25 AD_AUTH_DATA_AP_OPTION_TYPE, AP_REP_MSG_TYPE, AP_REQ_MSG_TYPE, AS_REQ_MSG_TYPE, KERB_AP_OPTIONS_CBT, KRB_PRIV,
26 NET_BIOS_ADDR_TYPE, NT_ENTERPRISE, NT_PRINCIPAL, NT_SRV_INST, PA_ENC_TIMESTAMP, PA_ENC_TIMESTAMP_KEY_USAGE,
27 PA_PAC_OPTIONS_TYPE, PA_PAC_REQUEST_TYPE, PA_TGS_REQ_TYPE, TGS_REQ_MSG_TYPE, TGT_REQ_MSG_TYPE,
28};
29use picky_krb::crypto::CipherSuite;
30use picky_krb::data_types::{
31 ApOptions, Authenticator, AuthenticatorInner, AuthorizationData, AuthorizationDataInner, Checksum, EncApRepPart,
32 EncApRepPartInner, EncKrbPrivPart, EncKrbPrivPartInner, EncryptedData, EncryptionKey, HostAddress,
33 KerbPaPacRequest, KerberosFlags, KerberosStringAsn1, KerberosTime, PaData, PaEncTsEnc, PaPacOptions, PrincipalName,
34 Realm, Ticket,
35};
36use picky_krb::gss_api::{
37 ApplicationTag0, GssApiNegInit, KrbMessage, MechType, MechTypeList, NegTokenInit, NegTokenTarg, NegTokenTarg1,
38};
39use picky_krb::messages::{
40 ApMessage, ApRep, ApRepInner, ApReq, ApReqInner, AsReq, KdcRep, KdcReq, KdcReqBody, KrbPriv, KrbPrivInner,
41 KrbPrivMessage, TgsReq, TgtReq,
42};
43use rand::prelude::StdRng;
44use rand::{RngCore, SeedableRng};
45use time::{Duration, OffsetDateTime};
46
47use crate::channel_bindings::ChannelBindings;
48use crate::crypto::compute_md5_channel_bindings_hash;
49use crate::kerberos::flags::{ApOptions as ApOptionsFlags, KdcOptions};
50use crate::kerberos::{EncryptionParams, DEFAULT_ENCRYPTION_TYPE, KERBEROS_VERSION};
51use crate::krb::Krb5Conf;
52use crate::utils::parse_target_name;
53use crate::{ClientRequestFlags, Error, ErrorKind, Result};
54
55const TGT_TICKET_LIFETIME_DAYS: i64 = 3;
56const NONCE_LEN: usize = 4;
57pub const MAX_MICROSECONDS: u32 = 999_999;
64const MD5_CHECKSUM_TYPE: [u8; 1] = [0x07];
65
66pub const DEFAULT_AS_REQ_OPTIONS: [u8; 4] = [0x00, 0x81, 0x00, 0x10];
69
70const DEFAULT_TGS_REQ_OPTIONS: [u8; 4] = [0x00, 0x81, 0x00, 0x00];
73
74const DEFAULT_PA_PAC_OPTIONS: [u8; 4] = [0x40, 0x00, 0x00, 0x00];
75
76pub const AUTHENTICATOR_DEFAULT_CHECKSUM: [u8; 24] = [
81 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
82 0x00, 0x00, 0x00, 0x00, 0x00,
83];
84
85pub fn get_client_principal_name_type(username: &str, _domain: &str) -> u8 {
88 if username.contains('@') {
89 NT_ENTERPRISE
90 } else {
91 NT_PRINCIPAL
92 }
93}
94
95pub fn get_client_principal_realm(username: &str, domain: &str) -> String {
96 let krb5_config = env::var("KRB5_CONFIG").unwrap_or_else(|_| "/etc/krb5.conf:/usr/local/etc/krb5.conf".to_string());
99 let krb5_conf_paths = krb5_config.split(':').map(Path::new).collect::<Vec<&Path>>();
100
101 get_client_principal_realm_impl(&krb5_conf_paths, username, domain)
102}
103
104fn get_client_principal_realm_impl(krb5_conf_paths: &[&Path], username: &str, domain: &str) -> String {
105 let domain = if domain.is_empty() {
106 if let Some((_left, right)) = username.split_once('@') {
107 right.to_string()
108 } else {
109 String::new()
110 }
111 } else {
112 domain.to_string()
113 };
114
115 for krb5_conf_path in krb5_conf_paths {
116 if !krb5_conf_path.exists() {
117 continue;
118 }
119
120 if let Some(krb5_conf) = Krb5Conf::new_from_file(krb5_conf_path) {
121 if let Some(mappings) = krb5_conf.get_values_in_section(&["domain_realm"]) {
122 for (mapping_domain, realm) in mappings {
123 if matches_domain(&domain, mapping_domain) {
124 return realm.to_owned();
125 }
126 }
127 }
128 }
129 }
130
131 domain.to_uppercase()
132}
133
134fn matches_domain(domain: &str, mapping_domain: &str) -> bool {
135 if mapping_domain.starts_with('.') {
136 domain
137 .split_once('.')
138 .map(|(_, remaining)| remaining.eq_ignore_ascii_case(&mapping_domain[1..]))
139 .unwrap_or(false)
140 } else {
141 domain.eq_ignore_ascii_case(mapping_domain)
142 }
143}
144
145#[derive(Debug)]
147pub struct GenerateAsPaDataOptions<'a> {
148 pub password: &'a str,
149 pub salt: Vec<u8>,
153 pub enc_params: EncryptionParams,
154 pub with_pre_auth: bool,
156}
157
158#[instrument(level = "trace", ret, skip_all, fields(options.salt, options.enc_params, options.with_pre_auth))]
159pub fn generate_pa_datas_for_as_req(options: &GenerateAsPaDataOptions<'_>) -> Result<Vec<PaData>> {
160 let GenerateAsPaDataOptions {
161 password,
162 salt,
163 enc_params,
164 with_pre_auth,
165 } = options;
166
167 let mut pa_datas = if *with_pre_auth {
168 let current_date = OffsetDateTime::now_utc();
169 let microseconds = current_date.microsecond().min(MAX_MICROSECONDS);
170
171 let timestamp = PaEncTsEnc {
172 patimestamp: ExplicitContextTag0::from(KerberosTime::from(GeneralizedTime::from(current_date))),
173 pausec: Optional::from(Some(ExplicitContextTag1::from(IntegerAsn1::from(
174 microseconds.to_be_bytes().to_vec(),
175 )))),
176 };
177 let timestamp_bytes = picky_asn1_der::to_vec(×tamp)?;
178
179 let encryption_type = enc_params.encryption_type.as_ref().unwrap_or(&DEFAULT_ENCRYPTION_TYPE);
180 let cipher = encryption_type.cipher();
181
182 let key = cipher.generate_key_from_password(password.as_bytes(), salt)?;
183 trace!(?key, ?encryption_type, "AS timestamp encryption params",);
184
185 let encrypted_timestamp = cipher.encrypt(&key, PA_ENC_TIMESTAMP_KEY_USAGE, ×tamp_bytes)?;
186
187 trace!(
188 ?current_date,
189 ?microseconds,
190 ?timestamp_bytes,
191 ?encrypted_timestamp,
192 "Encrypted timestamp params",
193 );
194
195 vec![PaData {
196 padata_type: ExplicitContextTag1::from(IntegerAsn1::from(PA_ENC_TIMESTAMP.to_vec())),
197 padata_data: ExplicitContextTag2::from(OctetStringAsn1::from(picky_asn1_der::to_vec(&EncryptedData {
198 etype: ExplicitContextTag0::from(IntegerAsn1::from(vec![encryption_type.into()])),
199 kvno: Optional::from(None),
200 cipher: ExplicitContextTag2::from(OctetStringAsn1::from(encrypted_timestamp)),
201 })?)),
202 }]
203 } else {
204 Vec::new()
205 };
206
207 pa_datas.push(PaData {
208 padata_type: ExplicitContextTag1::from(IntegerAsn1::from(PA_PAC_REQUEST_TYPE.to_vec())),
209 padata_data: ExplicitContextTag2::from(OctetStringAsn1::from(picky_asn1_der::to_vec(&KerbPaPacRequest {
210 include_pac: ExplicitContextTag0::from(true),
211 })?)),
212 });
213
214 Ok(pa_datas)
215}
216
217#[derive(Debug)]
219pub struct GenerateAsReqOptions<'a> {
220 pub realm: &'a str,
221 pub username: &'a str,
222 pub cname_type: u8,
223 pub snames: &'a [&'a str],
224 pub nonce: &'a [u8],
225 pub hostname: &'a str,
226 pub context_requirements: ClientRequestFlags,
227}
228
229#[instrument(level = "trace", ret)]
230pub fn generate_as_req_kdc_body(options: &GenerateAsReqOptions<'_>) -> Result<KdcReqBody> {
231 let GenerateAsReqOptions {
232 realm,
233 username,
234 cname_type,
235 snames,
236 nonce,
237 hostname: address,
238 context_requirements,
239 } = options;
240
241 let expiration_date = OffsetDateTime::now_utc()
242 .checked_add(Duration::days(TGT_TICKET_LIFETIME_DAYS))
243 .unwrap();
244
245 let host_address = HostAddress {
246 addr_type: ExplicitContextTag0::from(IntegerAsn1::from(vec![NET_BIOS_ADDR_TYPE])),
247 address: ExplicitContextTag1::from(OctetStringAsn1::from(address.as_bytes().to_vec())),
248 };
249
250 let address = Some(ExplicitContextTag9::from(Asn1SequenceOf::from(vec![host_address])));
251
252 let mut service_names = Vec::with_capacity(snames.len());
253 for sname in *snames {
254 service_names.push(KerberosStringAsn1::from(IA5String::from_string((*sname).to_owned())?));
255 }
256
257 let mut as_req_options = KdcOptions::from_bits(u32::from_be_bytes(DEFAULT_AS_REQ_OPTIONS)).unwrap();
258 if context_requirements.contains(ClientRequestFlags::DELEGATE) {
259 as_req_options |= KdcOptions::FORWARDABLE;
260 }
261
262 Ok(KdcReqBody {
263 kdc_options: ExplicitContextTag0::from(KerberosFlags::from(BitString::with_bytes(
264 as_req_options.bits().to_be_bytes().to_vec(),
265 ))),
266 cname: Optional::from(Some(ExplicitContextTag1::from(PrincipalName {
267 name_type: ExplicitContextTag0::from(IntegerAsn1::from(vec![*cname_type])),
268 name_string: ExplicitContextTag1::from(Asn1SequenceOf::from(vec![KerberosStringAsn1::from(
269 IA5String::from_string((*username).into())?,
270 )])),
271 }))),
272 realm: ExplicitContextTag2::from(Realm::from(IA5String::from_string((*realm).into())?)),
273 sname: Optional::from(Some(ExplicitContextTag3::from(PrincipalName {
274 name_type: ExplicitContextTag0::from(IntegerAsn1::from(vec![NT_SRV_INST])),
275 name_string: ExplicitContextTag1::from(Asn1SequenceOf::from(service_names)),
276 }))),
277 from: Optional::from(None),
278 till: ExplicitContextTag5::from(GeneralizedTimeAsn1::from(GeneralizedTime::from(expiration_date))),
279 rtime: Optional::from(Some(ExplicitContextTag6::from(GeneralizedTimeAsn1::from(
280 GeneralizedTime::from(expiration_date),
281 )))),
282 nonce: ExplicitContextTag7::from(IntegerAsn1::from(nonce.to_vec())),
283 etype: ExplicitContextTag8::from(Asn1SequenceOf::from(vec![
284 IntegerAsn1::from(vec![CipherSuite::Aes256CtsHmacSha196.into()]),
285 IntegerAsn1::from(vec![CipherSuite::Aes128CtsHmacSha196.into()]),
286 ])),
287 addresses: Optional::from(address),
288 enc_authorization_data: Optional::from(None),
289 additional_tickets: Optional::from(None),
290 })
291}
292
293#[instrument(level = "debug", ret, skip_all)]
294pub fn generate_as_req(pa_datas: Vec<PaData>, kdc_req_body: KdcReqBody) -> AsReq {
295 AsReq::from(KdcReq {
296 pvno: ExplicitContextTag1::from(IntegerAsn1::from(vec![KERBEROS_VERSION])),
297 msg_type: ExplicitContextTag2::from(IntegerAsn1::from(vec![AS_REQ_MSG_TYPE])),
298 padata: Optional::from(Some(ExplicitContextTag3::from(Asn1SequenceOf::from(pa_datas)))),
299 req_body: ExplicitContextTag4::from(kdc_req_body),
300 })
301}
302
303#[derive(Debug)]
305pub struct GenerateTgsReqOptions<'a> {
306 pub realm: &'a str,
307 pub service_principal: &'a str,
308 pub session_key: &'a [u8],
309 pub ticket: Ticket,
311 pub authenticator: &'a mut Authenticator,
313 pub additional_tickets: Option<Vec<Ticket>>,
316 pub enc_params: &'a EncryptionParams,
317 pub context_requirements: ClientRequestFlags,
318}
319
320#[instrument(level = "debug", ret)]
321pub fn generate_tgs_req(options: GenerateTgsReqOptions<'_>) -> Result<TgsReq> {
322 let GenerateTgsReqOptions {
323 realm,
324 service_principal,
325 session_key,
326 ticket,
327 authenticator,
328 additional_tickets,
329 enc_params,
330 context_requirements,
331 } = options;
332
333 let (service_name, service_principal_name) = parse_target_name(service_principal)?;
334
335 let expiration_date = OffsetDateTime::now_utc()
336 .checked_add(Duration::days(TGT_TICKET_LIFETIME_DAYS))
337 .unwrap();
338
339 let mut tgs_req_options = KdcOptions::from_bits(u32::from_be_bytes(DEFAULT_TGS_REQ_OPTIONS)).unwrap();
340 if context_requirements.contains(ClientRequestFlags::DELEGATE) {
341 tgs_req_options |= KdcOptions::FORWARDABLE;
342 }
343 if context_requirements.contains(ClientRequestFlags::USE_SESSION_KEY) {
344 tgs_req_options |= KdcOptions::ENC_TKT_IN_SKEY;
345 }
346
347 let mut rng = StdRng::try_from_os_rng()?;
348 let mut nonce = [0; NONCE_LEN];
349 rng.fill_bytes(&mut nonce);
350
351 let req_body = KdcReqBody {
352 kdc_options: ExplicitContextTag0::from(KerberosFlags::from(BitString::with_bytes(
353 tgs_req_options.bits().to_be_bytes().to_vec(),
354 ))),
355 cname: Optional::from(None),
356 realm: ExplicitContextTag2::from(Realm::from(IA5String::from_str(realm)?)),
357 sname: Optional::from(Some(ExplicitContextTag3::from(PrincipalName {
358 name_type: ExplicitContextTag0::from(IntegerAsn1::from(vec![NT_SRV_INST])),
359 name_string: ExplicitContextTag1::from(Asn1SequenceOf::from(vec![
360 KerberosStringAsn1::from(IA5String::from_string(service_name.into())?),
361 KerberosStringAsn1::from(IA5String::from_string(service_principal_name.into())?),
362 ])),
363 }))),
364 from: Optional::from(None),
365 till: ExplicitContextTag5::from(GeneralizedTimeAsn1::from(GeneralizedTime::from(expiration_date))),
366 rtime: Optional::from(None),
367 nonce: ExplicitContextTag7::from(IntegerAsn1::from(nonce.to_vec())),
368 etype: ExplicitContextTag8::from(Asn1SequenceOf::from(vec![
369 IntegerAsn1::from(vec![CipherSuite::Aes256CtsHmacSha196.into()]),
370 IntegerAsn1::from(vec![CipherSuite::Aes128CtsHmacSha196.into()]),
371 ])),
372 addresses: Optional::from(None),
373 enc_authorization_data: Optional::from(None),
374 additional_tickets: Optional::from(
375 additional_tickets.map(|tickets| ExplicitContextTag11::from(Asn1SequenceOf::from(tickets))),
376 ),
377 };
378
379 let mut md5 = Md5::new();
380 md5.update(&picky_asn1_der::to_vec(&req_body)?);
381 let checksum = md5.finalize();
382
383 authenticator.0.cksum = Optional::from(Some(ExplicitContextTag3::from(Checksum {
384 cksumtype: ExplicitContextTag0::from(IntegerAsn1::from(MD5_CHECKSUM_TYPE.to_vec())),
385 checksum: ExplicitContextTag1::from(OctetStringAsn1::from(checksum.to_vec())),
386 })));
387
388 let pa_tgs_req =
389 PaData {
390 padata_type: ExplicitContextTag1::from(IntegerAsn1::from(PA_TGS_REQ_TYPE.to_vec())),
391 padata_data: ExplicitContextTag2::from(OctetStringAsn1::from(picky_asn1_der::to_vec(
392 &generate_tgs_ap_req(ticket, session_key, authenticator, enc_params)?,
393 )?)),
394 };
395
396 let pa_pac_options = PaData {
397 padata_type: ExplicitContextTag1::from(IntegerAsn1::from(PA_PAC_OPTIONS_TYPE.to_vec())),
398 padata_data: ExplicitContextTag2::from(OctetStringAsn1::from(picky_asn1_der::to_vec(&PaPacOptions {
399 flags: ExplicitContextTag0::from(KerberosFlags::from(BitString::with_bytes(
400 DEFAULT_PA_PAC_OPTIONS.to_vec(),
401 ))),
402 })?)),
403 };
404
405 Ok(TgsReq::from(KdcReq {
406 pvno: ExplicitContextTag1::from(IntegerAsn1::from(vec![KERBEROS_VERSION])),
407 msg_type: ExplicitContextTag2::from(IntegerAsn1::from(vec![TGS_REQ_MSG_TYPE])),
408 padata: Optional::from(Some(ExplicitContextTag3::from(Asn1SequenceOf::from(vec![
409 pa_tgs_req,
410 pa_pac_options,
411 ])))),
412 req_body: ExplicitContextTag4::from(req_body),
413 }))
414}
415
416#[derive(Debug)]
417pub struct ChecksumOptions {
418 pub checksum_type: Vec<u8>,
419 pub checksum_value: ChecksumValues,
420}
421
422#[derive(Debug)]
423pub struct ChecksumValues {
424 inner: Vec<u8>, }
426
427impl Default for ChecksumValues {
428 fn default() -> Self {
429 Self {
430 inner: AUTHENTICATOR_DEFAULT_CHECKSUM.to_vec(),
431 }
432 }
433}
434
435impl From<ChecksumValues> for Vec<u8> {
436 fn from(val: ChecksumValues) -> Self {
437 val.inner
438 }
439}
440
441impl From<[u8; 24]> for ChecksumValues {
442 fn from(bytes: [u8; 24]) -> Self {
443 ChecksumValues {
444 inner: Vec::from(bytes),
445 }
446 }
447}
448
449impl ChecksumValues {
450 pub(crate) fn set_flags(&mut self, flags: GssFlags) {
451 let flag_bits = flags.bits();
452 let flag_bytes = flag_bits.to_le_bytes();
453 self.inner[20..24].copy_from_slice(&flag_bytes);
454 }
455
456 pub(crate) fn into_inner(self) -> Vec<u8> {
457 self.inner
458 }
459}
460
461bitflags::bitflags! {
462 #[derive(Debug,Clone,Copy)]
466 pub(crate) struct GssFlags: u32 {
467 const GSS_C_DELEG_FLAG = 1;
469 const GSS_C_MUTUAL_FLAG = 2;
470 const GSS_C_REPLAY_FLAG = 4;
471 const GSS_C_SEQUENCE_FLAG = 8;
472 const GSS_C_CONF_FLAG = 16;
473 const GSS_C_INTEG_FLAG = 32;
474
475 const GSS_C_ANON_FLAG = 64;
476 const GSS_C_PROT_READY_FLAG = 128;
477 const GSS_C_TRANS_FLAG = 256;
478 const GSS_C_DELEG_POLICY_FLAG = 32768;
479
480 const GSS_C_EXTENDED_ERROR_FLAG = 0x4000;
488 const GSS_C_IDENTIFY_FLAG = 0x2000;
491 const GSS_C_DCE_STYLE = 0x1000;
494 }
495}
496
497impl From<ClientRequestFlags> for GssFlags {
498 fn from(value: ClientRequestFlags) -> Self {
502 let mut flags = GssFlags::empty();
503
504 if value.contains(ClientRequestFlags::DELEGATE) {
505 flags |= GssFlags::GSS_C_DELEG_FLAG;
506 }
507
508 if value.contains(ClientRequestFlags::MUTUAL_AUTH) {
509 flags |= GssFlags::GSS_C_MUTUAL_FLAG;
510 }
511
512 if value.contains(ClientRequestFlags::REPLAY_DETECT) {
513 flags |= GssFlags::GSS_C_REPLAY_FLAG;
514 }
515
516 if value.contains(ClientRequestFlags::SEQUENCE_DETECT) {
517 flags |= GssFlags::GSS_C_SEQUENCE_FLAG;
518 }
519
520 if value.contains(ClientRequestFlags::CONFIDENTIALITY) {
521 flags |= GssFlags::GSS_C_CONF_FLAG;
522 }
523
524 if value.contains(ClientRequestFlags::INTEGRITY) {
525 flags |= GssFlags::GSS_C_INTEG_FLAG;
526 }
527
528 if value.contains(ClientRequestFlags::NO_INTEGRITY) {
529 flags &= !GssFlags::GSS_C_INTEG_FLAG;
530 }
531
532 if value.contains(ClientRequestFlags::USE_DCE_STYLE) {
533 flags |= GssFlags::GSS_C_DCE_STYLE;
534 }
535
536 flags
537 }
538}
539
540#[derive(Debug)]
541pub struct AuthenticatorChecksumExtension {
542 pub extension_type: u32,
543 pub extension_value: Vec<u8>,
544}
545
546#[derive(Debug)]
548pub struct EncKey {
549 pub key_type: CipherSuite,
551 pub key_value: Vec<u8>,
553}
554
555#[derive(Debug)]
557pub struct GenerateAuthenticatorOptions<'a> {
558 pub kdc_rep: &'a KdcRep,
560 pub seq_num: Option<u32>,
562 pub sub_key: Option<EncKey>,
564 pub checksum: Option<ChecksumOptions>,
566 pub channel_bindings: Option<&'a ChannelBindings>,
568 pub extensions: Vec<AuthenticatorChecksumExtension>,
570}
571
572#[instrument(level = "trace", ret)]
574pub fn generate_authenticator(options: GenerateAuthenticatorOptions<'_>) -> Result<Authenticator> {
575 let GenerateAuthenticatorOptions {
576 kdc_rep,
577 seq_num,
578 sub_key,
579 checksum,
580 channel_bindings,
581 ..
582 } = options;
583
584 let current_date = OffsetDateTime::now_utc();
585 let mut microseconds = current_date.microsecond();
586 if microseconds > MAX_MICROSECONDS {
587 microseconds = MAX_MICROSECONDS;
588 }
589
590 let authorization_data = Optional::from(channel_bindings.as_ref().map(|_| {
591 ExplicitContextTag8::from(AuthorizationData::from(vec![AuthorizationDataInner {
592 ad_type: ExplicitContextTag0::from(IntegerAsn1::from(AD_AUTH_DATA_AP_OPTION_TYPE.to_vec())),
593 ad_data: ExplicitContextTag1::from(OctetStringAsn1::from(KERB_AP_OPTIONS_CBT.to_vec())),
594 }]))
595 }));
596
597 let cksum = if let Some(ChecksumOptions {
598 checksum_type,
599 checksum_value,
600 }) = checksum
601 {
602 let mut checksum_value = checksum_value.into_inner();
603 if checksum_type == AUTHENTICATOR_CHECKSUM_TYPE && channel_bindings.is_some() {
604 if checksum_value.len() < 20 {
605 return Err(Error::new(
606 ErrorKind::InvalidParameter,
607 format!(
608 "Invalid authenticator checksum length: expected >= 20 but got {}. ",
609 checksum_value.len()
610 ),
611 ));
612 }
613 checksum_value[4..20]
616 .copy_from_slice(&compute_md5_channel_bindings_hash(channel_bindings.as_ref().unwrap()));
617 }
618 Optional::from(Some(ExplicitContextTag3::from(Checksum {
619 cksumtype: ExplicitContextTag0::from(IntegerAsn1::from(checksum_type)),
620 checksum: ExplicitContextTag1::from(OctetStringAsn1::from(checksum_value)),
621 })))
622 } else {
623 Optional::from(None)
624 };
625
626 Ok(Authenticator::from(AuthenticatorInner {
627 authenticator_vno: ExplicitContextTag0::from(IntegerAsn1::from(vec![KERBEROS_VERSION])),
628 crealm: ExplicitContextTag1::from(kdc_rep.crealm.0.clone()),
629 cname: ExplicitContextTag2::from(kdc_rep.cname.0.clone()),
630 cksum,
631 cusec: ExplicitContextTag4::from(IntegerAsn1::from(microseconds.to_be_bytes().to_vec())),
632 ctime: ExplicitContextTag5::from(KerberosTime::from(GeneralizedTime::from(current_date))),
633 subkey: Optional::from(sub_key.map(|EncKey { key_type, key_value }| {
634 ExplicitContextTag6::from(EncryptionKey {
635 key_type: ExplicitContextTag0::from(IntegerAsn1::from(vec![key_type.into()])),
636 key_value: ExplicitContextTag1::from(OctetStringAsn1::from(key_value)),
637 })
638 })),
639 seq_number: Optional::from(seq_num.map(|seq_num| {
640 ExplicitContextTag7::from(IntegerAsn1::from_bytes_be_unsigned(seq_num.to_be_bytes().to_vec()))
641 })),
642 authorization_data,
643 }))
644}
645
646#[instrument(level = "trace", skip_all, ret)]
647pub fn generate_ap_rep(session_key: &[u8], seq_number: Vec<u8>, enc_params: &EncryptionParams) -> Result<ApRep> {
648 let current_date = OffsetDateTime::now_utc();
649 let microseconds = current_date.microsecond().min(MAX_MICROSECONDS);
650
651 let encryption_type = enc_params.encryption_type.as_ref().unwrap_or(&DEFAULT_ENCRYPTION_TYPE);
652
653 let enc_ap_rep_part = EncApRepPart::from(EncApRepPartInner {
654 ctime: ExplicitContextTag0::from(KerberosTime::from(GeneralizedTime::from(current_date))),
655 cusec: ExplicitContextTag1::from(IntegerAsn1::from(microseconds.to_be_bytes().to_vec())),
656 subkey: Optional::from(None),
657 seq_number: Optional::from(Some(ExplicitContextTag3::from(IntegerAsn1::from(seq_number)))),
658 });
659
660 let cipher = encryption_type.cipher();
661
662 let encoded_enc_ap_rep_part = picky_asn1_der::to_vec(&enc_ap_rep_part)?;
663 let encrypted_enc_ap_rep_part = cipher.encrypt(session_key, AP_REP_ENC, &encoded_enc_ap_rep_part)?;
664
665 Ok(ApRep::from(ApRepInner {
666 pvno: ExplicitContextTag0::from(IntegerAsn1::from(vec![KERBEROS_VERSION])),
667 msg_type: ExplicitContextTag1::from(IntegerAsn1::from(vec![AP_REP_MSG_TYPE])),
668 enc_part: ExplicitContextTag2::from(EncryptedData {
669 etype: ExplicitContextTag0::from(IntegerAsn1::from(vec![encryption_type.into()])),
670 kvno: Optional::from(None),
671 cipher: ExplicitContextTag2::from(OctetStringAsn1::from(encrypted_enc_ap_rep_part)),
672 }),
673 }))
674}
675
676pub fn generate_tgs_ap_req(
677 ticket: Ticket,
678 session_key: &[u8],
679 authenticator: &Authenticator,
680 enc_params: &EncryptionParams,
681) -> Result<ApReq> {
682 let encryption_type = enc_params.encryption_type.as_ref().unwrap_or(&DEFAULT_ENCRYPTION_TYPE);
683 let cipher = encryption_type.cipher();
684
685 let encoded_authenticator = picky_asn1_der::to_vec(&authenticator)?;
686 let encrypted_authenticator = cipher.encrypt(
687 session_key,
688 TGS_REQ_PA_DATA_AP_REQ_AUTHENTICATOR,
689 &encoded_authenticator,
690 )?;
691
692 trace!(
693 ?session_key,
694 ?encryption_type,
695 "TGS AP_REQ authenticator encryption params",
696 );
697 trace!(
698 plain = ?encoded_authenticator,
699 encrypted = ?encrypted_authenticator,
700 "TGS AP_REQ authenticator",
701 );
702
703 Ok(ApReq::from(ApReqInner {
704 pvno: ExplicitContextTag0::from(IntegerAsn1::from(vec![KERBEROS_VERSION])),
705 msg_type: ExplicitContextTag1::from(IntegerAsn1::from(vec![AP_REQ_MSG_TYPE])),
706 ap_options: ExplicitContextTag2::from(ApOptions::from(BitString::with_bytes(vec![
707 0x00, 0x00, 0x00, 0x00,
709 ]))),
710 ticket: ExplicitContextTag3::from(ticket),
711 authenticator: ExplicitContextTag4::from(EncryptedData {
712 etype: ExplicitContextTag0::from(IntegerAsn1::from(vec![encryption_type.into()])),
713 kvno: Optional::from(None),
714 cipher: ExplicitContextTag2::from(OctetStringAsn1::from(encrypted_authenticator)),
715 }),
716 }))
717}
718
719#[instrument(level = "trace", ret)]
720pub fn generate_ap_req(
721 ticket: Ticket,
722 session_key: &[u8],
723 authenticator: &Authenticator,
724 enc_params: &EncryptionParams,
725 options: ApOptionsFlags,
726) -> Result<ApReq> {
727 let encryption_type = enc_params.encryption_type.as_ref().unwrap_or(&DEFAULT_ENCRYPTION_TYPE);
728 let cipher = encryption_type.cipher();
729
730 let encoded_authenticator = picky_asn1_der::to_vec(&authenticator)?;
731 let encrypted_authenticator = cipher.encrypt(session_key, AP_REQ_AUTHENTICATOR, &encoded_authenticator)?;
732
733 trace!(
734 plain = ?encoded_authenticator,
735 encrypted = ?encrypted_authenticator,
736 "AP_REQ authenticator",
737 );
738
739 Ok(ApReq::from(ApReqInner {
740 pvno: ExplicitContextTag0::from(IntegerAsn1::from(vec![KERBEROS_VERSION])),
741 msg_type: ExplicitContextTag1::from(IntegerAsn1::from(vec![AP_REQ_MSG_TYPE])),
742 ap_options: ExplicitContextTag2::from(ApOptions::from(BitString::with_bytes(
743 options.bits().to_be_bytes().to_vec(),
744 ))),
745 ticket: ExplicitContextTag3::from(ticket),
746 authenticator: ExplicitContextTag4::from(EncryptedData {
747 etype: ExplicitContextTag0::from(IntegerAsn1::from(vec![encryption_type.into()])),
748 kvno: Optional::from(None),
749 cipher: ExplicitContextTag2::from(OctetStringAsn1::from(encrypted_authenticator)),
750 }),
751 }))
752}
753
754pub fn get_mech_list() -> MechTypeList {
756 MechTypeList::from(vec![MechType::from(oids::ms_krb5()), MechType::from(oids::krb5())])
757}
758
759pub fn generate_neg_token_init(sname: Option<&[&str]>) -> Result<ApplicationTag0<GssApiNegInit>> {
764 let mech_token = if let Some(sname) = sname {
765 let sname = sname
766 .iter()
767 .map(|sname| Ok(KerberosStringAsn1::from(IA5String::from_string(sname.to_string())?)))
768 .collect::<Result<Vec<_>>>()?;
769
770 let krb5_neg_token_init = ApplicationTag0(KrbMessage {
771 krb5_oid: ObjectIdentifierAsn1::from(oids::krb5_user_to_user()),
772 krb5_token_id: TGT_REQ_TOKEN_ID,
773 krb_msg: TgtReq {
774 pvno: ExplicitContextTag0::from(IntegerAsn1::from(vec![KERBEROS_VERSION])),
775 msg_type: ExplicitContextTag1::from(IntegerAsn1::from(vec![TGT_REQ_MSG_TYPE])),
776 server_name: ExplicitContextTag2::from(PrincipalName {
777 name_type: ExplicitContextTag0::from(IntegerAsn1::from(vec![NT_SRV_INST])),
778 name_string: ExplicitContextTag1::from(Asn1SequenceOf::from(sname)),
779 }),
780 },
781 });
782
783 Some(ExplicitContextTag2::from(OctetStringAsn1::from(
784 picky_asn1_der::to_vec(&krb5_neg_token_init)?,
785 )))
786 } else {
787 None
788 };
789
790 Ok(ApplicationTag0(GssApiNegInit {
791 oid: ObjectIdentifierAsn1::from(oids::spnego()),
792 neg_token_init: ExplicitContextTag0::from(NegTokenInit {
793 mech_types: Optional::from(Some(ExplicitContextTag0::from(get_mech_list()))),
794 req_flags: Optional::from(None),
795 mech_token: Optional::from(mech_token),
796 mech_list_mic: Optional::from(None),
797 }),
798 }))
799}
800
801pub fn generate_neg_ap_req(ap_req: ApReq, mech_id: oid::ObjectIdentifier) -> Result<ExplicitContextTag1<NegTokenTarg>> {
802 let krb_blob = ApplicationTag0(KrbMessage {
803 krb5_oid: ObjectIdentifierAsn1::from(mech_id),
804 krb5_token_id: AP_REQ_TOKEN_ID,
805 krb_msg: ap_req,
806 });
807
808 Ok(ExplicitContextTag1::from(NegTokenTarg {
809 neg_result: Optional::from(Some(ExplicitContextTag0::from(Asn1RawDer(ACCEPT_INCOMPLETE.to_vec())))),
810 supported_mech: Optional::from(None),
811 response_token: Optional::from(Some(ExplicitContextTag2::from(OctetStringAsn1::from(
812 picky_asn1_der::to_vec(&krb_blob)?,
813 )))),
814 mech_list_mic: Optional::from(None),
815 }))
816}
817
818pub fn generate_final_neg_token_targ(mech_list_mic: Option<Vec<u8>>) -> NegTokenTarg1 {
819 NegTokenTarg1::from(NegTokenTarg {
820 neg_result: Optional::from(Some(ExplicitContextTag0::from(Asn1RawDer(ACCEPT_COMPLETE.to_vec())))),
821 supported_mech: Optional::from(None),
822 response_token: Optional::from(None),
823 mech_list_mic: Optional::from(mech_list_mic.map(|v| ExplicitContextTag3::from(OctetStringAsn1::from(v)))),
824 })
825}
826
827#[instrument(level = "trace", ret)]
828pub fn generate_krb_priv_request(
829 ticket: Ticket,
830 session_key: &[u8],
831 new_password: &[u8],
832 authenticator: &Authenticator,
833 enc_params: &EncryptionParams,
834 seq_num: u32,
835 address: &str,
836) -> Result<KrbPrivMessage> {
837 let ap_req = generate_ap_req(ticket, session_key, authenticator, enc_params, ApOptionsFlags::empty())?;
838
839 let enc_part = EncKrbPrivPart::from(EncKrbPrivPartInner {
840 user_data: ExplicitContextTag0::from(OctetStringAsn1::from(new_password.to_vec())),
841 timestamp: Optional::from(None),
842 usec: Optional::from(None),
843 seq_number: Optional::from(Some(ExplicitContextTag3::from(IntegerAsn1::from_bytes_be_unsigned(
844 seq_num.to_be_bytes().to_vec(),
845 )))),
846 s_address: ExplicitContextTag4::from(HostAddress {
847 addr_type: ExplicitContextTag0::from(IntegerAsn1::from(vec![NET_BIOS_ADDR_TYPE])),
848 address: ExplicitContextTag1::from(OctetStringAsn1::from(address.as_bytes().to_vec())),
849 }),
850 r_address: Optional::from(None),
851 });
852
853 let encryption_type = enc_params.encryption_type.as_ref().unwrap_or(&DEFAULT_ENCRYPTION_TYPE);
854 let cipher = encryption_type.cipher();
855
856 let encryption_key = &authenticator.0.subkey.0.as_ref().unwrap().key_value.0;
857 let encoded_krb_priv = picky_asn1_der::to_vec(&enc_part)?;
858
859 let enc_part = cipher.encrypt(encryption_key, KRB_PRIV_ENC_PART, &encoded_krb_priv)?;
860
861 trace!(?encryption_key, ?encryption_type, "KRB_PRIV encryption params",);
862 trace!(
863 plain = ?encoded_krb_priv,
864 encrypted = ?enc_part,
865 "KRB_PRIV encrypted part",
866 );
867
868 let krb_priv = KrbPriv::from(KrbPrivInner {
869 pvno: ExplicitContextTag0::from(IntegerAsn1::from(vec![KERBEROS_VERSION])),
870 msg_type: ExplicitContextTag1::from(IntegerAsn1::from(vec![KRB_PRIV])),
871 enc_part: ExplicitContextTag3::from(EncryptedData {
872 etype: ExplicitContextTag0::from(IntegerAsn1::from(vec![encryption_type.into()])),
873 kvno: Optional::from(None),
874 cipher: ExplicitContextTag2::from(OctetStringAsn1::from(enc_part)),
875 }),
876 });
877
878 Ok(KrbPrivMessage {
879 ap_message: ApMessage::ApReq(ap_req),
880 krb_priv,
881 })
882}
883
884#[cfg(test)]
885mod tests {
886 use super::*;
887
888 const KRB5_CONFIG_FILE_PATH: &str = "test_assets/krb5.conf";
889
890 #[test]
891 fn test_set_flags() {
892 let mut checksum_values = ChecksumValues::default();
893 let flags = GssFlags::GSS_C_MUTUAL_FLAG | GssFlags::GSS_C_REPLAY_FLAG;
894 checksum_values.set_flags(flags);
895 let expected_bytes = flags.bits().to_le_bytes();
896 assert_eq!(checksum_values.inner[20..24], expected_bytes);
897 }
898
899 #[test]
900 fn test_default() {
901 let checksum_values = ChecksumValues::default();
903 assert_eq!(checksum_values.into_inner(), AUTHENTICATOR_DEFAULT_CHECKSUM);
904 }
905
906 #[test]
907 fn test_flag_for_sign_and_seal() {
908 let mut checksum_values = ChecksumValues::default();
909 let flags = GssFlags::GSS_C_MUTUAL_FLAG
910 | GssFlags::GSS_C_REPLAY_FLAG
911 | GssFlags::GSS_C_SEQUENCE_FLAG
912 | GssFlags::GSS_C_CONF_FLAG
913 | GssFlags::GSS_C_INTEG_FLAG;
914 checksum_values.set_flags(flags);
915 let expected_bytes = [0x3E, 0x00, 0x00, 0x00];
916 assert_eq!(checksum_values.inner[20..24], expected_bytes);
917 }
918
919 #[test]
920 fn test_get_client_principal_realm_from_domain() {
921 let username = "";
922 let domain = "TBT.com";
923
924 let realm = get_client_principal_realm_impl(&[Path::new(KRB5_CONFIG_FILE_PATH)], username, domain);
925
926 assert_eq!(realm, "TBT.COM");
927 }
928
929 #[test]
930 fn test_get_client_principal_realm_from_username() {
931 let username = "user@tbt.com";
932 let domain = "";
933
934 let realm = get_client_principal_realm_impl(&[Path::new(KRB5_CONFIG_FILE_PATH)], username, domain);
935
936 assert_eq!(realm, "TBT.COM");
937 }
938
939 #[test]
940 fn test_get_client_principal_realm_from_subdomain_and_domain() {
941 let username = "";
942 let domain = "s.tbt.com";
943
944 let realm = get_client_principal_realm_impl(&[Path::new(KRB5_CONFIG_FILE_PATH)], username, domain);
945
946 assert_eq!(realm, "TBT.COM");
947 }
948}