Skip to content

feat: support for Kerberos cross realm referral#694

Merged
Richard Markiewicz (thenextman) merged 1 commit into
masterfrom
fix/kerberos-cross-realm-referral
Jun 24, 2026
Merged

feat: support for Kerberos cross realm referral#694
Richard Markiewicz (thenextman) merged 1 commit into
masterfrom
fix/kerberos-cross-realm-referral

Conversation

@thenextman

@thenextman Richard Markiewicz (thenextman) commented Jun 23, 2026

Copy link
Copy Markdown
Member

RDP/NLA via FreeRDP and sspi-rs failed when a parent-domain user (richard@rjm.local) authenticated to a host in a child domain (TERMSRV/WIN-UE7FOENEK0D.dev.rjm.local) with

InvalidToken: Asn1 error: "Expected Application number tag 15 but got: 30"

(Kerberos app tags: 15 = AP-REP, 30 = KRB-ERROR — i.e. we expected an AP-REP from the server but got a KRB-ERROR.)

Root cause

sspi-rs has no cross-realm referral chasing. A KDC only issues tickets for principals in its own realm; for a service in another realm it returns a referral TGT (sname = krbtgt/<NEXT_REALM>), and the client must re-send the TGS-REQ to that next realm. Instead, sspi-rs:

  1. Sent the TGS-REQ to the home KDC (RJM.LOCAL) → got back a referral TGT for krbtgt/DEV.RJM.LOCAL.
  2. Stuffed that referral TGT directly into the AP_REQ as if it were the service ticket.
  3. The target (WIN-UE7FOENEK0D) couldn't decrypt a ticket encrypted with the trust key; returned KRB-ERROR 41 (KRB_AP_ERR_MODIFIED).
  4. sspi-rs tried to parse that KRB-ERROR as an AP-REP ---> InvalidToken.

What we changed

Referral chasing loop in the TGS exchange (src/kerberos/client/mod.rs): after each TGS-REP, if the returned ticket sname is krbtgt/<NEXT_REALM>, re-issue the TGS-REQ for the same SPN to that realm using the referral TGT, chaining the session key and authenticator each hop, until the real service ticket comes back. Bounded to 10 hops with a no-progress guard.

Per-realm KDC routing (src/kerberos/mod.rs): split send into send/send_to and added send_for_realm. The pinned kdc_url (KDC proxy) stays authoritative for the home realm only; referral hops resolve through detect_kdc_url(realm).

Helper and tests: extracted referral_target_realm(sname) (testable predicate) with 5 unit tests.

Key decisions

Reuse the existing per-realm resolution chain rather than add new config. Referral hops route via the existing SSPI_KDC_URL_<REALM> env → krb5.conf → DNS SRV (_kerberos._tcp.) fallback. No new FFI/config plumbing; set one env var (or rely on DNS) and it works.

Pinned KDC = home realm only. A KDC proxy pinned to the home DC can't decrypt a krbtgt/<NEXT_REALM> referral ticket, so referral hops must reach the target realm's KDC.

For further discussion

(Because I'm trying to keep these changes focussed)

Windows DNS SRV limitations (src/dns.rs): the Windows path forces port :88 (ignores the SRV port) and reads only the first SRV record (no multi-DC failover).

Dotted env-var friction: SSPI_KDC_URL_DEV.RJM.LOCAL breaks PowerShell $env: parsing. Add underscore-normalized aliasing (SSPI_KDC_URL_DEV_RJM_LOCAL)?

U2U + referral interaction: additional_tickets is only carried on the first hop; edge case, likely fine, but worth a sanity check.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes Kerberos cross-realm authentication by adding referral chasing during the TGS exchange, ensuring the client follows krbtgt/<NEXT_REALM> referral TGTs until it obtains the actual service ticket for the requested SPN.

Changes:

  • Adds a referral-chasing loop in the Kerberos client TGS exchange to handle cross-realm referrals (bounded and guarded).
  • Introduces realm-aware KDC routing via send_for_realm, allowing referral hops to resolve the appropriate realm KDC rather than always using the pinned home-realm KDC.
  • Adds unit tests for referral ticket detection logic.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/kerberos/mod.rs Refactors KDC send logic into send_to and adds send_for_realm to support realm-specific KDC resolution for referral hops.
src/kerberos/client/mod.rs Implements the cross-realm referral chase loop during TGS exchange and adds tests for referral detection.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/kerberos/mod.rs
Comment on lines +183 to +196
async fn send_for_realm(&self, yield_point: &mut YieldPointLocal, realm: &str, data: &[u8]) -> Result<Vec<u8>> {
let kdc_url = if self.realm.as_deref() == Some(realm) {
self.kdc_url.clone().or_else(|| detect_kdc_url(realm))
} else {
detect_kdc_url(realm)
}
.ok_or_else(|| {
Error::new(
ErrorKind::NoAuthenticatingAuthority,
format!("No KDC server found for realm `{realm}`"),
)
})?;
self.send_to(yield_point, realm, kdc_url, data).await
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment thread src/kerberos/client/mod.rs
@CBenoit Benoît Cortier (CBenoit) changed the title Kerberos cross realm referral feat: support for Kerberos cross realm referral Jun 24, 2026
When the target service is in a different realm than the user (e.g. a
parent-domain user authenticating to a child-domain host), the home-realm
KDC returns a referral TGT (sname = krbtgt/<NEXT_REALM>) instead of the
service ticket. sspi-rs used that referral TGT directly in the AP_REQ, so
the target server could not decrypt it and NLA failed with
"Expected Application number tag 15 but got: 30" (AP-REP expected, got a
KRB-ERROR / KRB_AP_ERR_MODIFIED).

Loop the TGS exchange: after each TGS-REP, if the returned ticket sname is
krbtgt/<NEXT_REALM>, re-issue the TGS-REQ for the same SPN to that realm's
KDC using the referral TGT, until the real service ticket is returned
(bounded to 10 hops, with a no-progress guard).

Referral hops are routed via send_for_realm, which resolves the target
realm's KDC through SSPI_KDC_URL_<REALM> / krb5.conf / DNS SRV instead of
the pinned home-realm KDC (which cannot decrypt a krbtgt/<NEXT_REALM>
referral ticket).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@thenextman Richard Markiewicz (thenextman) force-pushed the fix/kerberos-cross-realm-referral branch from 59cb4c4 to af0be31 Compare June 24, 2026 12:44
@thenextman Richard Markiewicz (thenextman) marked this pull request as ready for review June 24, 2026 12:44
@thenextman Richard Markiewicz (thenextman) merged commit 7973654 into master Jun 24, 2026
61 checks passed
@thenextman Richard Markiewicz (thenextman) deleted the fix/kerberos-cross-realm-referral branch June 24, 2026 12:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants