Skip to content
Merged
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
49 changes: 49 additions & 0 deletions Kerberos.NET/Entities/Krb/KrbExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// -----------------------------------------------------------------------
// Licensed to The .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// -----------------------------------------------------------------------

using System.Linq;

namespace Kerberos.NET.Entities
{
public static class KrbExtensions
{
public static bool TryGetPac(this KrbEncTicketPart encTicketPart, out PrivilegedAttributeCertificate pac)
{
pac = null;

KrbAuthorizationData adIfRelevantEntry = encTicketPart.AuthorizationData?.FirstOrDefault(ad => ad.Type == AuthorizationDataType.AdIfRelevant);
if (adIfRelevantEntry == null)
{
return false;
}

KrbAuthorizationDataSequence adIfRelevant = null;
try
{
adIfRelevant = KrbAuthorizationDataSequence.Decode(adIfRelevantEntry.Data);
}
catch
{
return false;
}

KrbAuthorizationData pacEntry = adIfRelevant?.AuthorizationData?.First(ad => ad.Type == AuthorizationDataType.AdWin2kPac);
if (pacEntry == null)
{
return false;
}

try
{
pac = new PrivilegedAttributeCertificate(pacEntry);
return true;
}
catch
{
return false;
}
}
}
}
14 changes: 13 additions & 1 deletion Kerberos.NET/Entities/Krb/KrbKdcRep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,18 @@ private static KrbPrincipalName CreateCNameForTicket(ServiceTicketRequest reques
);
}

private static string GetClientNameForPac(ServiceTicketRequest request)
{
// If ClientName is explicitly set, use that for the PAC client name.
// The PAC client name should match the ticket's cname.
if (request.ClientName != null)
{
return request.ClientName.FullyQualifiedName;
}

return request.Principal.PrincipalName;
}

private static IEnumerable<KrbAuthorizationData> GenerateAuthorizationData(ServiceTicketRequest request)
{
// authorization-data is annoying because it's a sequence of
Expand Down Expand Up @@ -303,7 +315,7 @@ private static IEnumerable<KrbAuthorizationData> GenerateAuthorizationData(Servi
pac.ClientInformation = new PacClientInfo
{
ClientId = RpcFileTime.ConvertWithoutMicroseconds(request.Now),
Name = request.Principal.PrincipalName
Name = GetClientNameForPac(request)
};

var sequence = new KrbAuthorizationDataSequence
Expand Down
14 changes: 4 additions & 10 deletions Tests/Tests.Kerberos.NET/FakePrincipalService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,20 @@ public Task<IKerberosPrincipal> FindAsync(KrbPrincipalName principalName, string

public IKerberosPrincipal Find(KrbPrincipalName principalName, string realm = null)
{
IKerberosPrincipal principal = null;

bool fallback = false;

if (principalName.FullyQualifiedName.Contains("-fallback", StringComparison.OrdinalIgnoreCase) &&
principalName.Type == PrincipalNameType.NT_ENTERPRISE)
{
principal = null;
fallback = true;
return null;
}

if ((principalName.FullyQualifiedName.EndsWith(this.realm, StringComparison.InvariantCultureIgnoreCase) ||
if (principalName.FullyQualifiedName.EndsWith(this.realm, StringComparison.InvariantCultureIgnoreCase) ||
principalName.FullyQualifiedName.StartsWith("krbtgt", StringComparison.InvariantCultureIgnoreCase) ||
principalName.Type == PrincipalNameType.NT_PRINCIPAL)
&& !fallback)
{
principal = new FakeKerberosPrincipal(principalName.FullyQualifiedName);
return new FakeKerberosPrincipal(principalName.FullyQualifiedName);
}

return principal;
return null;
}

public X509Certificate2 RetrieveKdcCertificate()
Expand Down
122 changes: 113 additions & 9 deletions Tests/Tests.Kerberos.NET/Kdc/KdcHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Kerberos.NET;
using Kerberos.NET.Client;
using Kerberos.NET.Configuration;
using Kerberos.NET.Credentials;
using Kerberos.NET.Crypto;
using Kerberos.NET.Entities;
using Kerberos.NET.Entities.Pac;
using Kerberos.NET.Server;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using static Tests.Kerberos.NET.KdcListenerTestBase;
Expand All @@ -33,7 +35,13 @@ public void KdcAsReqHandler_Sync()
{
KrbAsRep asRep = RequestTgt(cname: Upn, crealm: Realm, srealm: Realm, out _, out KrbAsReq asReq);

ValidateAsRep(asRep, expectedCName: Upn, expectedCRealm: Realm, expectedSRealm: Realm, asReq);
ValidateAsRep(
asRep,
expectedCName: Upn,
expectedCRealm: Realm,
expectedSRealm: Realm,
expectPac: true,
asReq);
}

[TestMethod]
Expand Down Expand Up @@ -77,7 +85,8 @@ out KrbEncryptionKey sessionKey
expectedCName: Upn,
expectedCRealm: Realm,
expectedSName: spn,
expectedSRealm: Realm);
expectedSRealm: Realm,
expectPac: true);
}

[TestMethod]
Expand All @@ -91,9 +100,21 @@ public void KdcTgsReqHandler_Sync_ReferralTgt()
Name = new[] { Upn2WithoutRealm }
};

KrbAsRep asRep = CreateReferralTgt(sourceRealm, destRealm, cname, out KerberosKey tgtKey, out KerberosKey asRepKey, out KrbEncryptionKey sessionKey);
KrbAsRep asRep = CreateReferralTgt(
sourceRealm,
destRealm,
cname,
includePac: true,
out KerberosKey tgtKey,
out KerberosKey asRepKey,
out KrbEncryptionKey sessionKey);

ValidateAsRep(asRep, expectedCName: Upn2WithoutRealm, expectedCRealm: sourceRealm, expectedSRealm: destRealm);
ValidateAsRep(
asRep,
expectedCName: Upn2WithoutRealm,
expectedCRealm: sourceRealm,
expectedSRealm: destRealm,
expectPac: true);

// Send a TGS-REQ to get a service ticket in the destination realm
var spn = "host/foo." + Realm;
Expand Down Expand Up @@ -131,10 +152,17 @@ out KrbEncryptionKey subSessionKey
expectedCName: Upn2WithoutRealm,
expectedCRealm: sourceRealm,
expectedSName: spn,
expectedSRealm: destRealm);
expectedSRealm: destRealm,
expectPac: true);
}

private void ValidateAsRep(KrbAsRep asRep, string expectedCName, string expectedCRealm, string expectedSRealm, KrbAsReq asReq = null)
private void ValidateAsRep(
KrbAsRep asRep,
string expectedCName,
string expectedCRealm,
string expectedSRealm,
bool expectPac,
KrbAsReq asReq = null)
{
Assert.IsNotNull(asRep);

Expand Down Expand Up @@ -170,9 +198,31 @@ private void ValidateAsRep(KrbAsRep asRep, string expectedCName, string expected
Assert.IsNotNull(ticketEncPart);
Assert.AreEqual(expectedCRealm, ticketEncPart.CRealm);
Assert.AreEqual(expectedCName, ticketEncPart.CName.FullyQualifiedName);

// Check PAC fields
bool success = ticketEncPart.TryGetPac(out PrivilegedAttributeCertificate pac);
if (!expectPac)
{
Assert.IsFalse(success);
Assert.IsNull(pac);
}
else
{
Assert.IsTrue(success);
Assert.IsNotNull(pac);
Assert.AreEqual(expectedCName, pac.ClientInformation.Name);
}
}

private void ValidateTgsRep(KrbTgsRep tgsRep, KerberosKey subSessionKey, KerberosKey ticketKey, string expectedCName, string expectedCRealm, string expectedSName, string expectedSRealm)
private void ValidateTgsRep(
KrbTgsRep tgsRep,
KerberosKey subSessionKey,
KerberosKey ticketKey,
string expectedCName,
string expectedCRealm,
string expectedSName,
string expectedSRealm,
bool expectPac)
{
Assert.IsNotNull(tgsRep);

Expand Down Expand Up @@ -200,9 +250,30 @@ private void ValidateTgsRep(KrbTgsRep tgsRep, KerberosKey subSessionKey, Kerbero
Assert.IsNotNull(ticketEncPart);
Assert.AreEqual(expectedCRealm, ticketEncPart.CRealm);
Assert.AreEqual(expectedCName, ticketEncPart.CName.FullyQualifiedName);

// Check PAC fields
bool success = ticketEncPart.TryGetPac(out PrivilegedAttributeCertificate pac);
if (!expectPac)
{
Assert.IsFalse(success);
Assert.IsNull(pac);
}
else
{
Assert.IsTrue(success);
Assert.IsNotNull(pac);
Assert.AreEqual(expectedCName, pac.ClientInformation.Name);
}
}

private KrbAsRep CreateReferralTgt(string sourceRealm, string destRealm, KrbPrincipalName cname, out KerberosKey tgtKey, out KerberosKey asRepKey, out KrbEncryptionKey sessionKey)
private KrbAsRep CreateReferralTgt(
string sourceRealm,
string destRealm,
KrbPrincipalName cname,
bool includePac,
out KerberosKey tgtKey,
out KerberosKey asRepKey,
out KrbEncryptionKey sessionKey)
{
var sourceRealmService = new FakeRealmService(sourceRealm);

Expand All @@ -217,6 +288,39 @@ private KrbAsRep CreateReferralTgt(string sourceRealm, string destRealm, KrbPrin

DateTimeOffset now = DateTimeOffset.UtcNow;

KrbAuthorizationData[] authorizationData = null;

if (includePac)
{
var pac = clientPrincipal.GeneratePac();
Assert.IsNotNull(pac);

pac.ClientInformation = new PacClientInfo
{
Name = cname.FullyQualifiedName,
ClientId = RpcFileTime.ConvertWithoutMicroseconds(now),
};

authorizationData = new[]
{
new KrbAuthorizationData
{
Type = AuthorizationDataType.AdIfRelevant,
Data = new KrbAuthorizationDataSequence
{
AuthorizationData = new[]
{
new KrbAuthorizationData
{
Type = AuthorizationDataType.AdWin2kPac,
Data = pac.Encode(tgtKey, tgtKey)
}
}
}.Encode()
}
};
}

var encTicketPart = new KrbEncTicketPart()
{
CName = cname,
Expand All @@ -227,7 +331,7 @@ private KrbAsRep CreateReferralTgt(string sourceRealm, string destRealm, KrbPrin
EndTime = now.AddHours(1),
RenewTill = now.AddDays(30),
Flags = TicketFlags.PreAuthenticated | TicketFlags.Initial | TicketFlags.Renewable | TicketFlags.Forwardable,
AuthorizationData = null,
AuthorizationData = authorizationData,
CAddr = new KrbHostAddress[] { },
Transited = new KrbTransitedEncoding()
};
Expand Down
Loading
Loading