Skip to content

Commit cda9b28

Browse files
committed
Add old behavior under feature flag
1 parent 73db490 commit cda9b28

File tree

5 files changed

+49
-28
lines changed

5 files changed

+49
-28
lines changed

Kerberos.NET/Entities/Krb/KrbKdcRep.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ private static KrbPrincipalName CreateCNameForTicket(ServiceTicketRequest reques
243243
#pragma warning disable CS0618 // Type or member is obsolete
244244
if (!string.IsNullOrEmpty(request.SamAccountName))
245245
{
246+
// Note that the name is returned in a single part here, even if the request may have had multiple parts.
247+
// This may be okay for AS-REQs with Canonicalize set, but it is not spec-compliant for other scenarios.
246248
return new KrbPrincipalName
247249
{
248250
Type = PrincipalNameType.NT_PRINCIPAL,
@@ -256,7 +258,8 @@ private static KrbPrincipalName CreateCNameForTicket(ServiceTicketRequest reques
256258
// Note: this might not be correct in all scenarios. For instance, if the client does not accept
257259
// name canonicalization (i.e., the Canonicalize flag is not set), then it's not spec-compliant to deviate
258260
// from the requested cname. Also, in TGS-REP, the cname should match what's in the TGT, and should not be
259-
// derived from the found principal.
261+
// derived from the found principal. It is the responsibility of the caller to decide whether the request
262+
// warrants passing Principal only, or forcing a specific cname via ClientName.
260263
//
261264
// Note: historically Kerberos.NET had a bug where the service realm was used to derive cname from the principal.
262265
// However, it should be the client realm. This has been corrected under the IsolateRealmsConsistently flag.

Kerberos.NET/Server/KdcAsReqMessageHandler.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,17 @@ private ReadOnlyMemory<byte> GenerateAsRep(KrbAsReq asReq, PreAuthenticationCont
207207
// and types in the AS response and ticket returned from those in the request.
208208
if (!asReq.Body.KdcOptions.HasFlag(KdcOptions.Canonicalize))
209209
{
210-
rst.ClientName = asReq.Body.CName;
210+
if (this.RealmService.Settings.Compatibility.HasFlag(KerberosCompatibilityFlags.EnableSpecCompliantCNameHandling))
211+
{
212+
rst.ClientName = asReq.Body.CName;
213+
}
214+
else
215+
{
216+
#pragma warning disable CS0618 // Type or member is obsolete
217+
rst.SamAccountName = asReq.Body.CName.FullyQualifiedName;
218+
#pragma warning restore CS0618 // Type or member is obsolete
219+
}
220+
211221
}
212222

213223
if (rst.EncryptedPartKey == null)

Kerberos.NET/Server/KdcTgsReqMessageHandler.cs

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -265,24 +265,7 @@ public override ReadOnlyMemory<byte> ExecuteCore(PreAuthenticationContext contex
265265

266266
var rst = new ServiceTicketRequest
267267
{
268-
// In TGS-REP, we should always reply with cname/crealm copied from the TGT, even when the Canonicalize
269-
// flag is set in TGS-REQ.
270-
//
271-
// RFC 4120 § 3.3.3 Generation of KRB_TGS_REP Message
272-
// --------------------------------------------------
273-
// "By default, the address field, the client's name and realm, the list of transited realms, the time
274-
// of initial authentication, the expiration time, and the authorization data of the newly-issued
275-
// ticket will be copied from the TGT or renewable ticket."
276-
//
277-
// RFC 6806 § 6. Name Canonicalization
278-
// -----------------------------------
279-
// "If the "canonicalize" KDC option is set, then the KDC MAY change the client and server principal
280-
// names and types in the AS response and ticket returned from those in the request. Names MUST NOT be
281-
// changed in the response to a TGS request, although it is common for KDCs to maintain a set of
282-
// aliases for service principals."
283-
ClientName = context.Ticket.CName,
284268
ClientRealmName = context.Ticket.CRealm,
285-
286269
KdcAuthorizationKey = context.EvidenceTicketKey,
287270
Principal = context.Principal,
288271
EncryptedPartKey = context.EncryptedPartKey,
@@ -307,17 +290,35 @@ public override ReadOnlyMemory<byte> ExecuteCore(PreAuthenticationContext contex
307290
Compatibility = this.RealmService.Settings.Compatibility,
308291
};
309292

310-
// The code below introduced an annoying regression in a separate party.
311-
// The compatibility flag is a workaround to make sure it can use the original behavior in cases where
312-
// that's expected.
313-
314-
if (!this.RealmService.Settings.Compatibility.HasFlag(KerberosCompatibilityFlags.DoNotCanonicalizeTgsReqFromTgt) &&
293+
if (this.RealmService.Settings.Compatibility.HasFlag(KerberosCompatibilityFlags.EnableSpecCompliantCNameHandling))
294+
{
295+
// In TGS-REP, we should always reply with cname/crealm copied from the TGT, even when the Canonicalize
296+
// flag is set in TGS-REQ.
297+
//
298+
// RFC 4120 § 3.3.3 Generation of KRB_TGS_REP Message
299+
// --------------------------------------------------
300+
// "By default, the address field, the client's name and realm, the list of transited realms, the time
301+
// of initial authentication, the expiration time, and the authorization data of the newly-issued
302+
// ticket will be copied from the TGT or renewable ticket."
303+
//
304+
// RFC 6806 § 6. Name Canonicalization
305+
// -----------------------------------
306+
// "If the "canonicalize" KDC option is set, then the KDC MAY change the client and server principal
307+
// names and types in the AS response and ticket returned from those in the request. Names MUST NOT be
308+
// changed in the response to a TGS request, although it is common for KDCs to maintain a set of
309+
// aliases for service principals."
310+
rst.ClientName = context.Ticket.CName;
311+
}
312+
else if (!this.RealmService.Settings.Compatibility.HasFlag(KerberosCompatibilityFlags.DoNotCanonicalizeTgsReqFromTgt) &&
315313
tgsReq.Body.KdcOptions.HasFlag(KdcOptions.Canonicalize))
316314
{
317-
rst.ClientName = null;
318-
#pragma warning disable CS0612 // Type or member is obsolete
315+
// The code below introduced an annoying regression in a separate party.
316+
// The compatibility flag is a workaround to make sure it can use the original behavior in cases where
317+
// that's expected.
318+
319+
#pragma warning disable CS0612 // Type or member is obsolete
319320
rst.SamAccountName = context.GetState<TgsState>(PaDataType.PA_TGS_REQ).DecryptedApReq.Ticket.CName.FullyQualifiedName;
320-
#pragma warning restore CS0612 // Type or member is obsolete
321+
#pragma warning restore CS0612 // Type or member is obsolete
321322
}
322323

323324
// this is set here instead of in GenerateServiceTicket because GST is used by unit tests to

Kerberos.NET/Server/KerberosCompatibilityFlags.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,12 @@ public enum KerberosCompatibilityFlags
3333
/// fields or properties. This separates the names into two.
3434
/// </summary>
3535
IsolateRealmsConsistently = 1 << 2,
36+
37+
/// <summary>
38+
/// CName handling was historically non-spec compliant in some cases.
39+
/// This flag enables handling that more strictly adheres to the spec, for better compliance
40+
/// with other implementations.
41+
/// </summary>
42+
EnableSpecCompliantCNameHandling = 1 << 3,
3643
}
3744
}

Tests/Tests.Kerberos.NET/FakeRealmService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class FakeRealmService : IRealmService
1313
{
1414
private readonly KerberosCompatibilityFlags compatibilityFlags;
1515

16-
public FakeRealmService(string realm, Krb5Config config = null, KerberosCompatibilityFlags compatibilityFlags = KerberosCompatibilityFlags.IsolateRealmsConsistently)
16+
public FakeRealmService(string realm, Krb5Config config = null, KerberosCompatibilityFlags compatibilityFlags = KerberosCompatibilityFlags.IsolateRealmsConsistently | KerberosCompatibilityFlags.EnableSpecCompliantCNameHandling)
1717
{
1818
this.Name = realm;
1919
this.Configuration = config ?? Krb5Config.Kdc();

0 commit comments

Comments
 (0)