Skip to content

[WIP] Flow client and target names and realms separately #397

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Kerberos.NET/Configuration/Krb5RealmConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ public class Krb5RealmConfig : Krb5ConfigObject
/// Compatibility shims should be enforced by the KDC.
/// </summary>
[EnumAsInteger]
[DefaultValue(KerberosCompatibilityFlags.None)]
[DefaultValue(KerberosCompatibilityFlags.IsolateRealmsConsistently)]
[DisplayName("compatibility_flags")]
public KerberosCompatibilityFlags CompatibilityFlags { get; set; }
}
Expand Down
2 changes: 1 addition & 1 deletion Kerberos.NET/Crypto/DecryptedKrbApReq.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ public override void Validate(ValidationActions validation)

if (validation.HasFlag(ValidationActions.Realm))
{
this.ValidateRealm(this.Ticket.CRealm, this.Authenticator.Realm);
this.ValidateRealm(this.Ticket.CRealm, this.Authenticator.CRealm);
}

var now = this.Now();
Expand Down
3 changes: 3 additions & 0 deletions Kerberos.NET/Crypto/DecryptedKrbMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

using System;
using Kerberos.NET.Entities;
using Kerberos.NET.Server;
using static Kerberos.NET.Entities.KerberosConstants;

namespace Kerberos.NET.Crypto
Expand All @@ -20,6 +21,8 @@ public Func<DateTimeOffset> Now
set { this.nowFunc = value; }
}

public KerberosCompatibilityFlags CompatibilityFlags { get; set; }

public abstract void Validate(ValidationActions validation);

public virtual void Decrypt(KeyTable keytab)
Expand Down
2 changes: 1 addition & 1 deletion Kerberos.NET/Entities/Krb/KrbApReq.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ out KrbAuthenticator authenticator
authenticator = new KrbAuthenticator
{
CName = tgsRep.CName,
Realm = tgsRep.CRealm
CRealm = tgsRep.CRealm
};

if (rst.AuthenticatorChecksum != null)
Expand Down
5 changes: 5 additions & 0 deletions Kerberos.NET/Entities/Krb/KrbAsRep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ IRealmService realmService
rst.RealmName = realmService.Name;
}

if (string.IsNullOrWhiteSpace(rst.ClientRealmName))
{
rst.ClientRealmName = realmService.Name;
}

KrbPrincipalName krbtgtName = KrbPrincipalName.WellKnown.Krbtgt(rst.RealmName);

if (rst.ServicePrincipal == null)
Expand Down
9 changes: 7 additions & 2 deletions Kerberos.NET/Entities/Krb/KrbAuthenticator.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
// Licensed to The .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// -----------------------------------------------------------------------

using System;

namespace Kerberos.NET.Entities
{
public partial class KrbAuthenticator
Expand All @@ -11,5 +13,8 @@ public KrbAuthenticator()
{
this.AuthenticatorVersionNumber = 5;
}

[Obsolete("Use to property named to match the spec `CRealm`.")]
public string Realm { get => this.CRealm; set => this.CRealm = value; }
}
}
}
6 changes: 3 additions & 3 deletions Kerberos.NET/Entities/Krb/KrbAuthenticator.generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public partial class KrbAuthenticator

public int AuthenticatorVersionNumber { get; set; }

public string Realm { get; set; }
public string CRealm { get; set; }

public KrbPrincipalName CName { get; set; }

Expand Down Expand Up @@ -63,7 +63,7 @@ internal void Encode(AsnWriter writer, Asn1Tag tag)
writer.WriteInteger(AuthenticatorVersionNumber);
writer.PopSequence(new Asn1Tag(TagClass.ContextSpecific, 0));
writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 1));
writer.WriteCharacterString(UniversalTagNumber.GeneralString, Realm);
writer.WriteCharacterString(UniversalTagNumber.GeneralString, CRealm);
writer.PopSequence(new Asn1Tag(TagClass.ContextSpecific, 1));
writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 2));
CName?.Encode(writer);
Expand Down Expand Up @@ -223,7 +223,7 @@ internal static void Decode<T>(AsnReader reader, Asn1Tag expectedTag, out T deco
explicitReader.ThrowIfNotEmpty();

explicitReader = sequenceReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 1));
decoded.Realm = explicitReader.ReadCharacterString(UniversalTagNumber.GeneralString);
decoded.CRealm = explicitReader.ReadCharacterString(UniversalTagNumber.GeneralString);

explicitReader.ThrowIfNotEmpty();

Expand Down
2 changes: 1 addition & 1 deletion Kerberos.NET/Entities/Krb/KrbAuthenticator.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
}-->

<asn:Integer name="AuthenticatorVersionNumber" explicitTag="0" backingType="int" />
<asn:GeneralString name="Realm" explicitTag="1" />
<asn:GeneralString name="CRealm" explicitTag="1" />
<asn:AsnType name="CName" typeName="KrbPrincipalName" explicitTag="2" />
<asn:AsnType name="Checksum" typeName="KrbChecksum" explicitTag="3" optional="true" />
<asn:Integer name="CuSec" explicitTag="4" backingType="int" />
Expand Down
48 changes: 26 additions & 22 deletions Kerberos.NET/Entities/Krb/KrbKdcRep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public static KrbCred GenerateWrappedServiceTicket(
ServiceTicketRequest request,
KrbEncryptionKey sessionKey = null,
IEnumerable<KrbAuthorizationData> authz = null
)
)
{
GenerateServiceTicket<KrbTgsRep>(
request,
Expand All @@ -46,8 +46,7 @@ public static T GenerateServiceTicket<T>(
ServiceTicketRequest request,
KrbEncryptionKey encryptionKey = null,
IEnumerable<KrbAuthorizationData> authz = null
)
where T : KrbKdcRep, new()
) where T : KrbKdcRep, new()
{
if (request.EncryptedPartKey == null)
{
Expand All @@ -67,8 +66,14 @@ out MessageType messageType

var rep = new T
{
CName = encTicketPart.CName,
CRealm = request.RealmName,
CName = request.Compatibility.HasFlag(KerberosCompatibilityFlags.IsolateRealmsConsistently) ?
KrbPrincipalName.FromPrincipal(request.Principal) ?? encTicketPart.CName :
encTicketPart.CName,

CRealm = request.Compatibility.HasFlag(KerberosCompatibilityFlags.IsolateRealmsConsistently) ?
request.ClientRealmName :
request.RealmName,

MessageType = messageType,
Ticket = ticket,
EncPart = KrbEncryptedData.Encrypt(
Expand All @@ -91,8 +96,7 @@ private static ServiceTicketRequest GenerateServiceTicket<T>(
out KrbEncKdcRepPart encKdcRepPart,
out KeyUsage keyUsage,
out MessageType messageType
)
where T : KrbKdcRep, new()
) where T : KrbKdcRep, new()
{
if (request.Principal == null)
{
Expand All @@ -112,17 +116,12 @@ out MessageType messageType
if (request.Compatibility.HasFlag(KerberosCompatibilityFlags.NormalizeRealmsUppercase))
{
request.RealmName = request.RealmName?.ToUpperInvariant();
request.ClientRealmName = request.ClientRealmName?.ToUpperInvariant() ?? throw new InvalidOperationException("Unknown client realm name");
}

if (authz == null)
{
authz = GenerateAuthorizationData(request);
}
authz ??= GenerateAuthorizationData(request);

if (sessionKey == null)
{
sessionKey = KrbEncryptionKey.Generate(request.PreferredClientEType ?? request.ServicePrincipalKey.EncryptionType);
}
sessionKey ??= KrbEncryptionKey.Generate(request.PreferredClientEType ?? request.ServicePrincipalKey.EncryptionType);

encTicketPart = CreateEncTicketPart(request, authz.ToArray(), sessionKey);
bool appendRealm = false;
Expand All @@ -146,6 +145,7 @@ out MessageType messageType
KeyUsage.Ticket
)
};

if (typeof(T) == typeof(KrbAsRep))
{
encKdcRepPart = new KrbEncAsRepPart();
Expand Down Expand Up @@ -186,13 +186,15 @@ out MessageType messageType
}
}
};

return request;
}

private static KrbEncTicketPart CreateEncTicketPart(
ServiceTicketRequest request,
KrbAuthorizationData[] authorizationDatas,
KrbEncryptionKey sessionKey)
KrbEncryptionKey sessionKey
)
{
var cname = CreateCNameForTicket(request);

Expand All @@ -205,19 +207,16 @@ private static KrbEncTicketPart CreateEncTicketPart(

var addresses = request.Addresses;

if (addresses == null)
{
addresses = Array.Empty<KrbHostAddress>();
}
addresses ??= Array.Empty<KrbHostAddress>();

var encTicketPart = new KrbEncTicketPart()
{
CName = cname,
CRealm = request.ClientRealmName,
Key = sessionKey,
AuthTime = request.Now,
StartTime = request.StartTime,
EndTime = request.EndTime,
CRealm = request.RealmName,
Flags = flags,
AuthorizationData = authorizationDatas,
CAddr = addresses.ToArray(),
Expand All @@ -238,7 +237,12 @@ private static KrbPrincipalName CreateCNameForTicket(ServiceTicketRequest reques
{
if (string.IsNullOrEmpty(request.SamAccountName))
{
return KrbPrincipalName.FromPrincipal(request.Principal, realm: request.RealmName);
return KrbPrincipalName.FromPrincipal(
request.Principal,
realm: request.Compatibility.HasFlag(KerberosCompatibilityFlags.IsolateRealmsConsistently) ?
request.ClientRealmName :
request.RealmName
);
}

return new KrbPrincipalName
Expand Down
2 changes: 1 addition & 1 deletion Kerberos.NET/Entities/Krb/KrbTgsReq.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ private static KrbApReq CreateApReq(KrbKdcRep kdcRep, KrbEncryptionKey tgtSessio
var authenticator = new KrbAuthenticator
{
CName = kdcRep.CName,
Realm = kdcRep.CRealm,
CRealm = kdcRep.CRealm,
SequenceNumber = GetNonce(),
Checksum = checksum
};
Expand Down
5 changes: 5 additions & 0 deletions Kerberos.NET/Entities/Krb/ServiceTicketRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public struct ServiceTicketRequest : IEquatable<ServiceTicketRequest>
/// </summary>
public KerberosKey KdcAuthorizationKey { get; set; }

/// <summary>
/// The realm name for which the requested identity originated
/// </summary>
public string ClientRealmName { get; set; }

/// <summary>
/// The principal for which a service ticket is requested
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion Kerberos.NET/KerberosAuthenticator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ private static void SetMinimumIdentity(DecryptedKrbApReq krbApReq, List<Claim> c
claims.Add(new Claim(ClaimTypes.NameIdentifier, krbApReq.Ticket.CName.FullyQualifiedName, ClaimValueTypes.String, AD_AUTHORITY));
}

private void DecodeRestrictions(
protected void DecodeRestrictions(
DecryptedKrbApReq krbApReq,
List<Claim> claims,
List<Restriction> restrictions
Expand Down
13 changes: 8 additions & 5 deletions Kerberos.NET/Server/KdcTgsReqMessageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,10 @@ public override async Task QueryPreExecuteAsync(PreAuthenticationContext context
tgsReq.Body.SName.FullyQualifiedName,
tgsReq.Body.Realm);

context.ServicePrincipal = await this.RealmService.Principals.FindAsync(tgsReq.Body.SName, tgsReq.Body.Realm).ConfigureAwait(false);
context.ServicePrincipal = await this.RealmService.Principals.FindAsync(
tgsReq.Body.SName,
tgsReq.Body.Realm
).ConfigureAwait(false);
}

public override ReadOnlyMemory<byte> ExecuteCore(PreAuthenticationContext context)
Expand Down Expand Up @@ -255,10 +258,9 @@ public override ReadOnlyMemory<byte> ExecuteCore(PreAuthenticationContext contex
{
context.IncludePac = DetectPacRequirement(tgsReq);

if (context.IncludePac == null)
{
context.IncludePac = context.Ticket?.AuthorizationData?.Any(a => a.Type == AuthorizationDataType.AdIfRelevant) ?? false;
}
context.IncludePac ??= context.Ticket?.AuthorizationData?.Any(
a => a.Type == AuthorizationDataType.AdIfRelevant
) ?? false;
}

var rst = new ServiceTicketRequest
Expand All @@ -269,6 +271,7 @@ public override ReadOnlyMemory<byte> ExecuteCore(PreAuthenticationContext contex
EncryptedPartEType = context.EncryptedPartEType,
ServicePrincipal = context.ServicePrincipal,
ServicePrincipalKey = serviceKey,
ClientRealmName = context.ClientRealm,
RealmName = tgsReq.Body.Realm,
Addresses = tgsReq.Body.Addresses,
RenewTill = context.Ticket.RenewTill,
Expand Down
6 changes: 6 additions & 0 deletions Kerberos.NET/Server/KerberosCompatibilityFlags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,11 @@ public enum KerberosCompatibilityFlags
/// Do not copy the name from the TGT if the canonicalize bit is set
/// </summary>
DoNotCanonicalizeTgsReqFromTgt = 1 << 1,

/// <summary>
/// Realms are unique between the client and the target but historically they shared common
/// fields or properties. This separates the names into two.
/// </summary>
IsolateRealmsConsistently = 1 << 2,
}
}
26 changes: 7 additions & 19 deletions Kerberos.NET/Server/PaDataTgsTicketHandler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
// Licensed to The .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// -----------------------------------------------------------------------
Expand Down Expand Up @@ -94,30 +94,18 @@ public override KrbPaData Validate(KrbKdcReq asReq, PreAuthenticationContext con
// in either case we can and will validate the ticket and
// extract the user principal from within the krbtgt ticket

var krbtgtKey = context.EvidenceTicketIdentity.RetrieveLongTermCredential();
var krbtgtKey = context.EvidenceTicketIdentity.RetrieveLongTermCredential()
?? throw new KerberosProtocolException(KerberosErrorCode.KDC_ERR_ETYPE_NOSUPP);

if (krbtgtKey == null)
{
// since the key comes from caller-implemented code we
// should check to make sure they gave us a usable key

throw new KerberosProtocolException(KerberosErrorCode.KDC_ERR_ETYPE_NOSUPP);
}

if (context.EvidenceTicketKey == null)
{
context.EvidenceTicketKey = krbtgtKey;
}
context.EvidenceTicketKey ??= krbtgtKey;

var state = context.GetState<TgsState>(PaDataType.PA_TGS_REQ);

if (state.DecryptedApReq == null)
{
state.DecryptedApReq = this.DecryptApReq(state.ApReq, context.EvidenceTicketKey);
}
state.DecryptedApReq ??= this.DecryptApReq(state.ApReq, context.EvidenceTicketKey);

context.EncryptedPartKey = state.DecryptedApReq.SessionKey;
context.Ticket = state.DecryptedApReq.Ticket;
context.ClientRealm = state.DecryptedApReq.Ticket.CRealm;

return null;
}
Expand Down Expand Up @@ -159,4 +147,4 @@ private DecryptedKrbApReq DecryptApReq(KrbApReq apReq, KerberosKey krbtgtKey)
return apReqDecrypted;
}
}
}
}
5 changes: 5 additions & 0 deletions Kerberos.NET/Server/PreAuthenticationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ public class PreAuthenticationContext
/// </summary>
public KerberosKey EvidenceTicketKey { get; set; }

/// <summary>
/// The name of the realm that the client issued a TGT from.
/// </summary>
public string ClientRealm { get; set; }

/// <summary>
/// The identity that will be the subject of the issued ticket.
/// </summary>
Expand Down
Loading
Loading