Skip to content

Commit bf5390c

Browse files
committed
Added basic KRB_CRED parsing and decryption.
1 parent 3e6914b commit bf5390c

5 files changed

Lines changed: 193 additions & 10 deletions

File tree

NtApiDotNet/NtApiDotNet.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@
338338
<Compile Include="Win32\Security\Authentication\Kerberos\KerberosAuthenticator.cs" />
339339
<Compile Include="Win32\Security\Authentication\Kerberos\KerberosChecksum.cs" />
340340
<Compile Include="Win32\Security\Authentication\Kerberos\KerberosChecksumGSSApi.cs" />
341+
<Compile Include="Win32\Security\Authentication\Kerberos\KerberosCredential.cs" />
341342
<Compile Include="Win32\Security\Authentication\Kerberos\KerberosHostAddress.cs" />
342343
<Compile Include="Win32\Security\Authentication\Kerberos\KerberosPreAuthenticationType.cs" />
343344
<Compile Include="Win32\Security\Authentication\Kerberos\KerberosTicketDecrypted.cs" />

NtApiDotNet/Win32/Security/Authentication/Kerberos/KerberosAPRequestAuthenticationToken.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public override KerberosAuthenticationToken Decrypt(KerberosKeySet keyset)
102102

103103
if (Authenticator.Decrypt(tmp_keys, Ticket.Realm, Ticket.ServerName, KeyUsage.ApReqAuthSubKey, out byte[] auth_decrypt))
104104
{
105-
if (!KerberosAuthenticator.Parse(Ticket, Authenticator, auth_decrypt, out authenticator))
105+
if (!KerberosAuthenticator.Parse(Ticket, Authenticator, auth_decrypt, keyset, out authenticator))
106106
{
107107
authenticator = null;
108108
}

NtApiDotNet/Win32/Security/Authentication/Kerberos/KerberosAuthenticator.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using NtApiDotNet.Utilities.ASN1;
1616
using System.Collections.Generic;
1717
using System.IO;
18+
using System.Linq;
1819
using System.Text;
1920

2021
namespace NtApiDotNet.Win32.Security.Authentication.Kerberos
@@ -102,7 +103,7 @@ private KerberosAuthenticator(KerberosEncryptedData orig_data)
102103
AuthenticatorVersion = 5;
103104
}
104105

105-
internal static bool Parse(KerberosTicket orig_ticket, KerberosEncryptedData orig_data, byte[] decrypted, out KerberosEncryptedData ticket)
106+
internal static bool Parse(KerberosTicket orig_ticket, KerberosEncryptedData orig_data, byte[] decrypted, KerberosKeySet keyset, out KerberosEncryptedData ticket)
106107
{
107108
ticket = null;
108109
try
@@ -137,7 +138,7 @@ internal static bool Parse(KerberosTicket orig_ticket, KerberosEncryptedData ori
137138
case 3:
138139
if (!next.Children[0].CheckSequence())
139140
return false;
140-
ret.Checksum = Kerberos.KerberosChecksum.Parse(next.Children[0]);
141+
ret.Checksum = KerberosChecksum.Parse(next.Children[0]);
141142
break;
142143
case 4:
143144
ret.ClientUSec = next.ReadChildInteger();
@@ -162,6 +163,18 @@ internal static bool Parse(KerberosTicket orig_ticket, KerberosEncryptedData ori
162163
return false;
163164
}
164165
}
166+
167+
if (ret.Checksum is KerberosChecksumGSSApi gssapi && gssapi.Credentials != null)
168+
{
169+
KerberosKeySet tmp_keyset = new KerberosKeySet(keyset.AsEnumerable() ?? new KerberosKey[0]);
170+
if (ret.SubKey != null)
171+
{
172+
tmp_keyset.Add(ret.SubKey);
173+
}
174+
175+
gssapi.Decrypt(tmp_keyset);
176+
}
177+
165178
ticket = ret;
166179
}
167180
catch (InvalidDataException)

NtApiDotNet/Win32/Security/Authentication/Kerberos/KerberosChecksumGssApi.cs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
using NtApiDotNet.Utilities.ASN1;
1516
using NtApiDotNet.Utilities.Text;
1617
using System;
1718
using System.IO;
@@ -60,7 +61,7 @@ public class KerberosChecksumGSSApi : KerberosChecksum
6061
/// <summary>
6162
/// KRB_CRED structure when in delegation.
6263
/// </summary>
63-
public byte[] KerbCredential { get; private set; }
64+
public KerberosCredential Credentials { get; private set; }
6465

6566
/// <summary>
6667
/// Additional extension data.
@@ -72,14 +73,11 @@ internal override void Format(StringBuilder builder)
7273
builder.AppendLine("Checksum : GSSAPI");
7374
builder.AppendLine($"Channel Binding : {NtObjectUtils.ToHexString(ChannelBinding)}");
7475
builder.AppendLine($"Context Flags : {ContextFlags}");
75-
if (KerbCredential != null)
76+
if (Credentials != null)
7677
{
7778
builder.AppendLine($"Delegate Opt ID : {DelegationOptionIdentifier}");
78-
HexDumpBuilder hex = new HexDumpBuilder(false, true, false, false, 0);
79-
hex.Append(KerbCredential);
80-
hex.Complete();
8179
builder.AppendLine("Kerb Credential :");
82-
builder.Append(hex.ToString());
80+
builder.AppendLine(Credentials.Format());
8381
}
8482
if (Extensions != null)
8583
{
@@ -95,6 +93,11 @@ private KerberosChecksumGSSApi(KerberosChecksumType type, byte[] data)
9593
{
9694
}
9795

96+
internal void Decrypt(KerberosKeySet keyset)
97+
{
98+
Credentials = (KerberosCredential)Credentials.Decrypt(keyset);
99+
}
100+
98101
internal static bool Parse(byte[] data, out KerberosChecksum checksum)
99102
{
100103
checksum = null;
@@ -111,7 +114,12 @@ internal static bool Parse(byte[] data, out KerberosChecksum checksum)
111114
{
112115
ret.DelegationOptionIdentifier = reader.ReadUInt16();
113116
int cred_length = reader.ReadUInt16();
114-
ret.KerbCredential = reader.ReadAllBytes(cred_length);
117+
byte[] cred = reader.ReadAllBytes(cred_length);
118+
119+
DERValue[] values = DERParser.ParseData(cred, 0);
120+
if (!KerberosCredential.TryParse(cred, values, out KerberosCredential cred_token))
121+
return false;
122+
ret.Credentials = cred_token;
115123
}
116124
if (reader.RemainingLength() > 0)
117125
{
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright 2020 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using NtApiDotNet.Utilities.ASN1;
16+
using System.Collections.Generic;
17+
using System.IO;
18+
using System.Text;
19+
20+
namespace NtApiDotNet.Win32.Security.Authentication.Kerberos
21+
{
22+
/// <summary>
23+
/// Class representing a KRB-CRED structure.
24+
/// </summary>
25+
public class KerberosCredential : KerberosAuthenticationToken
26+
{
27+
#region Public Properties
28+
/// <summary>
29+
/// List of tickets in this credential.
30+
/// </summary>
31+
public IReadOnlyList<KerberosTicket> Tickets { get; private set; }
32+
/// <summary>
33+
/// Encrypted part contains sesssion keys etc.
34+
/// </summary>
35+
public KerberosEncryptedData EncryptedPart { get; private set; }
36+
#endregion
37+
38+
private KerberosCredential(byte[] data, DERValue[] values)
39+
: base(data, values, KerberosMessageType.KRB_CRED)
40+
{
41+
}
42+
43+
/// <summary>
44+
/// Format the Authentication Token.
45+
/// </summary>
46+
/// <returns>The Formatted Token.</returns>
47+
public override string Format()
48+
{
49+
StringBuilder builder = new StringBuilder();
50+
builder.AppendLine($"<KerberosV{ProtocolVersion} {MessageType}>");
51+
for (int i = 0; i < Tickets.Count; ++i)
52+
{
53+
builder.AppendLine($"<Ticket {i}>");
54+
builder.Append(Tickets[i].Format());
55+
}
56+
builder.AppendLine("<Encrypted Part>");
57+
builder.Append(EncryptedPart.Format());
58+
return builder.ToString();
59+
}
60+
61+
/// <summary>
62+
/// Decrypt the Authentication Token using a keyset.
63+
/// </summary>
64+
/// <param name="keyset">The set of keys to decrypt the </param>
65+
/// <returns>The decrypted token, or the same token if nothing could be decrypted.</returns>
66+
public override KerberosAuthenticationToken Decrypt(KerberosKeySet keyset)
67+
{
68+
KerberosEncryptedData encdata = null;
69+
KerberosKeySet tmp_keys = new KerberosKeySet(keyset);
70+
List<KerberosTicket> dec_tickets = new List<KerberosTicket>();
71+
bool decrypted_ticket = false;
72+
foreach (var ticket in Tickets)
73+
{
74+
if (ticket.Decrypt(tmp_keys, KeyUsage.AsRepTgsRepTicket, out KerberosTicket dec_ticket))
75+
{
76+
dec_tickets.Add(dec_ticket);
77+
decrypted_ticket = true;
78+
}
79+
else
80+
{
81+
dec_tickets.Add(ticket);
82+
}
83+
}
84+
85+
if (EncryptedPart.Decrypt(tmp_keys, string.Empty, new KerberosPrincipalName(), KeyUsage.KrbCred, out byte[] decrypted))
86+
{
87+
// Needs session key from TGT request which we don't necessarily have.
88+
}
89+
90+
if (decrypted_ticket || encdata != null)
91+
{
92+
KerberosCredential ret = (KerberosCredential)MemberwiseClone();
93+
ret.Tickets = dec_tickets.AsReadOnly();
94+
ret.EncryptedPart = encdata ?? ret.EncryptedPart;
95+
return ret;
96+
}
97+
return base.Decrypt(keyset);
98+
}
99+
100+
/// <summary>
101+
/// Try and parse data into an ASN1 authentication token.
102+
/// </summary>
103+
/// <param name="data">The data to parse.</param>
104+
/// <param name="token">The Negotiate authentication token.</param>
105+
/// <param name="values">Parsed DER Values.</param>
106+
internal static bool TryParse(byte[] data, DERValue[] values, out KerberosCredential token)
107+
{
108+
token = null;
109+
try
110+
{
111+
var ret = new KerberosCredential(data, values);
112+
if (values.Length != 1 || !values[0].CheckMsg(KerberosMessageType.KRB_CRED) || !values[0].HasChildren())
113+
return false;
114+
115+
values = values[0].Children;
116+
if (values.Length != 1 || !values[0].CheckSequence() || !values[0].HasChildren())
117+
return false;
118+
119+
foreach (var next in values[0].Children)
120+
{
121+
if (next.Type != DERTagType.ContextSpecific)
122+
return false;
123+
switch (next.Tag)
124+
{
125+
case 0:
126+
if (next.ReadChildInteger() != 5)
127+
return false;
128+
break;
129+
case 1:
130+
if ((KerberosMessageType)next.ReadChildInteger() != KerberosMessageType.KRB_CRED)
131+
return false;
132+
break;
133+
case 2:
134+
if (!next.Children[0].CheckSequence())
135+
return false;
136+
List<KerberosTicket> tickets = new List<KerberosTicket>();
137+
foreach (var child in next.Children[0].Children)
138+
{
139+
tickets.Add(KerberosTicket.Parse(child));
140+
}
141+
ret.Tickets = tickets.AsReadOnly();
142+
break;
143+
case 3:
144+
if (!next.HasChildren())
145+
return false;
146+
ret.EncryptedPart = KerberosEncryptedData.Parse(next.Children[0]);
147+
break;
148+
default:
149+
return false;
150+
}
151+
}
152+
token = ret;
153+
return true;
154+
}
155+
catch (InvalidDataException)
156+
{
157+
return false;
158+
}
159+
}
160+
}
161+
}

0 commit comments

Comments
 (0)