libkrimes 0.1.0

A pure rust, minimal kerberos library
Documentation
use super::kdc_rep::KdcRep;
use super::krb_error::KrbError;
use der::{Tag, TagNumber, Writer};

/// ```text
/// AS-REP          ::= [APPLICATION 11] KDC-REP
/// TGS-REP         ::= [APPLICATION 13] KDC-REP
/// ```
#[derive(Debug, Eq, PartialEq)]
// For clarity and keeping to the spec, we allow this warning.
// Normally clippy likes to say "no" because each variant ends
// with 'rep'.
#[allow(clippy::enum_variant_names)]
pub(crate) enum KrbKdcRep {
    AsRep(KdcRep),
    TgsRep(KdcRep),
    ErrRep(KrbError),
}

impl<'a> ::der::Decode<'a> for KrbKdcRep {
    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(11),
            } => {
                let kdc_rep: KdcRep = decoder.decode()?;
                Ok(KrbKdcRep::AsRep(kdc_rep))
            }
            Tag::Application {
                constructed: true,
                number: TagNumber(13),
            } => {
                let kdc_rep: KdcRep = decoder.decode()?;
                Ok(KrbKdcRep::TgsRep(kdc_rep))
            }
            Tag::Application {
                constructed: true,
                number: TagNumber(30),
            } => {
                let err_rep: KrbError = decoder.decode()?;
                Ok(KrbKdcRep::ErrRep(err_rep))
            }
            _ => Err(der::Error::from(der::ErrorKind::TagUnexpected {
                expected: None,
                actual: tag,
            })),
        }
    }
}

impl ::der::Encode for KrbKdcRep {
    fn encoded_len(&self) -> Result<der::Length, der::Error> {
        let len: der::Length = match self {
            KrbKdcRep::AsRep(asrep) => {
                Tag::Application {
                    constructed: true,
                    number: TagNumber(11),
                }
                .encoded_len()?
                    + asrep.encoded_len()?
                    + asrep.encoded_len()?.encoded_len()?
            }
            KrbKdcRep::TgsRep(tgsrep) => {
                Tag::Application {
                    constructed: true,
                    number: TagNumber(13),
                }
                .encoded_len()?
                    + tgsrep.encoded_len()?
                    + tgsrep.encoded_len()?.encoded_len()?
            }
            KrbKdcRep::ErrRep(err_rep) => {
                Tag::Application {
                    constructed: true,
                    number: TagNumber(30),
                }
                .encoded_len()?
                    + err_rep.encoded_len()?
                    + err_rep.encoded_len()?.encoded_len()?
            }
        }?;
        Ok(len)
    }

    fn encode(&self, writer: &mut impl Writer) -> der::Result<()> {
        match self {
            KrbKdcRep::AsRep(asrep) => {
                Tag::Application {
                    constructed: true,
                    number: TagNumber(11),
                }
                .encode(writer)?;
                asrep.encoded_len()?.encode(writer)?;
                asrep.encode(writer)
            }
            KrbKdcRep::TgsRep(tgsrep) => {
                Tag::Application {
                    constructed: true,
                    number: TagNumber(13),
                }
                .encode(writer)?;
                tgsrep.encoded_len()?.encode(writer)?;
                tgsrep.encode(writer)
            }
            KrbKdcRep::ErrRep(err_rep) => {
                Tag::Application {
                    constructed: true,
                    number: TagNumber(30),
                }
                .encode(writer)?;
                err_rep.encoded_len()?.encode(writer)?;
                err_rep.encode(writer)
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::asn1::encrypted_data::EncryptedData;
    use crate::asn1::kerberos_string::KerberosString;
    use crate::asn1::principal_name::PrincipalName;
    use crate::asn1::tagged_ticket::{TaggedTicket, Ticket};
    use der::asn1::Ia5String;
    use der::asn1::OctetString;
    use std::iter::zip;

    use super::KdcRep;
    use super::KrbKdcRep;
    use der::Decode;

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

    struct TestAsRep {
        blob: String,
        padata: Option<Vec<TestPaData>>,
        crealm: String,
        cname: PrincipalName,
        ticket: TaggedTicket,
        encpart: EncryptedData,
    }

    fn verify_as_rep(asrep: &KdcRep, tasrep: &TestAsRep) {
        assert_eq!(asrep.pvno, 5);
        assert_eq!(asrep.msg_type, 11);

        if let Some(tpadata) = &tasrep.padata {
            let padata = &asrep
                .padata
                .as_ref()
                .expect("AS-REP pa_data should be there");
            assert_eq!(tpadata.len(), padata.len());
            let iter = zip(*padata, tpadata);
            for (pa, tpa) in iter {
                assert_eq!(pa.padata_type, tpa.padata_type);
                assert_eq!(pa.padata_value.as_bytes(), tpa.padata_value);
            }
        }

        assert_eq!(asrep.crealm.0.as_str(), tasrep.crealm);

        assert_eq!(asrep.cname.name_type, tasrep.cname.name_type);
        assert_eq!(
            asrep.cname.name_string.len(),
            tasrep.cname.name_string.len()
        );
        let iter = zip(&asrep.cname.name_string, &tasrep.cname.name_string);
        for (name, tname) in iter {
            assert_eq!(name.0.as_str(), tname.0.as_str());
        }

        assert_eq!(asrep.ticket, tasrep.ticket);
        assert_eq!(asrep.enc_part, tasrep.encpart);
    }

    #[test]
    fn krb_kdc_rep_parse() {
        let samples: Vec<TestAsRep> = vec![
            TestAsRep {
                blob: "6b8203513082034da003020105a10302010ba22d302b3029a103020113a2220420301e301ca003020112a1151b134558414d504c452e434f4d7465737475736572a30d1b0b4558414d504c452e434f4da4153013a003020101a10c300a1b087465737475736572a58201ba618201b6308201b2a003020105a10d1b0b4558414d504c452e434f4da220301ea003020102a11730151b066b72627467741b0b4558414d504c452e434f4da382017830820174a003020112a103020101a28201660482016297d16c13bbd7fdd8dac58f284e9eea01c1cc89413195aee01d12ab05c5775f701849e25fd416427693cf8cf6567180cb5c9c1bf157521fdf38316c0ddb0a824b60c98056677ace3bcbccd2c82c203aaad8a0e6df44d07c76be2ddb70349a3c23b7b7bc2211c8bcc879a704872cf46d1d650b55f75e487eafdffbae8dc00e9083e9e0b59aa275a4591a7965d5ffb15f8d96d84a9d0a5840ef5d4715f2e99b3cf3cdc961ce416e4d9e49e7a1a617d9199006d07eb886a70a49c1e8e966f99d6939c0d853636081a1ed0b9fdc4971f447cc5aa503092d91f352d451e349bf58a4320aa116d9a30e944402014aee43f51a457c01ae7f3a6863a8df05569ed969edc97f298bf93be1ed85d64914b293e6dc6ebc8229a6aa040ce7c184cf7082ab3b3b3ff53bc4b47b3512e29479b4ffe8508cfcc1f3e5ec6371039bff5b5c78facc9e00a6d818d4b6ea2be680547abbe8bd79e804814699f51fcdc531bb94613dc9923840a682012c30820128a003020112a282011f0482011be5fca41337468155848766f655f34e00f7124a268bbfc79b68d4e949aa466c05a5cdaca4f21f62303e0175b5112b544c9b8dd950c85c58498aaf0e950ac4eecebd56616c192b640bca93298f4c2ed63bef8efe82ed585847ff4af54ae74bf6d2f9103fd99f90b724df57c0f8daea1d5e801c11d49af9671a1a8a4e8be6f86219e22af04b1b2a76c09489ea3b78eda7d0cf791a598f1e238586a0563b5fa690459cc3a8be3ea6c6a1dc539e37e1e055d2473f30d51e2e91bd5387f3be96d58add57057635ed29da77eeb9d111f18416e9eb3ef192e92c39151f171bd9fbeea181ced330bb6d53ef08001db94a0276914c24ecabf7629bea0309748e4b1630a0e36159f8db557d7e2a87eeaa499ea6d8d8a17efa582ca8b1e023d9a8".to_string(),
                padata: Some(vec![
                    TestPaData { padata_type: 19_u32, padata_value: hex::decode("301e301ca003020112a1151b134558414d504c452e434f4d7465737475736572").expect("Failed to hex encode") }
                ]),
                crealm: "EXAMPLE.COM".to_string(),
                cname: PrincipalName {
                    name_type: 1_i32,
                    name_string: vec![
                        KerberosString(Ia5String::new("testuser").expect("Failed to build test Ia5String"))
                    ]
                },
                ticket: TaggedTicket::new(
                    Ticket {
                        tkt_vno: 5,
                        realm: KerberosString(Ia5String::new("EXAMPLE.COM").expect("Failed to build Ia5String")),
                        sname: PrincipalName {
                            name_type: 2_i32,
                            name_string: vec![
                                KerberosString(Ia5String::new("krbtgt").expect("Failed to build test Ia5String")),
                                KerberosString(Ia5String::new("EXAMPLE.COM").expect("Failed to build test Ia5String"))
                            ],
                        },
                        enc_part: EncryptedData {
                            etype: 18,
                            kvno: Some(1),
                            cipher: OctetString::new(hex::decode("97d16c13bbd7fdd8dac58f284e9eea01c1cc89413195aee01d12ab05c5775f701849e25fd416427693cf8cf6567180cb5c9c1bf157521fdf38316c0ddb0a824b60c98056677ace3bcbccd2c82c203aaad8a0e6df44d07c76be2ddb70349a3c23b7b7bc2211c8bcc879a704872cf46d1d650b55f75e487eafdffbae8dc00e9083e9e0b59aa275a4591a7965d5ffb15f8d96d84a9d0a5840ef5d4715f2e99b3cf3cdc961ce416e4d9e49e7a1a617d9199006d07eb886a70a49c1e8e966f99d6939c0d853636081a1ed0b9fdc4971f447cc5aa503092d91f352d451e349bf58a4320aa116d9a30e944402014aee43f51a457c01ae7f3a6863a8df05569ed969edc97f298bf93be1ed85d64914b293e6dc6ebc8229a6aa040ce7c184cf7082ab3b3b3ff53bc4b47b3512e29479b4ffe8508cfcc1f3e5ec6371039bff5b5c78facc9e00a6d818d4b6ea2be680547abbe8bd79e804814699f51fcdc531bb94613dc9923840").expect("Failed to hex decode")).expect("Failed to build OctetString"),
                        },
                    }
                ),
                encpart: EncryptedData {
                    etype: 18,
                    kvno: None,
                    cipher: OctetString::new(hex::decode("e5fca41337468155848766f655f34e00f7124a268bbfc79b68d4e949aa466c05a5cdaca4f21f62303e0175b5112b544c9b8dd950c85c58498aaf0e950ac4eecebd56616c192b640bca93298f4c2ed63bef8efe82ed585847ff4af54ae74bf6d2f9103fd99f90b724df57c0f8daea1d5e801c11d49af9671a1a8a4e8be6f86219e22af04b1b2a76c09489ea3b78eda7d0cf791a598f1e238586a0563b5fa690459cc3a8be3ea6c6a1dc539e37e1e055d2473f30d51e2e91bd5387f3be96d58add57057635ed29da77eeb9d111f18416e9eb3ef192e92c39151f171bd9fbeea181ced330bb6d53ef08001db94a0276914c24ecabf7629bea0309748e4b1630a0e36159f8db557d7e2a87eeaa499ea6d8d8a17efa582ca8b1e023d9a8").expect("Failed to hex decode")).expect("Failed to build OctetString"),
                },
            }
        ];

        for sample in samples {
            let blob = hex::decode(&sample.blob).expect("Failed to decode sample");
            let message = KrbKdcRep::from_der(&blob).expect("Failed to decode");
            match message {
                KrbKdcRep::AsRep(asrep) => verify_as_rep(&asrep, &sample),
                KrbKdcRep::TgsRep(_) | KrbKdcRep::ErrRep(_) => {
                    unimplemented!("TGS-REP and ErrRep not implemented")
                }
            }
        }
    }
}