Skip to content

Commit 958ef49

Browse files
authored
Merge pull request #418 from MaximeKjaer/pac-cname
Fix PAC ClientInformation.Name generation
2 parents 0079da3 + 38649e2 commit 958ef49

5 files changed

Lines changed: 284 additions & 44 deletions

File tree

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// -----------------------------------------------------------------------
2+
// Licensed to The .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// -----------------------------------------------------------------------
5+
6+
using System.Linq;
7+
8+
namespace Kerberos.NET.Entities
9+
{
10+
public static class KrbExtensions
11+
{
12+
public static bool TryGetPac(this KrbEncTicketPart encTicketPart, out PrivilegedAttributeCertificate pac)
13+
{
14+
pac = null;
15+
16+
KrbAuthorizationData adIfRelevantEntry = encTicketPart.AuthorizationData?.FirstOrDefault(ad => ad.Type == AuthorizationDataType.AdIfRelevant);
17+
if (adIfRelevantEntry == null)
18+
{
19+
return false;
20+
}
21+
22+
KrbAuthorizationDataSequence adIfRelevant = null;
23+
try
24+
{
25+
adIfRelevant = KrbAuthorizationDataSequence.Decode(adIfRelevantEntry.Data);
26+
}
27+
catch
28+
{
29+
return false;
30+
}
31+
32+
KrbAuthorizationData pacEntry = adIfRelevant?.AuthorizationData?.First(ad => ad.Type == AuthorizationDataType.AdWin2kPac);
33+
if (pacEntry == null)
34+
{
35+
return false;
36+
}
37+
38+
try
39+
{
40+
pac = new PrivilegedAttributeCertificate(pacEntry);
41+
return true;
42+
}
43+
catch
44+
{
45+
return false;
46+
}
47+
}
48+
}
49+
}

Kerberos.NET/Entities/Krb/KrbKdcRep.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,18 @@ private static KrbPrincipalName CreateCNameForTicket(ServiceTicketRequest reques
271271
);
272272
}
273273

274+
private static string GetClientNameForPac(ServiceTicketRequest request)
275+
{
276+
// If ClientName is explicitly set, use that for the PAC client name.
277+
// The PAC client name should match the ticket's cname.
278+
if (request.ClientName != null)
279+
{
280+
return request.ClientName.FullyQualifiedName;
281+
}
282+
283+
return request.Principal.PrincipalName;
284+
}
285+
274286
private static IEnumerable<KrbAuthorizationData> GenerateAuthorizationData(ServiceTicketRequest request)
275287
{
276288
// authorization-data is annoying because it's a sequence of
@@ -303,7 +315,7 @@ private static IEnumerable<KrbAuthorizationData> GenerateAuthorizationData(Servi
303315
pac.ClientInformation = new PacClientInfo
304316
{
305317
ClientId = RpcFileTime.ConvertWithoutMicroseconds(request.Now),
306-
Name = request.Principal.PrincipalName
318+
Name = GetClientNameForPac(request)
307319
};
308320

309321
var sequence = new KrbAuthorizationDataSequence

Tests/Tests.Kerberos.NET/FakePrincipalService.cs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,26 +30,20 @@ public Task<IKerberosPrincipal> FindAsync(KrbPrincipalName principalName, string
3030

3131
public IKerberosPrincipal Find(KrbPrincipalName principalName, string realm = null)
3232
{
33-
IKerberosPrincipal principal = null;
34-
35-
bool fallback = false;
36-
3733
if (principalName.FullyQualifiedName.Contains("-fallback", StringComparison.OrdinalIgnoreCase) &&
3834
principalName.Type == PrincipalNameType.NT_ENTERPRISE)
3935
{
40-
principal = null;
41-
fallback = true;
36+
return null;
4237
}
4338

44-
if ((principalName.FullyQualifiedName.EndsWith(this.realm, StringComparison.InvariantCultureIgnoreCase) ||
39+
if (principalName.FullyQualifiedName.EndsWith(this.realm, StringComparison.InvariantCultureIgnoreCase) ||
4540
principalName.FullyQualifiedName.StartsWith("krbtgt", StringComparison.InvariantCultureIgnoreCase) ||
4641
principalName.Type == PrincipalNameType.NT_PRINCIPAL)
47-
&& !fallback)
4842
{
49-
principal = new FakeKerberosPrincipal(principalName.FullyQualifiedName);
43+
return new FakeKerberosPrincipal(principalName.FullyQualifiedName);
5044
}
5145

52-
return principal;
46+
return null;
5347
}
5448

5549
public X509Certificate2 RetrieveKdcCertificate()

Tests/Tests.Kerberos.NET/Kdc/KdcHandlerTests.cs

Lines changed: 113 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55

66
using System;
77
using System.Collections.Generic;
8+
using System.Linq;
89
using System.Security.Cryptography.X509Certificates;
910
using Kerberos.NET;
1011
using Kerberos.NET.Client;
1112
using Kerberos.NET.Configuration;
1213
using Kerberos.NET.Credentials;
1314
using Kerberos.NET.Crypto;
1415
using Kerberos.NET.Entities;
16+
using Kerberos.NET.Entities.Pac;
1517
using Kerberos.NET.Server;
1618
using Microsoft.VisualStudio.TestTools.UnitTesting;
1719
using static Tests.Kerberos.NET.KdcListenerTestBase;
@@ -33,7 +35,13 @@ public void KdcAsReqHandler_Sync()
3335
{
3436
KrbAsRep asRep = RequestTgt(cname: Upn, crealm: Realm, srealm: Realm, out _, out KrbAsReq asReq);
3537

36-
ValidateAsRep(asRep, expectedCName: Upn, expectedCRealm: Realm, expectedSRealm: Realm, asReq);
38+
ValidateAsRep(
39+
asRep,
40+
expectedCName: Upn,
41+
expectedCRealm: Realm,
42+
expectedSRealm: Realm,
43+
expectPac: true,
44+
asReq);
3745
}
3846

3947
[TestMethod]
@@ -77,7 +85,8 @@ out KrbEncryptionKey sessionKey
7785
expectedCName: Upn,
7886
expectedCRealm: Realm,
7987
expectedSName: spn,
80-
expectedSRealm: Realm);
88+
expectedSRealm: Realm,
89+
expectPac: true);
8190
}
8291

8392
[TestMethod]
@@ -91,9 +100,21 @@ public void KdcTgsReqHandler_Sync_ReferralTgt()
91100
Name = new[] { Upn2WithoutRealm }
92101
};
93102

94-
KrbAsRep asRep = CreateReferralTgt(sourceRealm, destRealm, cname, out KerberosKey tgtKey, out KerberosKey asRepKey, out KrbEncryptionKey sessionKey);
103+
KrbAsRep asRep = CreateReferralTgt(
104+
sourceRealm,
105+
destRealm,
106+
cname,
107+
includePac: true,
108+
out KerberosKey tgtKey,
109+
out KerberosKey asRepKey,
110+
out KrbEncryptionKey sessionKey);
95111

96-
ValidateAsRep(asRep, expectedCName: Upn2WithoutRealm, expectedCRealm: sourceRealm, expectedSRealm: destRealm);
112+
ValidateAsRep(
113+
asRep,
114+
expectedCName: Upn2WithoutRealm,
115+
expectedCRealm: sourceRealm,
116+
expectedSRealm: destRealm,
117+
expectPac: true);
97118

98119
// Send a TGS-REQ to get a service ticket in the destination realm
99120
var spn = "host/foo." + Realm;
@@ -131,10 +152,17 @@ out KrbEncryptionKey subSessionKey
131152
expectedCName: Upn2WithoutRealm,
132153
expectedCRealm: sourceRealm,
133154
expectedSName: spn,
134-
expectedSRealm: destRealm);
155+
expectedSRealm: destRealm,
156+
expectPac: true);
135157
}
136158

137-
private void ValidateAsRep(KrbAsRep asRep, string expectedCName, string expectedCRealm, string expectedSRealm, KrbAsReq asReq = null)
159+
private void ValidateAsRep(
160+
KrbAsRep asRep,
161+
string expectedCName,
162+
string expectedCRealm,
163+
string expectedSRealm,
164+
bool expectPac,
165+
KrbAsReq asReq = null)
138166
{
139167
Assert.IsNotNull(asRep);
140168

@@ -170,9 +198,31 @@ private void ValidateAsRep(KrbAsRep asRep, string expectedCName, string expected
170198
Assert.IsNotNull(ticketEncPart);
171199
Assert.AreEqual(expectedCRealm, ticketEncPart.CRealm);
172200
Assert.AreEqual(expectedCName, ticketEncPart.CName.FullyQualifiedName);
201+
202+
// Check PAC fields
203+
bool success = ticketEncPart.TryGetPac(out PrivilegedAttributeCertificate pac);
204+
if (!expectPac)
205+
{
206+
Assert.IsFalse(success);
207+
Assert.IsNull(pac);
208+
}
209+
else
210+
{
211+
Assert.IsTrue(success);
212+
Assert.IsNotNull(pac);
213+
Assert.AreEqual(expectedCName, pac.ClientInformation.Name);
214+
}
173215
}
174216

175-
private void ValidateTgsRep(KrbTgsRep tgsRep, KerberosKey subSessionKey, KerberosKey ticketKey, string expectedCName, string expectedCRealm, string expectedSName, string expectedSRealm)
217+
private void ValidateTgsRep(
218+
KrbTgsRep tgsRep,
219+
KerberosKey subSessionKey,
220+
KerberosKey ticketKey,
221+
string expectedCName,
222+
string expectedCRealm,
223+
string expectedSName,
224+
string expectedSRealm,
225+
bool expectPac)
176226
{
177227
Assert.IsNotNull(tgsRep);
178228

@@ -200,9 +250,30 @@ private void ValidateTgsRep(KrbTgsRep tgsRep, KerberosKey subSessionKey, Kerbero
200250
Assert.IsNotNull(ticketEncPart);
201251
Assert.AreEqual(expectedCRealm, ticketEncPart.CRealm);
202252
Assert.AreEqual(expectedCName, ticketEncPart.CName.FullyQualifiedName);
253+
254+
// Check PAC fields
255+
bool success = ticketEncPart.TryGetPac(out PrivilegedAttributeCertificate pac);
256+
if (!expectPac)
257+
{
258+
Assert.IsFalse(success);
259+
Assert.IsNull(pac);
260+
}
261+
else
262+
{
263+
Assert.IsTrue(success);
264+
Assert.IsNotNull(pac);
265+
Assert.AreEqual(expectedCName, pac.ClientInformation.Name);
266+
}
203267
}
204268

205-
private KrbAsRep CreateReferralTgt(string sourceRealm, string destRealm, KrbPrincipalName cname, out KerberosKey tgtKey, out KerberosKey asRepKey, out KrbEncryptionKey sessionKey)
269+
private KrbAsRep CreateReferralTgt(
270+
string sourceRealm,
271+
string destRealm,
272+
KrbPrincipalName cname,
273+
bool includePac,
274+
out KerberosKey tgtKey,
275+
out KerberosKey asRepKey,
276+
out KrbEncryptionKey sessionKey)
206277
{
207278
var sourceRealmService = new FakeRealmService(sourceRealm);
208279

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

218289
DateTimeOffset now = DateTimeOffset.UtcNow;
219290

291+
KrbAuthorizationData[] authorizationData = null;
292+
293+
if (includePac)
294+
{
295+
var pac = clientPrincipal.GeneratePac();
296+
Assert.IsNotNull(pac);
297+
298+
pac.ClientInformation = new PacClientInfo
299+
{
300+
Name = cname.FullyQualifiedName,
301+
ClientId = RpcFileTime.ConvertWithoutMicroseconds(now),
302+
};
303+
304+
authorizationData = new[]
305+
{
306+
new KrbAuthorizationData
307+
{
308+
Type = AuthorizationDataType.AdIfRelevant,
309+
Data = new KrbAuthorizationDataSequence
310+
{
311+
AuthorizationData = new[]
312+
{
313+
new KrbAuthorizationData
314+
{
315+
Type = AuthorizationDataType.AdWin2kPac,
316+
Data = pac.Encode(tgtKey, tgtKey)
317+
}
318+
}
319+
}.Encode()
320+
}
321+
};
322+
}
323+
220324
var encTicketPart = new KrbEncTicketPart()
221325
{
222326
CName = cname,
@@ -227,7 +331,7 @@ private KrbAsRep CreateReferralTgt(string sourceRealm, string destRealm, KrbPrin
227331
EndTime = now.AddHours(1),
228332
RenewTill = now.AddDays(30),
229333
Flags = TicketFlags.PreAuthenticated | TicketFlags.Initial | TicketFlags.Renewable | TicketFlags.Forwardable,
230-
AuthorizationData = null,
334+
AuthorizationData = authorizationData,
231335
CAddr = new KrbHostAddress[] { },
232336
Transited = new KrbTransitedEncoding()
233337
};

0 commit comments

Comments
 (0)