feat: support for Kerberos cross realm referral#694
Merged
Richard Markiewicz (thenextman) merged 1 commit intoJun 24, 2026
Conversation
Copilot started reviewing on behalf of
Richard Markiewicz (thenextman)
June 23, 2026 17:22
View session
There was a problem hiding this comment.
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 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 | ||
| } |
Pavlo Myroniuk (TheBestTvarynka)
approved these changes
Jun 24, 2026
Pavlo Myroniuk (TheBestTvarynka)
left a comment
Collaborator
There was a problem hiding this comment.
LGTM
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>
59cb4c4 to
af0be31
Compare
This was referenced Jun 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
(Kerberos app tags: 15 =
AP-REP, 30 =KRB-ERROR— i.e. we expected anAP-REPfrom the server but got aKRB-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 theTGS-REQto that next realm. Instead, sspi-rs:TGS-REQto the home KDC (RJM.LOCAL) → got back a referralTGTfor krbtgt/DEV.RJM.LOCAL.TGTdirectly into theAP_REQas if it were the service ticket.KRB-ERROR41 (KRB_AP_ERR_MODIFIED).KRB-ERRORas anAP-REP--->InvalidToken.What we changed
Referral chasing loop in the
TGSexchange (src/kerberos/client/mod.rs): after eachTGS-REP, if the returned ticket sname is krbtgt/<NEXT_REALM>, re-issue theTGS-REQfor the same SPN to that realm using the referralTGT, 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_toand addedsend_for_realm. The pinned kdc_url (KDC proxy) stays authoritative for the home realm only; referral hops resolve throughdetect_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_ticketsis only carried on the first hop; edge case, likely fine, but worth a sanity check.