libkrimes 0.1.0

A pure rust, minimal kerberos library
Documentation
use super::kdc_req::KdcReq;
use der::{Tag, TagNumber, Writer};

/// ```text
/// AS-REQ          ::= [APPLICATION 10] KDC-REQ
/// TGS-REQ         ::= [APPLICATION 12] KDC-REQ
/// ```
#[derive(Debug, Eq, PartialEq)]
pub(crate) enum KrbKdcReq {
    AsReq(KdcReq),
    TgsReq(KdcReq),
}

impl<'a> ::der::Decode<'a> for KrbKdcReq {
    type Error = der::Error;

    fn decode<R: der::Reader<'a>>(decoder: &mut R) -> der::Result<Self> {
        let tag: der::Tag = decoder.decode()?;
        let _len: der::Length = decoder.decode()?;

        match tag {
            Tag::Application {
                constructed: true,
                number: TagNumber(10),
            } => {
                let kdc_req: KdcReq = decoder.decode()?;
                Ok(KrbKdcReq::AsReq(kdc_req))
            }
            Tag::Application {
                constructed: true,
                number: TagNumber(12),
            } => {
                let kdc_req: KdcReq = decoder.decode()?;
                Ok(KrbKdcReq::TgsReq(kdc_req))
            }
            _ => Err(der::Error::from(der::ErrorKind::TagUnexpected {
                expected: None,
                actual: tag,
            })),
        }
    }
}

impl ::der::Encode for KrbKdcReq {
    fn encoded_len(&self) -> Result<der::Length, der::Error> {
        tracing::trace!(?self);
        let len: der::Length = match self {
            KrbKdcReq::AsReq(asreq) => {
                let tag_len = Tag::Application {
                    constructed: true,
                    number: TagNumber(10),
                }
                .encoded_len()?;

                let as_req_len = asreq.encoded_len()?;
                let as_req_len_len = as_req_len.encoded_len()?;

                tracing::trace!(?tag_len, ?as_req_len, ?as_req_len_len);

                tag_len + as_req_len + as_req_len_len
            }
            KrbKdcReq::TgsReq(tgsreq) => {
                let tag_len = Tag::Application {
                    constructed: true,
                    number: TagNumber(12),
                }
                .encoded_len()?;

                let tgs_req_len = tgsreq.encoded_len()?;
                let tgs_req_len_len = tgs_req_len.encoded_len()?;

                tracing::trace!(?tag_len, ?tgs_req_len, ?tgs_req_len_len);

                tag_len + tgs_req_len + tgs_req_len_len
            }
        }?;
        Ok(len)
    }

    fn encode(&self, writer: &mut impl Writer) -> der::Result<()> {
        match self {
            KrbKdcReq::AsReq(asreq) => {
                Tag::Application {
                    constructed: true,
                    number: TagNumber(10),
                }
                .encode(writer)?;
                asreq.encoded_len()?.encode(writer)?;
                asreq.encode(writer)
            }
            KrbKdcReq::TgsReq(tgsreq) => {
                Tag::Application {
                    constructed: true,
                    number: TagNumber(12),
                }
                .encode(writer)?;
                tgsreq.encoded_len()?.encode(writer)?;
                tgsreq.encode(writer)
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::asn1::constants::{EncryptionType, KrbMessageType, PaDataType};
    use crate::asn1::kdc_req::KdcReq;
    use crate::asn1::kdc_req_body::KdcReqBody;
    use crate::asn1::kerberos_flags::KerberosFlags;
    use crate::asn1::kerberos_time::KerberosTime;
    use crate::asn1::krb_kdc_req::KrbKdcReq;
    use core::iter::zip;
    use der::asn1::OctetString;
    use der::DateTime;
    use der::Decode;

    struct TestPaData {
        padata_type: u32,
        padata_value: Vec<u8>,
    }

    struct TestAsReq {
        blob: String,
        principal: String,
        realm: String,
        padata: Vec<TestPaData>,
        kdc_options: KerberosFlags,
        from: Option<KerberosTime>,
        till: KerberosTime,
        rtime: Option<KerberosTime>,
        nonce: i32,
        etype: Vec<i32>,
        addresses: Option<Vec<(i32, OctetString)>>,
    }

    fn verify_as_req(asreq: &KdcReq, tasreq: &TestAsReq) {
        assert_eq!(asreq.pvno, 5);
        assert_eq!(asreq.msg_type, KrbMessageType::KrbAsReq.into());

        let pa = asreq.padata.as_ref().unwrap();
        assert_eq!(pa.len(), tasreq.padata.len());
        let iter = zip(pa, &tasreq.padata);
        for (pa, tpa) in iter {
            assert_eq!(pa.padata_type, tpa.padata_type);
            assert_eq!(pa.padata_value.as_bytes(), tpa.padata_value);
        }

        let bits = asreq
            .req_body
            .decode_as::<KdcReqBody>()
            .unwrap()
            .kdc_options;
        assert_eq!(bits, tasreq.kdc_options);

        // Needed because we have to hold the req body as Any (raw bytes) until
        // we perform checksums in the TGS path.
        let req_body = asreq.req_body.decode_as::<KdcReqBody>().unwrap();

        let cname = req_body.cname.as_ref().unwrap();
        assert_eq!(cname.name_type, 1);
        assert_eq!(cname.name_string[0].0.to_string(), tasreq.principal);

        assert_eq!(req_body.realm.0.to_string(), tasreq.realm);

        let sname = req_body.sname.as_ref().unwrap();
        assert_eq!(sname.name_type, 2);
        assert_eq!(sname.name_string[0].0.to_string(), "krbtgt");
        assert_eq!(sname.name_string[1].0.to_string(), tasreq.realm);

        if let Some(trtime) = tasreq.rtime {
            let rtime = req_body.rtime.expect("rtime must be there");
            assert_eq!(rtime, trtime);
        } else {
            assert!(req_body.rtime.is_none());
        }

        assert_eq!(req_body.till, tasreq.till);

        if let Some(tfrom) = tasreq.from {
            let from = req_body.from.expect("from must be there");
            assert_eq!(from, tfrom);
        } else {
            assert!(req_body.from.is_none());
        }

        assert_eq!(req_body.nonce, tasreq.nonce);
        assert_eq!(req_body.etype, tasreq.etype);

        if let Some(taddrs) = &tasreq.addresses {
            let addrs = req_body
                .addresses
                .as_ref()
                .expect("addresses must be there");
            assert_eq!(addrs.len(), taddrs.len());

            let iter = zip(addrs, taddrs);
            for (addr, taddr) in iter {
                assert_eq!(addr.addr_type, taddr.0);
                assert_eq!(addr.address, taddr.1);
            }
        } else {
            assert!(req_body.addresses.is_none());
        }
    }

    #[test]
    fn krb_kdc_req_parse() {
        let samples: Vec<TestAsReq> = vec![
            TestAsReq {
                blob: "6a81b23081afa103020105a20302010aa31a3018300aa10402020096a2020400300aa10402020095a2020400a48186308183a00703050000000010a1143012a003020101a10b30091b0777696c6c69616da20b1b094b4b4443502e444556a31e301ca003020102a11530131b066b72627467741b094b4b4443502e444556a511180f32303234303431373034313534395aa70602047fbda7aea81a301802011202011102011402011302011002011702011902011a".to_string(),
                principal: "william".to_string(),
                realm: "KKDCP.DEV".to_string(),
                padata: vec![
                    TestPaData {
                        padata_type: PaDataType::PadataAsFreshness as u32,
                        padata_value: vec![],
                    },
                    TestPaData {
                        padata_type: PaDataType::EncpadataReqEncPaRep as u32,
                        padata_value: vec![],
                    }
                ],
                kdc_options: KerberosFlags::RenewableOk,
                from: None,
                till: KerberosTime::from_date_time(DateTime::new(2024, 4, 17, 4, 15, 49).expect("Failed to build DateTime")),
                rtime: None,
                nonce: 2143135662,
                etype: vec![
                    EncryptionType::AES256_CTS_HMAC_SHA1_96 as i32,
                    EncryptionType::AES128_CTS_HMAC_SHA1_96 as i32,
                    EncryptionType::AES256_CTS_HMAC_SHA384_192 as i32,
                    EncryptionType::AES128_CTS_HMAC_SHA256_128 as i32,
                    EncryptionType::DES3_CBC_SHA1_KD as i32,
                    EncryptionType::RC4_HMAC as i32,
                    EncryptionType::CAMELLIA128_CTS_CMAC as i32,
                    EncryptionType::CAMELLIA256_CTS_CMAC as i32,
                ],
                addresses: None,
            },
            TestAsReq {
                blob: "6a81ff3081fca103020105a20302010aa32d302b300aa10402020096a2020400300aa10402020095a20204003011a10402020080a20904073005a0030101ffa481c03081bda00703050050010010a1123010a003020101a10930071b057573657231a20c1b0a41464f524553542e4144a31f301da003020102a11630141b066b72627467741b0a41464f524553542e4144a511180f32303234303631323134353130395aa7060204586155dda814301202011402011302011202011102011a020119a93e303c300da003020102a1060404c0a80164300da003020102a1060404ac110001300da003020102a1060404c0a86501300da003020102a10604040a95d65a".to_string(),
                principal: "user1".to_string(),
                realm: "AFOREST.AD".to_string(),
                padata: vec![
                    TestPaData {
                        padata_type: PaDataType::PadataAsFreshness as u32,
                        padata_value: vec![],
                    },
                    TestPaData {
                        padata_type: PaDataType::EncpadataReqEncPaRep as u32,
                        padata_value: vec![],
                    },
                    TestPaData {
                        padata_type: PaDataType::PaPacRequest as u32,
                        padata_value: vec![0x30, 0x05, 0xa0, 0x03, 0x01, 0x01, 0xff],
                    }
                ],
                kdc_options: KerberosFlags::RenewableOk | KerberosFlags::Forwardable | KerberosFlags::Canonicalize | KerberosFlags::Proxiable,
                from: None,
                till: KerberosTime::from_date_time(DateTime::new(2024, 6, 12, 14, 51, 9).expect("Failed to build DateTime")),
                rtime: None,
                nonce: 1482773981,
                etype: vec![
                    EncryptionType::AES256_CTS_HMAC_SHA384_192 as i32,
                    EncryptionType::AES128_CTS_HMAC_SHA256_128 as i32,
                    EncryptionType::AES256_CTS_HMAC_SHA1_96 as i32,
                    EncryptionType::AES128_CTS_HMAC_SHA1_96 as i32,
                    EncryptionType::CAMELLIA256_CTS_CMAC as i32,
                    EncryptionType::CAMELLIA128_CTS_CMAC as i32,
                ],
                addresses: Some(vec![
                    (2, OctetString::new(vec![0xc0, 0xa8, 0x01, 0x64]).expect("Failed to build octet string")),
                    (2, OctetString::new(vec![0xAC, 0x11, 0x00, 0x01]).expect("Failed to build octet string")),
                    (2, OctetString::new(vec![0xC0, 0xA8, 0x65, 0x01]).expect("Failed to build octet string")),
                    (2, OctetString::new(vec![0x0A, 0x95, 0xD6, 0x5A]).expect("Failed to build octet string"))
                ]),
            },
            TestAsReq {
                blob: "6a81ca3081c7a103020105a20302010aa31a3018300aa10402020096a2020400300aa10402020095a2020400a4819e30819ba00703050000800000a1153013a003020101a10c300a1b087465737475736572a20d1b0b4558414d504c452e434f4da320301ea003020102a11730151b066b72627467741b0b4558414d504c452e434f4da511180f32303234303631363035323730315aa611180f32303234303632323035323730315aa70602042e71de55a81a301802011202011102011402011302011002011702011902011a".to_string(),
                principal: "testuser".to_string(),
                realm: "EXAMPLE.COM".to_string(),
                padata: vec![
                    TestPaData {
                        padata_type: PaDataType::PadataAsFreshness as u32,
                        padata_value: vec![],
                    },
                    TestPaData {
                        padata_type: PaDataType::EncpadataReqEncPaRep as u32,
                        padata_value: vec![],
                    }
                ],
                kdc_options: KerberosFlags::Renewable,
                from: None,
                till: KerberosTime::from_date_time(DateTime::new(2024, 6, 16, 5, 27, 1).expect("Failed to build DateTime")),
                rtime: Some(KerberosTime::from_date_time(DateTime::new(2024, 6, 22, 5, 27, 1).expect("Failed to build DateTime"))),
                nonce: 779214421,
                etype: vec![
                    EncryptionType::AES256_CTS_HMAC_SHA1_96 as i32,
                    EncryptionType::AES128_CTS_HMAC_SHA1_96 as i32,
                    EncryptionType::AES256_CTS_HMAC_SHA384_192 as i32,
                    EncryptionType::AES128_CTS_HMAC_SHA256_128 as i32,
                    EncryptionType::DES3_CBC_SHA1_KD as i32,
                    EncryptionType::RC4_HMAC as i32,
                    EncryptionType::CAMELLIA128_CTS_CMAC as i32,
                    EncryptionType::CAMELLIA256_CTS_CMAC as i32,
                ],
                addresses: None,
            }
        ];

        for sample in samples {
            let blob = hex::decode(&sample.blob).expect("Failed to decode sample");
            let message = KrbKdcReq::from_der(&blob).expect("Failed to decode");

            match message {
                KrbKdcReq::AsReq(asreq) => verify_as_req(&asreq, &sample),
                KrbKdcReq::TgsReq(_) => unimplemented!("TGS-REQ not implemented"),
            }
        }
    }
}