From bef493f7e3e2b3706b4c3b72de148b9b0d8e4f44 Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 6 Oct 2023 14:07:08 -0400 Subject: [PATCH 01/12] feat: add if condition for HTTP target feat: add if condition for HTTP target --- Cargo.lock | 8 +- Cargo.toml | 43 ++++-- examples/kerberos.rs | 140 +++++++++++++++++ src/kerberos/client/generators.rs | 27 ++++ src/kerberos/mod.rs | 242 ++++++++++++++++++++++++++---- 5 files changed, 417 insertions(+), 43 deletions(-) create mode 100644 examples/kerberos.rs diff --git a/Cargo.lock b/Cargo.lock index 3ef6a97d..661f1ce6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -140,9 +140,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64" -version = "0.21.3" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "base64ct" @@ -1874,12 +1874,14 @@ version = "0.10.1" dependencies = [ "async-dnssd", "async-recursion", + "base64", "bitflags 2.4.0", "byteorder", "cfg-if", "crypto-mac", "futures", "hmac", + "hyper", "lazy_static", "md-5", "md4", @@ -1905,6 +1907,8 @@ dependencies = [ "time", "tokio", "tracing", + "tracing-log", + "tracing-subscriber", "trust-dns-resolver", "url", "uuid", diff --git a/Cargo.toml b/Cargo.toml index 464d7880..af505a3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,14 +11,8 @@ description = "A Rust implementation of the Security Support Provider Interface keywords = ["ntlm", "auth", "sspi", "windows", "kerberos"] [workspace] -members = [ - "ffi", - "ffi/symbol-rename-macro", -] -exclude = [ - "tools/sspi-ffi-attacker", - "tools/wasm-testcompile", -] +members = ["ffi", "ffi/symbol-rename-macro"] +exclude = ["tools/sspi-ffi-attacker", "tools/wasm-testcompile"] [features] default = [] @@ -47,7 +41,11 @@ lazy_static = "1.4" serde = "1" serde_derive = "1" url = "2.4" -reqwest = { version = "0.11", features = ["blocking", "rustls-tls", "rustls-tls-native-roots"], optional = true, default-features = false } +reqwest = { version = "0.11", features = [ + "blocking", + "rustls-tls", + "rustls-tls-native-roots", +], optional = true, default-features = false } picky = { version = "7.0.0-rc.8", default-features = false } picky-krb = "0.8" @@ -61,7 +59,9 @@ trust-dns-resolver = { version = "0.23", optional = true } portpicker = { version = "0.1", optional = true } num-bigint-dig = "0.8" tracing = "0.1" -rustls = { version = "0.21", features = ["dangerous_configuration"], optional = true } +rustls = { version = "0.21", features = [ + "dangerous_configuration", +], optional = true } zeroize = { version = "1.6", features = ["zeroize_derive"] } tokio = { version = "1.32", features = ["time", "rt"], optional = true } pcsc = { version = "2.8", optional = true } @@ -69,9 +69,22 @@ async-recursion = "1.0.5" [target.'cfg(windows)'.dependencies] winreg = "0.51" -winapi = { version = "0.3", features = ["std", "sspi", "rpcdce", "impl-default", "timezoneapi", "wincrypt"] } -windows = { version = "0.51", features = [ "Win32_Foundation", "Win32_NetworkManagement_Dns"] } -windows-sys = { version = "0.48", features = ["Win32_Security_Cryptography", "Win32_Foundation"] } +winapi = { version = "0.3", features = [ + "std", + "sspi", + "rpcdce", + "impl-default", + "timezoneapi", + "wincrypt", +] } +windows = { version = "0.51", features = [ + "Win32_Foundation", + "Win32_NetworkManagement_Dns", +] } +windows-sys = { version = "0.48", features = [ + "Win32_Security_Cryptography", + "Win32_Foundation", +] } [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] async-dnssd = "0.5" @@ -82,3 +95,7 @@ tokio = { version = "1.32", features = ["time", "rt"] } static_assertions = "1" whoami = "1.4" picky = { version = "7.0.0-rc.8", features = ["x509"] } +base64 = "0.21.4" +hyper = "0.14.27" +tracing-log = "0.1.3" +tracing-subscriber = "0.3.17" diff --git a/examples/kerberos.rs b/examples/kerberos.rs new file mode 100644 index 00000000..5f7befe6 --- /dev/null +++ b/examples/kerberos.rs @@ -0,0 +1,140 @@ +use base64::Engine; +use hyper::header::{ + ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, AUTHORIZATION, CONNECTION, CONTENT_LENGTH, HOST, USER_AGENT, +}; +use hyper::StatusCode; +use sspi::builders::EmptyInitializeSecurityContext; +use sspi::{ + AcquireCredentialsHandleResult, ClientRequestFlags, CredentialsBuffers, DataRepresentation, + InitializeSecurityContextResult, KerberosConfig, Negotiate, SecurityBuffer, SecurityBufferType, SecurityStatus, + Sspi, +}; +use sspi::{Kerberos, SspiImpl}; +use std::error::Error; +use tracing::debug; +use tracing_log::log::info; +const TARGET_NAME: &'static str = "HTTP/your-server-name.your.domain"; + +fn main() -> Result<(), Box> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) // Adjust level as needed + .init(); + let kdc_url = "kdc.your.domain:88".to_string(); + let client_hostname = whoami::hostname(); + let kerberos_config = KerberosConfig::new(&kdc_url, client_hostname.clone()); + let mut kerberos = Kerberos::new_client_from_config(kerberos_config).unwrap(); + + let mut acq_creds_handle_result = get_cred_handle(&mut kerberos); + + let mut input = vec![SecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; + let mut output = vec![SecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; + + loop { + match step( + &mut kerberos, + &mut acq_creds_handle_result.credentials_handle, + &mut input, + &mut output, + ) { + Err(e) => panic!("error steping {:?}", e), + Ok(result) => { + let status_code = process_authentication(&mut input, &mut output)?; + if status_code == StatusCode::OK { + info!("connection authenticated"); + break Ok(()); + } + + if result.status == SecurityStatus::Ok && status_code != StatusCode::OK { + panic!("connection authentication failed"); + } + } + } + } +} + +fn get_cred_handle(kerberos: &mut Kerberos) -> AcquireCredentialsHandleResult> { + let username = "user@your.domain".to_string(); + let password = "user's_password".to_string(); + let identity = sspi::AuthIdentity { + username, + password: password.into(), + domain: None, + }; + let acq_creds_handle_result = kerberos + .acquire_credentials_handle() + .with_credential_use(sspi::CredentialUse::Outbound) + .with_auth_data(&identity.into()) + .execute() + .expect("AcquireCredentialsHandle resulted in error"); + acq_creds_handle_result +} + +fn process_authentication( + input: &mut Vec, + output: &mut Vec, +) -> Result> { + let output_token_in_binary = &output[0].buffer; + let base_64_token = base64::engine::general_purpose::STANDARD.encode(output_token_in_binary); + let server_result = send_http(base_64_token)?; + debug!("server responde = {:?}", server_result); + let www_authenticate = server_result + .headers() + .get("www-authenticate") + .ok_or("www-authentication header not found")?; + let server_token = www_authenticate.to_str().unwrap().replace("Negotiate ", ""); + if server_token.len() <= 5 { + panic!("server token not found"); + } + let decoded_new_token = base64::engine::general_purpose::STANDARD.decode(server_token).unwrap(); + clear(input); + clear(output); + input[0].buffer = decoded_new_token; + + Ok(server_result.status()) +} + +fn clear(buf: &mut Vec) { + buf[0].buffer.clear(); +} + +fn send_http(negotiate_token: String) -> Result> { + let client = reqwest::blocking::Client::new(); + let resp = client + .post("http://your-server-name.your.domain:5985/wsman") + .header(AUTHORIZATION, format!("Negotiate {}", negotiate_token)) + .header(HOST, "our-server-name.your.domain:5985") + .header(CONNECTION, "keep-alive") + .header(CONTENT_LENGTH, "0") + .header( + USER_AGENT, + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0", + ) + .header(ACCEPT, "*/*") + .header(ACCEPT_ENCODING, "gzip, deflate") + .header(ACCEPT_LANGUAGE, "en-US,en;q=0.9") + .send()?; + + Ok(resp) +} + +fn step( + kerberos: &mut Kerberos, + credential_handle: &mut ::CredentialsHandle, + input_buffer: &mut Vec, + output_buffer: &mut Vec, +) -> Result> { + output_buffer[0].buffer.clear(); + let mut builder = EmptyInitializeSecurityContext::<::CredentialsHandle>::new() + .with_credentials_handle(credential_handle) + .with_context_requirements(ClientRequestFlags::MUTUAL_AUTH) + .with_target_data_representation(DataRepresentation::Native) + .with_target_name(TARGET_NAME) + .with_input(input_buffer) + .with_output(output_buffer); + + let result = kerberos + .initialize_security_context_impl(&mut builder) + .resolve_with_default_network_client()?; + + Ok(result) +} diff --git a/src/kerberos/client/generators.rs b/src/kerberos/client/generators.rs index df309d99..759430af 100644 --- a/src/kerberos/client/generators.rs +++ b/src/kerberos/client/generators.rs @@ -572,6 +572,33 @@ pub fn generate_neg_ap_req(ap_req: ApReq, mech_id: oid::ObjectIdentifier) -> Res })) } +pub fn generate_neg_ap_req_plain( + ap_req: ApReq, + mech_id: oid::ObjectIdentifier, +) -> Result> { + let krb_blob: ApplicationTag<_, 0> = ApplicationTag(KrbMessage { + krb5_oid: ObjectIdentifierAsn1::from(mech_id), + krb5_token_id: AP_REQ_TOKEN_ID, + krb_msg: ap_req, + }); + + let data = ExplicitContextTag0::from(NegTokenInit { + mech_types: Optional::from(Some(ExplicitContextTag0::from(get_mech_list()))), + req_flags: Optional::from(None), + mech_token: Optional::from(Some(ExplicitContextTag2::from(OctetStringAsn1::from( + picky_asn1_der::to_vec(&krb_blob)?, + )))), + mech_list_mic: Optional::from(None), + }); + + let gss_req = GssApiNegInit { + oid: ObjectIdentifierAsn1::from(oids::spnego()), + neg_token_init: data, + }; + + Ok(ApplicationTag0(gss_req)) +} + pub fn generate_final_neg_token_targ(mech_list_mic: Option>) -> NegTokenTarg1 { NegTokenTarg1::from(NegTokenTarg { neg_result: Optional::from(Some(ExplicitContextTag0::from(Asn1RawDer(ACCEPT_COMPLETE.to_vec())))), diff --git a/src/kerberos/mod.rs b/src/kerberos/mod.rs index f6a590f2..63153f0d 100644 --- a/src/kerberos/mod.rs +++ b/src/kerberos/mod.rs @@ -44,7 +44,8 @@ use crate::builders::ChangePassword; use crate::generator::{GeneratorChangePassword, GeneratorInitSecurityContext, NetworkRequest, YieldPointLocal}; use crate::kerberos::client::extractors::{extract_salt_from_krb_error, extract_status_code_from_krb_priv_response}; use crate::kerberos::client::generators::{ - generate_authenticator, generate_final_neg_token_targ, get_mech_list, GenerateTgsReqOptions, + generate_authenticator, generate_final_neg_token_targ, generate_neg_ap_req_plain, get_mech_list, + GenerateTgsReqOptions, }; use crate::kerberos::pa_datas::AsRepSessionKeyExtractor; use crate::kerberos::server::extractors::{extract_ap_rep_from_neg_token_targ, extract_sub_session_key_from_ap_rep}; @@ -653,40 +654,225 @@ impl<'a> Kerberos { Ok(()) } - pub async fn initialize_security_context_impl( + #[instrument(ret, fields(state = ?self.state), skip_all)] + pub(crate) async fn initialize_security_context_impl( &'a mut self, yield_point: &mut YieldPointLocal, builder: &'a mut crate::builders::FilledInitializeSecurityContext<'_, ::CredentialsHandle>, ) -> Result { + let (service_name, _service_principal_name) = parse_target_name(builder.target_name.ok_or_else(|| { + Error::new( + ErrorKind::NoCredentials, + "Service target name (service principal name) is not provided", + ) + })?)?; + + let (username, service_name) = match check_if_empty!( + builder.credentials_handle.as_ref().unwrap().as_ref(), + "AuthIdentity is not provided" + ) { + CredentialsBuffers::AuthIdentity(auth_identity) => { + let username = utf16_bytes_to_utf8_string(&auth_identity.user); + let domain = utf16_bytes_to_utf8_string(&auth_identity.domain); + + (format!("{}.{}", username, domain.to_ascii_lowercase()), service_name) + } + #[cfg(feature = "scard")] + CredentialsBuffers::SmartCard(_) => (_service_principal_name.into(), service_name), + }; + debug!(username, service_name); + + match service_name { + "HTTP" => self.initialize_security_context_impl_plain(yield_point, builder).await, + _ => { + self.initialize_security_context_impl_p2p(yield_point, builder, &username, service_name) + .await + } + } + } + + pub(crate) async fn initialize_security_context_impl_plain( + &'a mut self, + yield_point: &mut YieldPointLocal, + builder: &'a mut crate::builders::FilledInitializeSecurityContext<'_, ::CredentialsHandle>, + ) -> Result { + let credentials = builder + .credentials_handle + .as_ref() + .unwrap() + .as_ref() + .ok_or_else(|| Error::new(ErrorKind::WrongCredentialHandle, "No credentials provided"))?; + + let (username, password, realm, cname_type) = match credentials { + CredentialsBuffers::AuthIdentity(auth_identity) => { + let username = utf16_bytes_to_utf8_string(&auth_identity.user); + let domain = utf16_bytes_to_utf8_string(&auth_identity.domain); + let password = utf16_bytes_to_utf8_string(auth_identity.password.as_ref()); + + let realm = get_client_principal_realm(&username, &domain); + let cname_type = get_client_principal_name_type(&username, &domain); + + (username, password, realm, cname_type) + } + }; + self.realm = Some(realm.clone()); + + let options = GenerateAsReqOptions { + realm: &realm, + username: &username, + cname_type, + snames: &[TGT_SERVICE_NAME, &realm], + // 4 = size of u32 + nonce: &OsRng.gen::<[u8; 4]>(), + hostname: &unwrap_hostname(self.config.hostname.as_deref())?, + context_requirements: builder.context_requirements, + }; + let kdc_req_body = generate_as_req_kdc_body(&options)?; + + let pa_data_options = match credentials { + CredentialsBuffers::AuthIdentity(auth_identity) => { + let domain = utf16_bytes_to_utf8_string(&auth_identity.domain); + let salt = format!("{}{}", domain, username); + + AsReqPaDataOptions::AuthIdentity(GenerateAsPaDataOptions { + password: &password, + salt: salt.as_bytes().to_vec(), + enc_params: self.encryption_params.clone(), + with_pre_auth: false, + }) + } + }; + + let as_rep = self.as_exchange(yield_point, &kdc_req_body, pa_data_options).await?; + + info!("AS exchange finished successfully."); + + self.realm = Some(as_rep.0.crealm.0.to_string()); + + let (encryption_type, salt) = extract_encryption_params_from_as_rep(&as_rep)?; + + let encryption_type = CipherSuite::try_from(encryption_type as usize)?; + + self.encryption_params.encryption_type = Some(encryption_type); + + let mut authenticator = generate_authenticator(GenerateAuthenticatorOptions { + kdc_rep: &as_rep.0, + seq_num: Some(OsRng.gen::()), + sub_key: None, + checksum: None, + channel_bindings: self.channel_bindings.as_ref(), + extensions: Vec::new(), + })?; + + let mut session_key_extractor = match credentials { + CredentialsBuffers::AuthIdentity(_) => AsRepSessionKeyExtractor::AuthIdentity { + salt: &salt, + password: &password, + enc_params: &mut self.encryption_params, + }, + }; + let session_key_1 = session_key_extractor.session_key(&as_rep)?; + + let service_principal = builder.target_name.ok_or_else(|| { + Error::new( + ErrorKind::NoCredentials, + "Service target name (service principal name) is not provided", + ) + })?; + + let tgs_req: picky_asn1_der::application_tag::ApplicationTag = + generate_tgs_req(GenerateTgsReqOptions { + realm: &as_rep.0.crealm.0.to_string(), + service_principal, + session_key: &session_key_1, + ticket: as_rep.0.ticket.0, + authenticator: &mut authenticator, + additional_tickets: None.map(|ticket| vec![ticket]), + enc_params: &self.encryption_params, + context_requirements: builder.context_requirements, + })?; + + let response = self.send(yield_point, &serialize_message(&tgs_req)?).await?; + + // first 4 bytes are message len. skipping them + let mut d = picky_asn1_der::Deserializer::new_from_bytes(&response[4..]); + let tgs_rep: KrbResult = KrbResult::deserialize(&mut d)?; + let tgs_rep = tgs_rep?; + + info!("TGS exchange finished successfully"); + + let session_key_2 = extract_session_key_from_tgs_rep(&tgs_rep, &session_key_1, &self.encryption_params)?; + + self.encryption_params.session_key = Some(session_key_2); + + let seq_num = self.next_seq_number(); + + let enc_type = self + .encryption_params + .encryption_type + .as_ref() + .unwrap_or(&DEFAULT_ENCRYPTION_TYPE); + let authenticator_sub_key = generate_random_symmetric_key(enc_type, &mut OsRng); + + let authenticator_options = GenerateAuthenticatorOptions { + kdc_rep: &tgs_rep.0, + seq_num: Some(seq_num), + sub_key: Some(EncKey { + key_type: enc_type.clone(), + key_value: authenticator_sub_key, + }), + checksum: Some(ChecksumOptions { + checksum_type: AUTHENTICATOR_CHECKSUM_TYPE.to_vec(), + checksum_value: AUTHENTICATOR_DEFAULT_CHECKSUM.to_vec(), + }), + channel_bindings: self.channel_bindings.as_ref(), + extensions: Vec::new(), + }; + + let authenticator = generate_authenticator(authenticator_options)?; + let encoded_auth = picky_asn1_der::to_vec(&authenticator)?; + info!(encoded_ap_req_authenticator = ?encoded_auth); + + let mech_id = oids::krb5(); + + let context_requirements = builder.context_requirements; + + let ap_req = generate_ap_req( + tgs_rep.0.ticket.0, + self.encryption_params.session_key.as_ref().unwrap(), + &authenticator, + &self.encryption_params, + context_requirements.into(), + )?; + + let generated_neg_ap_req = generate_neg_ap_req_plain(ap_req, mech_id)?; + let encoded_neg_ap_req = picky_asn1_der::to_vec(&generated_neg_ap_req)?; + let output_token = SecurityBuffer::find_buffer_mut(builder.output, SecurityBufferType::Token)?; + // the out put token is already a certificate instead of a contentInfo here, need to figure out why + output_token.buffer.write_all(&encoded_neg_ap_req)?; + + self.state = KerberosState::ApExchange; + + Ok(InitializeSecurityContextResult { + status: SecurityStatus::Ok, + flags: ClientResponseFlags::empty(), + expiry: None, + }) + } + + pub(crate) async fn initialize_security_context_impl_p2p( + &'a mut self, + yield_point: &mut YieldPointLocal, + builder: &'a mut crate::builders::FilledInitializeSecurityContext<'_, ::CredentialsHandle>, + username: &'a str, + service_name: &'a str, + ) -> Result { + debug!("use kerberos peer to peer protocol"); trace!(?builder); let status = match self.state { KerberosState::Negotiate => { - let (service_name, _service_principal_name) = - parse_target_name(builder.target_name.ok_or_else(|| { - Error::new( - ErrorKind::NoCredentials, - "Service target name (service principal name) is not provided", - ) - })?)?; - - let (username, service_name) = match check_if_empty!( - builder.credentials_handle.as_ref().unwrap().as_ref(), - "AuthIdentity is not provided" - ) { - CredentialsBuffers::AuthIdentity(auth_identity) => { - let username = utf16_bytes_to_utf8_string(&auth_identity.user); - let domain = utf16_bytes_to_utf8_string(&auth_identity.domain); - - (format!("{}.{}", username, domain.to_ascii_lowercase()), service_name) - } - #[cfg(feature = "scard")] - CredentialsBuffers::SmartCard(_) => (_service_principal_name.into(), service_name), - }; - debug!(username, service_name); - - let encoded_neg_token_init = - picky_asn1_der::to_vec(&generate_neg_token_init(&username, service_name)?)?; + let encoded_neg_token_init = picky_asn1_der::to_vec(&generate_neg_token_init(username, service_name)?)?; let output_token = SecurityBuffer::find_buffer_mut(builder.output, SecurityBufferType::Token)?; output_token.buffer.write_all(&encoded_neg_token_init)?; @@ -890,7 +1076,7 @@ impl<'a> Kerberos { info!(encoded_ap_req_authenticator = ?encoded_auth); // FIXME: properly negotiate mech id - Windows always does KRB5 U2U - let mech_id = oids::krb5_user_to_user(); + let mech_id = oids::ms_krb5(); let mut context_requirements = builder.context_requirements; From 21c50925cd509e9d905d114e778fd01a16bfa28a Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 6 Oct 2023 14:36:12 -0400 Subject: [PATCH 02/12] fix typo --- src/kerberos/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/kerberos/mod.rs b/src/kerberos/mod.rs index 63153f0d..9747f6dc 100644 --- a/src/kerberos/mod.rs +++ b/src/kerberos/mod.rs @@ -1075,8 +1075,8 @@ impl<'a> Kerberos { let encoded_auth = picky_asn1_der::to_vec(&authenticator)?; info!(encoded_ap_req_authenticator = ?encoded_auth); - // FIXME: properly negotiate mech id - Windows always does KRB5 U2U - let mech_id = oids::ms_krb5(); + // FIXME: properly negotiate mech id - Windows always does KRB5 U2U for Non-HTTP Target + let mech_id = oids::krb5_user_to_user(); let mut context_requirements = builder.context_requirements; From bfbd79760407b1f5a98095a1dee1e2afd1da802e Mon Sep 17 00:00:00 2001 From: irving ou Date: Fri, 6 Oct 2023 15:00:25 -0400 Subject: [PATCH 03/12] do not test examples --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef04de02..0e64b055 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,7 +77,7 @@ jobs: - uses: actions/checkout@v3 - name: Test - run: cargo test --manifest-path ${{ matrix.manifest }} ${{ matrix.additional-args }} + run: cargo test --manifest-path ${{ matrix.manifest }} ${{ matrix.additional-args }} --test * wasm: name: WASM target From f9a79c4dda6ee27009079a9c126376f4b208a756 Mon Sep 17 00:00:00 2001 From: "irvingouj@Devolutions" <139169536+irvingoujAtDevolution@users.noreply.github.com> Date: Sat, 7 Oct 2023 12:09:50 -0400 Subject: [PATCH 04/12] Update src/kerberos/mod.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Benoît Cortier --- src/kerberos/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kerberos/mod.rs b/src/kerberos/mod.rs index 9747f6dc..27358047 100644 --- a/src/kerberos/mod.rs +++ b/src/kerberos/mod.rs @@ -848,7 +848,7 @@ impl<'a> Kerberos { let generated_neg_ap_req = generate_neg_ap_req_plain(ap_req, mech_id)?; let encoded_neg_ap_req = picky_asn1_der::to_vec(&generated_neg_ap_req)?; let output_token = SecurityBuffer::find_buffer_mut(builder.output, SecurityBufferType::Token)?; - // the out put token is already a certificate instead of a contentInfo here, need to figure out why + // TODO: the out put token is already a certificate instead of a contentInfo here, need to figure out why output_token.buffer.write_all(&encoded_neg_ap_req)?; self.state = KerberosState::ApExchange; From b2b5574d9f33c0e00049c27eabf895346b61d74b Mon Sep 17 00:00:00 2001 From: "irvingouj@Devolutions" <139169536+irvingoujAtDevolution@users.noreply.github.com> Date: Sat, 7 Oct 2023 12:09:58 -0400 Subject: [PATCH 05/12] Update examples/kerberos.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Benoît Cortier --- examples/kerberos.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/kerberos.rs b/examples/kerberos.rs index 5f7befe6..db8d26c2 100644 --- a/examples/kerberos.rs +++ b/examples/kerberos.rs @@ -12,7 +12,7 @@ use sspi::{ use sspi::{Kerberos, SspiImpl}; use std::error::Error; use tracing::debug; -use tracing_log::log::info; +use tracing::info; const TARGET_NAME: &'static str = "HTTP/your-server-name.your.domain"; fn main() -> Result<(), Box> { From fb2a5c10b7d46d2bd07bcd4a073efed1a4facae9 Mon Sep 17 00:00:00 2001 From: "irvingouj@Devolutions" <139169536+irvingoujAtDevolution@users.noreply.github.com> Date: Sat, 7 Oct 2023 12:11:12 -0400 Subject: [PATCH 06/12] Update examples/kerberos.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Benoît Cortier --- examples/kerberos.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/kerberos.rs b/examples/kerberos.rs index db8d26c2..1e1731cc 100644 --- a/examples/kerberos.rs +++ b/examples/kerberos.rs @@ -13,6 +13,7 @@ use sspi::{Kerberos, SspiImpl}; use std::error::Error; use tracing::debug; use tracing::info; + const TARGET_NAME: &'static str = "HTTP/your-server-name.your.domain"; fn main() -> Result<(), Box> { From ac8805b46a000c910bc0fc279cd300ea7923976e Mon Sep 17 00:00:00 2001 From: "irvingouj@Devolutions" <139169536+irvingoujAtDevolution@users.noreply.github.com> Date: Sat, 7 Oct 2023 12:14:26 -0400 Subject: [PATCH 07/12] Update examples/kerberos.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Benoît Cortier --- examples/kerberos.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/kerberos.rs b/examples/kerberos.rs index 1e1731cc..ee570210 100644 --- a/examples/kerberos.rs +++ b/examples/kerberos.rs @@ -20,7 +20,7 @@ fn main() -> Result<(), Box> { tracing_subscriber::fmt() .with_max_level(tracing::Level::DEBUG) // Adjust level as needed .init(); - let kdc_url = "kdc.your.domain:88".to_string(); + let kdc_url = std::env::var("SSPI_KDC_URL").expect("a KDC URL set in SSPI_KDC_URL"); let client_hostname = whoami::hostname(); let kerberos_config = KerberosConfig::new(&kdc_url, client_hostname.clone()); let mut kerberos = Kerberos::new_client_from_config(kerberos_config).unwrap(); From 8d9fab673bfc2059c328678125a6a31d184758b1 Mon Sep 17 00:00:00 2001 From: "irvingouj@Devolutions" <139169536+irvingoujAtDevolution@users.noreply.github.com> Date: Sat, 7 Oct 2023 12:21:01 -0400 Subject: [PATCH 08/12] Update src/kerberos/mod.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Benoît Cortier --- src/kerberos/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kerberos/mod.rs b/src/kerberos/mod.rs index 27358047..8069fadf 100644 --- a/src/kerberos/mod.rs +++ b/src/kerberos/mod.rs @@ -751,7 +751,7 @@ impl<'a> Kerberos { let (encryption_type, salt) = extract_encryption_params_from_as_rep(&as_rep)?; - let encryption_type = CipherSuite::try_from(encryption_type as usize)?; + let encryption_type = CipherSuite::try_from(usize::from(encryption_type))?; self.encryption_params.encryption_type = Some(encryption_type); From 334dfec9778db17c6f3a77922fb2fd88b46aba40 Mon Sep 17 00:00:00 2001 From: irving ou Date: Sat, 7 Oct 2023 13:34:29 -0400 Subject: [PATCH 09/12] fix review issues --- .github/workflows/ci.yml | 2 +- Cargo.lock | 1 - Cargo.toml | 37 ++----- examples/kerberos.rs | 217 +++++++++++++++++++++------------------ 4 files changed, 127 insertions(+), 130 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e64b055..ef04de02 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,7 +77,7 @@ jobs: - uses: actions/checkout@v3 - name: Test - run: cargo test --manifest-path ${{ matrix.manifest }} ${{ matrix.additional-args }} --test * + run: cargo test --manifest-path ${{ matrix.manifest }} ${{ matrix.additional-args }} wasm: name: WASM target diff --git a/Cargo.lock b/Cargo.lock index 661f1ce6..468e12c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1907,7 +1907,6 @@ dependencies = [ "time", "tokio", "tracing", - "tracing-log", "tracing-subscriber", "trust-dns-resolver", "url", diff --git a/Cargo.toml b/Cargo.toml index af505a3a..33119fc7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,11 @@ authors = ["Devolutions Inc. "] description = "A Rust implementation of the Security Support Provider Interface (SSPI) API" keywords = ["ntlm", "auth", "sspi", "windows", "kerberos"] +[[example]] +name = "kerberos" +path = "examples/kerberos.rs" +features = "network_client" + [workspace] members = ["ffi", "ffi/symbol-rename-macro"] exclude = ["tools/sspi-ffi-attacker", "tools/wasm-testcompile"] @@ -41,11 +46,7 @@ lazy_static = "1.4" serde = "1" serde_derive = "1" url = "2.4" -reqwest = { version = "0.11", features = [ - "blocking", - "rustls-tls", - "rustls-tls-native-roots", -], optional = true, default-features = false } +reqwest = { version = "0.11", features = ["blocking", "rustls-tls", "rustls-tls-native-roots"], optional = true, default-features = false } picky = { version = "7.0.0-rc.8", default-features = false } picky-krb = "0.8" @@ -59,9 +60,7 @@ trust-dns-resolver = { version = "0.23", optional = true } portpicker = { version = "0.1", optional = true } num-bigint-dig = "0.8" tracing = "0.1" -rustls = { version = "0.21", features = [ - "dangerous_configuration", -], optional = true } +rustls = { version = "0.21", features = ["dangerous_configuration"], optional = true } zeroize = { version = "1.6", features = ["zeroize_derive"] } tokio = { version = "1.32", features = ["time", "rt"], optional = true } pcsc = { version = "2.8", optional = true } @@ -69,22 +68,9 @@ async-recursion = "1.0.5" [target.'cfg(windows)'.dependencies] winreg = "0.51" -winapi = { version = "0.3", features = [ - "std", - "sspi", - "rpcdce", - "impl-default", - "timezoneapi", - "wincrypt", -] } -windows = { version = "0.51", features = [ - "Win32_Foundation", - "Win32_NetworkManagement_Dns", -] } -windows-sys = { version = "0.48", features = [ - "Win32_Security_Cryptography", - "Win32_Foundation", -] } +winapi = { version = "0.3", features = ["std", "sspi", "rpcdce", "impl-default", "timezoneapi", "wincrypt"] } +windows = { version = "0.51", features = ["Win32_Foundation", "Win32_NetworkManagement_Dns"] } +windows-sys = { version = "0.48", features = ["Win32_Security_Cryptography", "Win32_Foundation"] } [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] async-dnssd = "0.5" @@ -97,5 +83,4 @@ whoami = "1.4" picky = { version = "7.0.0-rc.8", features = ["x509"] } base64 = "0.21.4" hyper = "0.14.27" -tracing-log = "0.1.3" -tracing-subscriber = "0.3.17" +tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } diff --git a/examples/kerberos.rs b/examples/kerberos.rs index ee570210..499c1bd4 100644 --- a/examples/kerberos.rs +++ b/examples/kerberos.rs @@ -1,22 +1,8 @@ -use base64::Engine; -use hyper::header::{ - ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, AUTHORIZATION, CONNECTION, CONTENT_LENGTH, HOST, USER_AGENT, -}; -use hyper::StatusCode; -use sspi::builders::EmptyInitializeSecurityContext; -use sspi::{ - AcquireCredentialsHandleResult, ClientRequestFlags, CredentialsBuffers, DataRepresentation, - InitializeSecurityContextResult, KerberosConfig, Negotiate, SecurityBuffer, SecurityBufferType, SecurityStatus, - Sspi, -}; -use sspi::{Kerberos, SspiImpl}; -use std::error::Error; -use tracing::debug; -use tracing::info; - -const TARGET_NAME: &'static str = "HTTP/your-server-name.your.domain"; - -fn main() -> Result<(), Box> { +#[cfg(feature = "network_client")] +fn main() -> Result<(), Box> { + use hyper::StatusCode; + use sspi::{Kerberos, KerberosConfig, SecurityBuffer, SecurityBufferType, SecurityStatus}; + tracing_subscriber::fmt() .with_max_level(tracing::Level::DEBUG) // Adjust level as needed .init(); @@ -25,13 +11,13 @@ fn main() -> Result<(), Box> { let kerberos_config = KerberosConfig::new(&kdc_url, client_hostname.clone()); let mut kerberos = Kerberos::new_client_from_config(kerberos_config).unwrap(); - let mut acq_creds_handle_result = get_cred_handle(&mut kerberos); + let mut acq_creds_handle_result = kerberos_example::get_cred_handle(&mut kerberos); let mut input = vec![SecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; let mut output = vec![SecurityBuffer::new(Vec::new(), SecurityBufferType::Token)]; loop { - match step( + match kerberos_example::step( &mut kerberos, &mut acq_creds_handle_result.credentials_handle, &mut input, @@ -39,9 +25,9 @@ fn main() -> Result<(), Box> { ) { Err(e) => panic!("error steping {:?}", e), Ok(result) => { - let status_code = process_authentication(&mut input, &mut output)?; - if status_code == StatusCode::OK { - info!("connection authenticated"); + let status_code = kerberos_example::process_authentication(&mut input, &mut output)?; + if status_code == hyper::StatusCode::OK { + tracing::info!("connection authenticated"); break Ok(()); } @@ -53,89 +39,116 @@ fn main() -> Result<(), Box> { } } -fn get_cred_handle(kerberos: &mut Kerberos) -> AcquireCredentialsHandleResult> { - let username = "user@your.domain".to_string(); - let password = "user's_password".to_string(); - let identity = sspi::AuthIdentity { - username, - password: password.into(), - domain: None, +#[cfg(feature = "network_client")] +mod kerberos_example { + use base64::Engine; + use hyper::header::{ + ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, AUTHORIZATION, CONNECTION, CONTENT_LENGTH, HOST, USER_AGENT, }; - let acq_creds_handle_result = kerberos - .acquire_credentials_handle() - .with_credential_use(sspi::CredentialUse::Outbound) - .with_auth_data(&identity.into()) - .execute() - .expect("AcquireCredentialsHandle resulted in error"); - acq_creds_handle_result -} + use hyper::StatusCode; + use sspi::builders::EmptyInitializeSecurityContext; + use sspi::{ + AcquireCredentialsHandleResult, ClientRequestFlags, CredentialsBuffers, DataRepresentation, + InitializeSecurityContextResult, Negotiate, SecurityBuffer, Sspi, + }; + use sspi::{Kerberos, SspiImpl}; + use std::error::Error; + use tracing::debug; -fn process_authentication( - input: &mut Vec, - output: &mut Vec, -) -> Result> { - let output_token_in_binary = &output[0].buffer; - let base_64_token = base64::engine::general_purpose::STANDARD.encode(output_token_in_binary); - let server_result = send_http(base_64_token)?; - debug!("server responde = {:?}", server_result); - let www_authenticate = server_result - .headers() - .get("www-authenticate") - .ok_or("www-authentication header not found")?; - let server_token = www_authenticate.to_str().unwrap().replace("Negotiate ", ""); - if server_token.len() <= 5 { - panic!("server token not found"); + const TARGET_NAME: &'static str = "HTTP/your-server-name.your.domain"; + + pub(crate) fn get_cred_handle( + kerberos: &mut Kerberos, + ) -> AcquireCredentialsHandleResult> { + let username = "user@your.domain".to_string(); + let password = "user's_password".to_string(); + let identity = sspi::AuthIdentity { + username, + password: password.into(), + domain: None, + }; + let acq_creds_handle_result = kerberos + .acquire_credentials_handle() + .with_credential_use(sspi::CredentialUse::Outbound) + .with_auth_data(&identity.into()) + .execute() + .expect("AcquireCredentialsHandle resulted in error"); + acq_creds_handle_result } - let decoded_new_token = base64::engine::general_purpose::STANDARD.decode(server_token).unwrap(); - clear(input); - clear(output); - input[0].buffer = decoded_new_token; - Ok(server_result.status()) -} + pub(crate) fn process_authentication( + input: &mut Vec, + output: &mut Vec, + ) -> Result> { + let output_token_in_binary = &output[0].buffer; + let base_64_token = base64::engine::general_purpose::STANDARD.encode(output_token_in_binary); + let server_result = send_http(base_64_token)?; + debug!("server responde = {:?}", server_result); + let www_authenticate = server_result + .headers() + .get("www-authenticate") + .ok_or("www-authentication header not found")?; + let server_token = www_authenticate.to_str().unwrap().replace("Negotiate ", ""); + if server_token.len() <= 5 { + panic!("server token not found"); + } + let decoded_new_token = base64::engine::general_purpose::STANDARD.decode(server_token).unwrap(); + clear(input); + clear(output); + input[0].buffer = decoded_new_token; -fn clear(buf: &mut Vec) { - buf[0].buffer.clear(); -} + Ok(server_result.status()) + } -fn send_http(negotiate_token: String) -> Result> { - let client = reqwest::blocking::Client::new(); - let resp = client - .post("http://your-server-name.your.domain:5985/wsman") - .header(AUTHORIZATION, format!("Negotiate {}", negotiate_token)) - .header(HOST, "our-server-name.your.domain:5985") - .header(CONNECTION, "keep-alive") - .header(CONTENT_LENGTH, "0") - .header( - USER_AGENT, - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0", - ) - .header(ACCEPT, "*/*") - .header(ACCEPT_ENCODING, "gzip, deflate") - .header(ACCEPT_LANGUAGE, "en-US,en;q=0.9") - .send()?; - - Ok(resp) -} + pub(crate) fn clear(buf: &mut Vec) { + buf[0].buffer.clear(); + } + + pub(crate) fn send_http( + negotiate_token: String, + ) -> Result> { + let client = reqwest::blocking::Client::new(); + let resp = client + .post("http://your-server-name.your.domain:5985/wsman") + .header(AUTHORIZATION, format!("Negotiate {}", negotiate_token)) + .header(HOST, "our-server-name.your.domain:5985") + .header(CONNECTION, "keep-alive") + .header(CONTENT_LENGTH, "0") + .header( + USER_AGENT, + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0", + ) + .header(ACCEPT, "*/*") + .header(ACCEPT_ENCODING, "gzip, deflate") + .header(ACCEPT_LANGUAGE, "en-US,en;q=0.9") + .send()?; + + Ok(resp) + } -fn step( - kerberos: &mut Kerberos, - credential_handle: &mut ::CredentialsHandle, - input_buffer: &mut Vec, - output_buffer: &mut Vec, -) -> Result> { - output_buffer[0].buffer.clear(); - let mut builder = EmptyInitializeSecurityContext::<::CredentialsHandle>::new() - .with_credentials_handle(credential_handle) - .with_context_requirements(ClientRequestFlags::MUTUAL_AUTH) - .with_target_data_representation(DataRepresentation::Native) - .with_target_name(TARGET_NAME) - .with_input(input_buffer) - .with_output(output_buffer); - - let result = kerberos - .initialize_security_context_impl(&mut builder) - .resolve_with_default_network_client()?; - - Ok(result) + pub(crate) fn step( + kerberos: &mut Kerberos, + credential_handle: &mut ::CredentialsHandle, + input_buffer: &mut Vec, + output_buffer: &mut Vec, + ) -> Result> { + output_buffer[0].buffer.clear(); + let mut builder = EmptyInitializeSecurityContext::<::CredentialsHandle>::new() + .with_credentials_handle(credential_handle) + .with_context_requirements(ClientRequestFlags::MUTUAL_AUTH) + .with_target_data_representation(DataRepresentation::Native) + .with_target_name(TARGET_NAME) + .with_input(input_buffer) + .with_output(output_buffer); + + let result = kerberos + .initialize_security_context_impl(&mut builder) + .resolve_with_default_network_client()?; + + Ok(result) + } } + +// passes CI test compile +#[cfg(not(feature = "network_client"))] +fn main() {} From 6515f193442d89989903ff1796a4134be0ca6eb8 Mon Sep 17 00:00:00 2001 From: irving ou Date: Sat, 7 Oct 2023 13:39:54 -0400 Subject: [PATCH 10/12] cargo better format --- Cargo.lock | 3 --- Cargo.toml | 21 ++++++++++----------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 468e12c3..411aa519 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1874,14 +1874,12 @@ version = "0.10.1" dependencies = [ "async-dnssd", "async-recursion", - "base64", "bitflags 2.4.0", "byteorder", "cfg-if", "crypto-mac", "futures", "hmac", - "hyper", "lazy_static", "md-5", "md4", @@ -1907,7 +1905,6 @@ dependencies = [ "time", "tokio", "tracing", - "tracing-subscriber", "trust-dns-resolver", "url", "uuid", diff --git a/Cargo.toml b/Cargo.toml index 33119fc7..ff7ebb76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,14 +10,15 @@ authors = ["Devolutions Inc. "] description = "A Rust implementation of the Security Support Provider Interface (SSPI) API" keywords = ["ntlm", "auth", "sspi", "windows", "kerberos"] -[[example]] -name = "kerberos" -path = "examples/kerberos.rs" -features = "network_client" - [workspace] -members = ["ffi", "ffi/symbol-rename-macro"] -exclude = ["tools/sspi-ffi-attacker", "tools/wasm-testcompile"] +members = [ + "ffi", + "ffi/symbol-rename-macro", +] +exclude = [ + "tools/sspi-ffi-attacker", + "tools/wasm-testcompile", +] [features] default = [] @@ -69,7 +70,7 @@ async-recursion = "1.0.5" [target.'cfg(windows)'.dependencies] winreg = "0.51" winapi = { version = "0.3", features = ["std", "sspi", "rpcdce", "impl-default", "timezoneapi", "wincrypt"] } -windows = { version = "0.51", features = ["Win32_Foundation", "Win32_NetworkManagement_Dns"] } +windows = { version = "0.51", features = [ "Win32_Foundation", "Win32_NetworkManagement_Dns"] } windows-sys = { version = "0.48", features = ["Win32_Security_Cryptography", "Win32_Foundation"] } [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] @@ -81,6 +82,4 @@ tokio = { version = "1.32", features = ["time", "rt"] } static_assertions = "1" whoami = "1.4" picky = { version = "7.0.0-rc.8", features = ["x509"] } -base64 = "0.21.4" -hyper = "0.14.27" -tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } + From d17813d5ca80a6d3b6a87ba4795c677d80a2392c Mon Sep 17 00:00:00 2001 From: irving ou Date: Sat, 7 Oct 2023 13:40:35 -0400 Subject: [PATCH 11/12] update cargo --- Cargo.lock | 3 +++ Cargo.toml | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 411aa519..468e12c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1874,12 +1874,14 @@ version = "0.10.1" dependencies = [ "async-dnssd", "async-recursion", + "base64", "bitflags 2.4.0", "byteorder", "cfg-if", "crypto-mac", "futures", "hmac", + "hyper", "lazy_static", "md-5", "md4", @@ -1905,6 +1907,7 @@ dependencies = [ "time", "tokio", "tracing", + "tracing-subscriber", "trust-dns-resolver", "url", "uuid", diff --git a/Cargo.toml b/Cargo.toml index ff7ebb76..1a2524b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,4 +82,6 @@ tokio = { version = "1.32", features = ["time", "rt"] } static_assertions = "1" whoami = "1.4" picky = { version = "7.0.0-rc.8", features = ["x509"] } - +base64 = "0.21.4" +hyper = "0.14.27" +tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } From 75b45429b8d77db6d8dc1b3e86749120802edddd Mon Sep 17 00:00:00 2001 From: irving ou Date: Thu, 12 Oct 2023 16:54:09 -0400 Subject: [PATCH 12/12] a few more fix and experiments --- src/kerberos/mod.rs | 305 +++++++++++++++++------------- src/kerberos/server/extractors.rs | 1 + 2 files changed, 178 insertions(+), 128 deletions(-) diff --git a/src/kerberos/mod.rs b/src/kerberos/mod.rs index 8069fadf..f621d6ef 100644 --- a/src/kerberos/mod.rs +++ b/src/kerberos/mod.rs @@ -319,6 +319,7 @@ impl Sspi for Kerberos { let seq_number = self.next_seq_number(); let key = get_encryption_key(&self.encryption_params)?; + info!("encryption key is {:?}", key); let key_usage = self.encryption_params.sspi_encrypt_key_usage; @@ -696,165 +697,213 @@ impl<'a> Kerberos { yield_point: &mut YieldPointLocal, builder: &'a mut crate::builders::FilledInitializeSecurityContext<'_, ::CredentialsHandle>, ) -> Result { - let credentials = builder - .credentials_handle - .as_ref() - .unwrap() - .as_ref() - .ok_or_else(|| Error::new(ErrorKind::WrongCredentialHandle, "No credentials provided"))?; + let status = match self.state { + KerberosState::Negotiate => { + let credentials = builder + .credentials_handle + .as_ref() + .unwrap() + .as_ref() + .ok_or_else(|| Error::new(ErrorKind::WrongCredentialHandle, "No credentials provided"))?; - let (username, password, realm, cname_type) = match credentials { - CredentialsBuffers::AuthIdentity(auth_identity) => { - let username = utf16_bytes_to_utf8_string(&auth_identity.user); - let domain = utf16_bytes_to_utf8_string(&auth_identity.domain); - let password = utf16_bytes_to_utf8_string(auth_identity.password.as_ref()); + let (username, password, realm, cname_type) = match credentials { + CredentialsBuffers::AuthIdentity(auth_identity) => { + let username = utf16_bytes_to_utf8_string(&auth_identity.user); + let domain = utf16_bytes_to_utf8_string(&auth_identity.domain); + let password = utf16_bytes_to_utf8_string(auth_identity.password.as_ref()); - let realm = get_client_principal_realm(&username, &domain); - let cname_type = get_client_principal_name_type(&username, &domain); + let realm = get_client_principal_realm(&username, &domain); + let cname_type = get_client_principal_name_type(&username, &domain); - (username, password, realm, cname_type) - } - }; - self.realm = Some(realm.clone()); + (username, password, realm, cname_type) + } + }; + self.realm = Some(realm.clone()); - let options = GenerateAsReqOptions { - realm: &realm, - username: &username, - cname_type, - snames: &[TGT_SERVICE_NAME, &realm], - // 4 = size of u32 - nonce: &OsRng.gen::<[u8; 4]>(), - hostname: &unwrap_hostname(self.config.hostname.as_deref())?, - context_requirements: builder.context_requirements, - }; - let kdc_req_body = generate_as_req_kdc_body(&options)?; + let options = GenerateAsReqOptions { + realm: &realm, + username: &username, + cname_type, + snames: &[TGT_SERVICE_NAME, &realm], + // 4 = size of u32 + nonce: &OsRng.gen::<[u8; 4]>(), + hostname: &unwrap_hostname(self.config.hostname.as_deref())?, + context_requirements: builder.context_requirements, + }; + let kdc_req_body = generate_as_req_kdc_body(&options)?; - let pa_data_options = match credentials { - CredentialsBuffers::AuthIdentity(auth_identity) => { - let domain = utf16_bytes_to_utf8_string(&auth_identity.domain); - let salt = format!("{}{}", domain, username); - - AsReqPaDataOptions::AuthIdentity(GenerateAsPaDataOptions { - password: &password, - salt: salt.as_bytes().to_vec(), - enc_params: self.encryption_params.clone(), - with_pre_auth: false, - }) - } - }; + let pa_data_options = match credentials { + CredentialsBuffers::AuthIdentity(auth_identity) => { + let domain = utf16_bytes_to_utf8_string(&auth_identity.domain); + let salt = format!("{}{}", domain, username); - let as_rep = self.as_exchange(yield_point, &kdc_req_body, pa_data_options).await?; + AsReqPaDataOptions::AuthIdentity(GenerateAsPaDataOptions { + password: &password, + salt: salt.as_bytes().to_vec(), + enc_params: self.encryption_params.clone(), + with_pre_auth: false, + }) + } + }; - info!("AS exchange finished successfully."); + let as_rep = self.as_exchange(yield_point, &kdc_req_body, pa_data_options).await?; - self.realm = Some(as_rep.0.crealm.0.to_string()); + info!("AS exchange finished successfully."); - let (encryption_type, salt) = extract_encryption_params_from_as_rep(&as_rep)?; + self.realm = Some(as_rep.0.crealm.0.to_string()); - let encryption_type = CipherSuite::try_from(usize::from(encryption_type))?; + let (encryption_type, salt) = extract_encryption_params_from_as_rep(&as_rep)?; - self.encryption_params.encryption_type = Some(encryption_type); + let encryption_type = CipherSuite::try_from(usize::from(encryption_type))?; - let mut authenticator = generate_authenticator(GenerateAuthenticatorOptions { - kdc_rep: &as_rep.0, - seq_num: Some(OsRng.gen::()), - sub_key: None, - checksum: None, - channel_bindings: self.channel_bindings.as_ref(), - extensions: Vec::new(), - })?; + self.encryption_params.encryption_type = Some(encryption_type); - let mut session_key_extractor = match credentials { - CredentialsBuffers::AuthIdentity(_) => AsRepSessionKeyExtractor::AuthIdentity { - salt: &salt, - password: &password, - enc_params: &mut self.encryption_params, - }, - }; - let session_key_1 = session_key_extractor.session_key(&as_rep)?; + let mut authenticator = generate_authenticator(GenerateAuthenticatorOptions { + kdc_rep: &as_rep.0, + seq_num: Some(OsRng.gen::()), + sub_key: None, + checksum: None, + channel_bindings: self.channel_bindings.as_ref(), + extensions: Vec::new(), + })?; - let service_principal = builder.target_name.ok_or_else(|| { - Error::new( - ErrorKind::NoCredentials, - "Service target name (service principal name) is not provided", - ) - })?; + let mut session_key_extractor = match credentials { + CredentialsBuffers::AuthIdentity(_) => AsRepSessionKeyExtractor::AuthIdentity { + salt: &salt, + password: &password, + enc_params: &mut self.encryption_params, + }, + }; + let session_key_1 = session_key_extractor.session_key(&as_rep)?; - let tgs_req: picky_asn1_der::application_tag::ApplicationTag = - generate_tgs_req(GenerateTgsReqOptions { - realm: &as_rep.0.crealm.0.to_string(), - service_principal, - session_key: &session_key_1, - ticket: as_rep.0.ticket.0, - authenticator: &mut authenticator, - additional_tickets: None.map(|ticket| vec![ticket]), - enc_params: &self.encryption_params, - context_requirements: builder.context_requirements, - })?; + let service_principal = builder.target_name.ok_or_else(|| { + Error::new( + ErrorKind::NoCredentials, + "Service target name (service principal name) is not provided", + ) + })?; - let response = self.send(yield_point, &serialize_message(&tgs_req)?).await?; + let tgs_req: picky_asn1_der::application_tag::ApplicationTag = + generate_tgs_req(GenerateTgsReqOptions { + realm: &as_rep.0.crealm.0.to_string(), + service_principal, + session_key: &session_key_1, + ticket: as_rep.0.ticket.0, + authenticator: &mut authenticator, + additional_tickets: None.map(|ticket| vec![ticket]), + enc_params: &self.encryption_params, + context_requirements: builder.context_requirements, + })?; - // first 4 bytes are message len. skipping them - let mut d = picky_asn1_der::Deserializer::new_from_bytes(&response[4..]); - let tgs_rep: KrbResult = KrbResult::deserialize(&mut d)?; - let tgs_rep = tgs_rep?; + let response = self.send(yield_point, &serialize_message(&tgs_req)?).await?; - info!("TGS exchange finished successfully"); + // first 4 bytes are message len. skipping them + let mut d = picky_asn1_der::Deserializer::new_from_bytes(&response[4..]); + let tgs_rep: KrbResult = KrbResult::deserialize(&mut d)?; + let tgs_rep = tgs_rep?; - let session_key_2 = extract_session_key_from_tgs_rep(&tgs_rep, &session_key_1, &self.encryption_params)?; + info!("TGS exchange finished successfully"); - self.encryption_params.session_key = Some(session_key_2); + let session_key_2 = + extract_session_key_from_tgs_rep(&tgs_rep, &session_key_1, &self.encryption_params)?; - let seq_num = self.next_seq_number(); + self.encryption_params.session_key = Some(session_key_2); - let enc_type = self - .encryption_params - .encryption_type - .as_ref() - .unwrap_or(&DEFAULT_ENCRYPTION_TYPE); - let authenticator_sub_key = generate_random_symmetric_key(enc_type, &mut OsRng); + let seq_num = self.next_seq_number(); - let authenticator_options = GenerateAuthenticatorOptions { - kdc_rep: &tgs_rep.0, - seq_num: Some(seq_num), - sub_key: Some(EncKey { - key_type: enc_type.clone(), - key_value: authenticator_sub_key, - }), - checksum: Some(ChecksumOptions { - checksum_type: AUTHENTICATOR_CHECKSUM_TYPE.to_vec(), - checksum_value: AUTHENTICATOR_DEFAULT_CHECKSUM.to_vec(), - }), - channel_bindings: self.channel_bindings.as_ref(), - extensions: Vec::new(), - }; + let enc_type = self + .encryption_params + .encryption_type + .as_ref() + .unwrap_or(&DEFAULT_ENCRYPTION_TYPE); + let authenticator_sub_key = generate_random_symmetric_key(enc_type, &mut OsRng); - let authenticator = generate_authenticator(authenticator_options)?; - let encoded_auth = picky_asn1_der::to_vec(&authenticator)?; - info!(encoded_ap_req_authenticator = ?encoded_auth); + let authenticator_options = GenerateAuthenticatorOptions { + kdc_rep: &tgs_rep.0, + seq_num: Some(seq_num), + sub_key: Some(EncKey { + key_type: enc_type.clone(), + key_value: authenticator_sub_key, + }), + checksum: Some(ChecksumOptions { + checksum_type: AUTHENTICATOR_CHECKSUM_TYPE.to_vec(), + checksum_value: AUTHENTICATOR_DEFAULT_CHECKSUM.to_vec(), + }), + channel_bindings: self.channel_bindings.as_ref(), + extensions: Vec::new(), + }; - let mech_id = oids::krb5(); + let authenticator = generate_authenticator(authenticator_options)?; + let encoded_auth = picky_asn1_der::to_vec(&authenticator)?; + info!(encoded_ap_req_authenticator = ?encoded_auth); - let context_requirements = builder.context_requirements; + let mech_id = oids::krb5(); - let ap_req = generate_ap_req( - tgs_rep.0.ticket.0, - self.encryption_params.session_key.as_ref().unwrap(), - &authenticator, - &self.encryption_params, - context_requirements.into(), - )?; + let context_requirements = builder.context_requirements; + + let ap_req = generate_ap_req( + tgs_rep.0.ticket.0, + self.encryption_params.session_key.as_ref().unwrap(), + &authenticator, + &self.encryption_params, + context_requirements.into(), + )?; + + let generated_neg_ap_req = generate_neg_ap_req_plain(ap_req, mech_id)?; + let encoded_neg_ap_req = picky_asn1_der::to_vec(&generated_neg_ap_req)?; + let output_token = SecurityBuffer::find_buffer_mut(builder.output, SecurityBufferType::Token)?; + // TODO: the out put token is already a certificate instead of a contentInfo here, need to figure out why + output_token.buffer.write_all(&encoded_neg_ap_req)?; + + self.state = KerberosState::Final; + SecurityStatus::ContinueNeeded + } + KerberosState::Final => { + let input = builder + .input + .as_ref() + .ok_or_else(|| Error::new(ErrorKind::InvalidToken, "Input buffers must be specified"))?; + let input_token = SecurityBuffer::find_buffer(input, SecurityBufferType::Token)?; + + let neg_token_targ: NegTokenTarg1 = picky_asn1_der::from_bytes(&input_token.buffer)?; + if let Some(ref token) = neg_token_targ.0.mech_list_mic.0 { + validate_mic_token(&token.0 .0, ACCEPTOR_SIGN, &self.encryption_params)?; + } + + let ap_rep = extract_ap_rep_from_neg_token_targ(&neg_token_targ)?; + + let sub_session_key = extract_sub_session_key_from_ap_rep( + &ap_rep, + self.encryption_params.session_key.as_ref().unwrap(), + &self.encryption_params, + )?; + + self.encryption_params.sub_session_key = Some(sub_session_key); - let generated_neg_ap_req = generate_neg_ap_req_plain(ap_req, mech_id)?; - let encoded_neg_ap_req = picky_asn1_der::to_vec(&generated_neg_ap_req)?; - let output_token = SecurityBuffer::find_buffer_mut(builder.output, SecurityBufferType::Token)?; - // TODO: the out put token is already a certificate instead of a contentInfo here, need to figure out why - output_token.buffer.write_all(&encoded_neg_ap_req)?; + // let neg_token_targ = generate_final_neg_token_targ(Some(generate_initiator_raw( + // picky_asn1_der::to_vec(&get_mech_list())?, + // self.seq_number as u64, + // self.encryption_params.sub_session_key.as_ref().unwrap(), + // )?)); - self.state = KerberosState::ApExchange; + // let encoded_final_neg_token_targ = picky_asn1_der::to_vec(&neg_token_targ)?; + // let output_token = SecurityBuffer::find_buffer_mut(builder.output, SecurityBufferType::Token)?; + // output_token.buffer.write_all(&encoded_final_neg_token_targ)?; + + self.state = KerberosState::Final; + + SecurityStatus::Ok + } + _ => { + return Err(Error::new( + ErrorKind::OutOfSequence, + format!("Got wrong Kerberos state: {:?}", self.state), + )) + } + }; Ok(InitializeSecurityContextResult { - status: SecurityStatus::Ok, + status: status, flags: ClientResponseFlags::empty(), expiry: None, }) diff --git a/src/kerberos/server/extractors.rs b/src/kerberos/server/extractors.rs index 3044f800..43667300 100644 --- a/src/kerberos/server/extractors.rs +++ b/src/kerberos/server/extractors.rs @@ -51,6 +51,7 @@ pub fn extract_sub_session_key_from_ap_rep( })?; let ap_rep_enc_part: EncApRepPart = picky_asn1_der::from_bytes(&res)?; + info!("{:?}", &ap_rep_enc_part); Ok(ap_rep_enc_part .0