Skip to content

Commit a738671

Browse files
feat(kerberos): add support for IAKerb extension for Kerberos client (#751)
This PR adds support for the IAKERB extension according to the [IAKERB specification](https://datatracker.ietf.org/doc/html/draft-ietf-kitten-iakerb-03). The main changes are focused on the `Kerberos` state machine. Previously, the `Preauthentication` state handled the entire KDC message exchange withing a single async `intialize_security_context` call. This approach does not work with IAKERB, where KDC messages are exchanged via a server that acts as a proxy. The `Preauthentication` state has therefore been split into multiple states, allowing the state machine to save `Kerberos` state between KDC message exchanges. Additionaly, the `AS` and `TGS` exchanges have been extracted into separate state machines. These new state machines are KDC transport-agnostic and can be used with both external KDC and an IAKERB proxy. ## What is IAKERB IAKERB extends Kerberos to support scenarios where the client cannot directly access the KDC. Instead, KDC messages are encapsulated in GSS-API tokens and exchanged through an IAKERB proxy. The server forwards these messages to the LocalKDC, allowing the client to obtain the required Kerberos tickets without direct network access to the KDC. Microsoft recently introduced IAKERB support in Windows Insider builds as part of its effort to reduce NTLM dependency: https://techcommunity.microsoft.com/blog/windows-itpro-blog/reducing-ntlm-dependency-iakerb-and-localkdc-in-windows-insider-preview/4524615. ## Related PRs * picky-rs: Devolutions/picky-rs#531 * IronRDP: Devolutions/IronRDP#1987
1 parent 08d8622 commit a738671

38 files changed

Lines changed: 2151 additions & 725 deletions

‎Cargo.lock‎

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ rand = "0.10"
115115
rand_core = "0.10"
116116
getrandom = "0.3"
117117
cfg-if = "1"
118-
picky = { version = "=7.0.0-rc.26", default-features = false }
118+
picky = { version = "=7.0.0-rc.27", default-features = false }
119119
sha1 = { version = "0.11", default-features = false }
120120
sha2 = "0.11"
121121
num-derive = "0.5"
@@ -180,7 +180,7 @@ pkcs1 = "=0.8.0-rc.4"
180180
base64 = "0.23"
181181
static_assertions = "1"
182182
whoami = "2.1"
183-
picky = { version = "=7.0.0-rc.26", default-features = false, features = ["x509"] }
183+
picky = { version = "=7.0.0-rc.27", default-features = false, features = ["x509"] }
184184
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
185185
proptest = "1.6"
186186
cfg-if = "1"

‎crates/dpapi-web/src/lib.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use std::rc::Rc;
1616

1717
use anyhow::Context;
1818
use dpapi::{CryptProtectSecretArgs, CryptUnprotectSecretArgs};
19-
use sspi::KerberosConfig;
19+
use sspi::{KdcResolution, KerberosConfig};
2020
use url::Url;
2121
use wasm_bindgen::prelude::*;
2222

@@ -169,7 +169,7 @@ impl DpapiConfig {
169169
let kerberos_config = Url::parse(kdc_proxy_url.unwrap_or_default().as_str())
170170
.ok()
171171
.map(|url| KerberosConfig {
172-
kdc_url: Some(url),
172+
kdc_resolution: KdcResolution::KdcUrl(Some(url)),
173173
client_computer_name: computer_name.clone(),
174174
});
175175

‎crates/winscard/Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ name = "winscard"
1919
bitflags = "2.11"
2020
iso7816 = "0.1"
2121
iso7816-tlv = "0.4"
22-
picky = { version = "=7.0.0-rc.26", default-features = false, features = ["x509"] }
22+
picky = { version = "=7.0.0-rc.27", default-features = false, features = ["x509"] }
2323
picky-asn1-x509 = "0.15"
2424
tracing = { version = "0.1", default-features = false, features = ["attributes"] }
2525
time = { version = "0.3", default-features = false, features = [

‎examples/kerberos.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
2626
.with(EnvFilter::from_env("SSPI_LOG_LEVEL"))
2727
.init();
2828

29-
let kerberos_config = KerberosConfig::new(&kdc_url, hostname.clone());
29+
let kerberos_config = KerberosConfig::new_with_kdc_url(&kdc_url, hostname.clone());
3030
let mut kerberos = Kerberos::new_client_from_config(kerberos_config).unwrap();
3131

3232
let mut acq_creds_handle_result = get_cred_handle(&mut kerberos, username, password);

‎ffi/Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ sha1 = { version = "0.11", default-features = false }
2929
sha2 = "0.11"
3030
ffi-types = { workspace = true, features = ["sspi"] }
3131

32-
picky = { version = "=7.0.0-rc.26", default-features = false, features = ["x509"], optional = true }
32+
picky = { version = "=7.0.0-rc.27", default-features = false, features = ["x509"], optional = true }
3333
picky-asn1-der = "0.5"
3434
picky-asn1 = { version = "0.10", optional = true }
3535
picky-asn1-x509 = { version = "0.15", optional = true }

‎ffi/src/sspi/sec_handle.rs‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ use sspi::kerberos::config::KerberosConfig;
2020
use sspi::ntlm::NtlmConfig;
2121
use sspi::{
2222
CertContext, ClientRequestFlags, ConnectionInfo, Credentials, CredentialsBuffers, DataRepresentation, Error,
23-
ErrorKind, Kerberos, Negotiate, NegotiateConfig, Ntlm, PackageInfo, Result, Secret, Sspi, SspiImpl, StreamSizes,
24-
U16CString, Utf16String, Utf16StringExt, kerberos, negotiate, ntlm, pku2u,
23+
ErrorKind, KdcResolution, Kerberos, Negotiate, NegotiateConfig, Ntlm, PackageInfo, Result, Secret, Sspi, SspiImpl,
24+
StreamSizes, U16CString, Utf16String, Utf16StringExt, kerberos, negotiate, ntlm, pku2u,
2525
};
2626
#[cfg(target_os = "windows")]
2727
use windows::Win32::Security::Cryptography::{
@@ -438,7 +438,7 @@ fn create_negotiate_context(attributes: &CredentialsAttributes) -> Result<Negoti
438438
let client_computer_name = attributes.hostname()?;
439439

440440
let negotiate_config = if let Some(kdc_url) = attributes.kdc_url() {
441-
let kerberos_config = KerberosConfig::new(&kdc_url, client_computer_name.clone());
441+
let kerberos_config = KerberosConfig::new_with_kdc_url(&kdc_url, client_computer_name.clone());
442442

443443
NegotiateConfig::new(
444444
Box::new(kerberos_config),
@@ -505,14 +505,14 @@ pub(crate) unsafe fn p_ctxt_handle_to_sspi_context(
505505
let client_computer_name = attributes.hostname()?;
506506

507507
if let Some(kdc_url) = attributes.kdc_url() {
508-
SspiContext::Kerberos(Kerberos::new_client_from_config(KerberosConfig::new(
508+
SspiContext::Kerberos(Kerberos::new_client_from_config(KerberosConfig::new_with_kdc_url(
509509
&kdc_url,
510510
client_computer_name,
511511
))?)
512512
} else {
513513
let krb_config = KerberosConfig {
514514
client_computer_name,
515-
kdc_url: None,
515+
kdc_resolution: KdcResolution::KdcUrl(None),
516516
};
517517
SspiContext::Kerberos(Kerberos::new_client_from_config(krb_config)?)
518518
}
@@ -1778,9 +1778,9 @@ pub unsafe extern "system" fn ChangeAccountPasswordA(
17781778
SspiContext::Negotiate(try_execute!(Negotiate::new_client(negotiate_config)))
17791779
},
17801780
kerberos::PKG_NAME => {
1781-
let krb_config = KerberosConfig{
1781+
let krb_config = KerberosConfig {
17821782
client_computer_name: try_execute!(hostname()),
1783-
kdc_url:None
1783+
kdc_resolution: KdcResolution::KdcUrl(None),
17841784
};
17851785
SspiContext::Kerberos(try_execute!(Kerberos::new_client_from_config(
17861786
krb_config

‎src/auth_identity.rs‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,15 @@ impl CredentialsBuffers {
746746
_ => None,
747747
}
748748
}
749+
750+
pub fn extract_password(&self) -> Secret<String> {
751+
Secret::new(match self {
752+
CredentialsBuffers::AuthIdentity(auth_identity) => auth_identity.password.as_ref().as_ref().to_string(),
753+
#[cfg(feature = "scard")]
754+
CredentialsBuffers::SmartCard(smart_card) => smart_card.pin.as_ref().as_ref().to_string(),
755+
CredentialsBuffers::Keytab(_keytab) => String::new(),
756+
})
757+
}
749758
}
750759

751760
/// Generic enum that encapsulates credentials for any type of authentication

‎src/kerberos/client/as_exchange.rs‎

Lines changed: 0 additions & 112 deletions
This file was deleted.

‎src/kerberos/client/change_password.rs‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::kerberos::client::generators::{
1515
use crate::kerberos::client::principal::{get_client_principal_name_type, get_client_principal_realm};
1616
use crate::kerberos::pa_datas::AsReqPaDataOptions;
1717
use crate::kerberos::utils::serialize_message;
18-
use crate::kerberos::{CHANGE_PASSWORD_SERVICE_NAME, DEFAULT_ENCRYPTION_TYPE, KADMIN, client};
18+
use crate::kerberos::{CHANGE_PASSWORD_SERVICE_NAME, DEFAULT_ENCRYPTION_TYPE, KADMIN, server};
1919
use crate::utils::generate_random_symmetric_key;
2020
use crate::{ClientRequestFlags, Error, ErrorKind, Kerberos, Result};
2121

@@ -29,6 +29,13 @@ pub async fn change_password<'a>(
2929
yield_point: &mut YieldPointLocal,
3030
change_password: ChangePassword<'a>,
3131
) -> Result<()> {
32+
if client.is_iakerb() {
33+
return Err(Error::new(
34+
ErrorKind::UnsupportedFunction,
35+
"changing password is not supported with IAKerb.",
36+
));
37+
}
38+
3239
let username = &change_password.account_name;
3340
let domain = &change_password.domain_name;
3441
let password = &change_password.old_password;
@@ -59,7 +66,7 @@ pub async fn change_password<'a>(
5966
with_pre_auth: false,
6067
});
6168

62-
let as_rep = client::as_exchange(client, yield_point, &kdc_req_body, pa_data_options).await?;
69+
let as_rep = server::as_exchange::as_exchange(client, yield_point, &kdc_req_body, pa_data_options).await?;
6370

6471
debug!("AS exchange finished successfully.");
6572

0 commit comments

Comments
 (0)