Skip to content

Commit 5cc4538

Browse files
committed
Added Negotiate Token parsing.
1 parent 0c76ed5 commit 5cc4538

7 files changed

Lines changed: 437 additions & 9 deletions

File tree

NtApiDotNet/NtApiDotNet.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,9 @@
366366
<Compile Include="Win32\Security\Authentication\Kerberos\KerberosNameType.cs" />
367367
<Compile Include="Win32\Security\Authentication\Kerberos\Ndr\PacDeviceInfoParser.cs" />
368368
<Compile Include="Win32\Security\Authentication\Kerberos\PrincipalName.cs" />
369+
<Compile Include="Win32\Security\Authentication\Negotiate\NegotiateInitAuthenticationToken.cs" />
369370
<Compile Include="Win32\Security\Authentication\Negotiate\NegotiateAuthenticationToken.cs" />
371+
<Compile Include="Win32\Security\Authentication\Negotiate\NegotiateResponseAuthenticationToken.cs" />
370372
<Compile Include="Win32\Security\Authentication\Ntlm\NtlmAuthenticateAuthenticationTokenV2.cs" />
371373
<Compile Include="Win32\Security\Native\KERB_TICKET_LOGON.cs" />
372374
<Compile Include="Win32\Security\Native\SecPkgContextStructs.cs" />

NtApiDotNet/Utilities/ASN1/DERValue.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,15 @@ public int ReadChildInteger()
136136
return Children[0].ReadInteger();
137137
}
138138

139+
public int ReadChildEnumerated()
140+
{
141+
if (!HasChildren() || (!Children[0].CheckPrimitive(UniversalTag.ENUMERATED) && !Children[0].CheckPrimitive(UniversalTag.INTEGER)))
142+
{
143+
throw new InvalidDataException();
144+
}
145+
return Children[0].ReadInteger();
146+
}
147+
139148
public byte[] ReadChildOctetString()
140149
{
141150
if (!HasChildren() || !Children[0].CheckPrimitive(UniversalTag.OCTET_STRING))
@@ -154,6 +163,15 @@ public string ReadChildGeneralString()
154163
return Children[0].ReadGeneralString();
155164
}
156165

166+
public string ReadChildObjID()
167+
{
168+
if (!HasChildren() || !Children[0].CheckPrimitive(UniversalTag.OBJECT_IDENTIFIER))
169+
{
170+
throw new InvalidDataException();
171+
}
172+
return Children[0].ReadObjID();
173+
}
174+
157175
public string ReadChildGeneralizedTime()
158176
{
159177
if (!HasChildren() || !Children[0].CheckPrimitive(UniversalTag.GeneralizedTime))

NtApiDotNet/Utilities/ASN1/OIDValues.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,33 @@ internal static class OIDValues
2121
{
2222
internal const string KERBEROS_NAME = "1.2.840.113554.1.2.2.1";
2323
internal const string KERBEROS_PRINCIPAL = "1.2.840.113554.1.2.2.2";
24-
internal const string KERBEROS_USER_TO_USER_OID = "1.2.840.113554.1.2.2.3";
25-
internal const string KERBEROS_OID = "1.2.840.113554.1.2.2";
24+
internal const string KERBEROS_USER_TO_USER = "1.2.840.113554.1.2.2.3";
25+
internal const string KERBEROS = "1.2.840.113554.1.2.2";
2626
internal const string MS_KERBEROS = "1.2.840.48018.1.2.2";
2727
internal const string NTLM_SSP = "1.3.6.1.4.1.311.2.2.10";
2828
internal const string MS_NEGOX = "1.3.6.1.4.1.311.2.2.30";
29+
internal const string SPNEGO = "1.3.6.1.5.5.2";
30+
31+
public static string ToString(string oid)
32+
{
33+
switch (oid)
34+
{
35+
case KERBEROS:
36+
case KERBEROS_NAME:
37+
return "Kerberos";
38+
case KERBEROS_USER_TO_USER:
39+
return "Kerberos User to User";
40+
case MS_KERBEROS:
41+
return "Microsoft Kerberos";
42+
case NTLM_SSP:
43+
return "NTLM";
44+
case MS_NEGOX:
45+
return "Microsoft Negotiate Extended";
46+
case SPNEGO:
47+
return "SPNEGO";
48+
default:
49+
return "UNKNOWN OID";
50+
}
51+
}
2952
}
3053
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ internal static bool TryParse(byte[] data, int token_count, bool client, out Ker
8181

8282
switch (oid)
8383
{
84-
case OIDValues.KERBEROS_OID:
85-
case OIDValues.KERBEROS_USER_TO_USER_OID:
84+
case OIDValues.KERBEROS:
85+
case OIDValues.KERBEROS_USER_TO_USER:
8686
if (tok_id[0] == 1)
8787
{
8888
if (KerberosAPRequestAuthenticationToken.TryParse(data, values, out token))

NtApiDotNet/Win32/Security/Authentication/Negotiate/NegotiateAuthenticationToken.cs

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

15+
using NtApiDotNet.Utilities.ASN1;
16+
using NtApiDotNet.Win32.Security.Authentication.Kerberos;
17+
using NtApiDotNet.Win32.Security.Authentication.Ntlm;
18+
using System;
19+
using System.Collections;
20+
using System.Collections.Generic;
1521
using System.IO;
22+
using System.Text;
1623

1724
namespace NtApiDotNet.Win32.Security.Authentication.Negotiate
1825
{
1926
/// <summary>
2027
/// SPNEGO Authentication Token.
2128
/// </summary>
22-
public class NegotiateAuthenticationToken : ASN1AuthenticationToken
29+
public abstract class NegotiateAuthenticationToken : ASN1AuthenticationToken
2330
{
24-
internal NegotiateAuthenticationToken(byte[] data)
31+
/// <summary>
32+
/// The negotiated authentication token.
33+
/// </summary>
34+
public AuthenticationToken Token { get; private set; }
35+
36+
/// <summary>
37+
/// Optional message integrity code.
38+
/// </summary>
39+
public byte[] MessageIntegrityCode { get; }
40+
41+
/// <summary>
42+
/// Decrypt the Authentication Token using a keyset.
43+
/// </summary>
44+
/// <param name="keyset">The set of keys to decrypt the </param>
45+
/// <returns>The decrypted token, or the same token if nothing could be decrypted.</returns>
46+
public override AuthenticationToken Decrypt(IEnumerable<AuthenticationKey> keyset)
47+
{
48+
if (Token == null)
49+
return this;
50+
var ret = (NegotiateAuthenticationToken)MemberwiseClone();
51+
ret.Token = Token.Decrypt(keyset);
52+
return ret;
53+
}
54+
55+
/// <summary>
56+
/// Format the authentication token.
57+
/// </summary>
58+
/// <returns>The token as a formatted string.</returns>
59+
public override string Format()
60+
{
61+
StringBuilder builder = new StringBuilder();
62+
builder.AppendLine($"<SPNEGO {(this is NegotiateInitAuthenticationToken ? "Init" : "Response")}>");
63+
FormatData(builder);
64+
string token_format = Token?.Format();
65+
if (!string.IsNullOrWhiteSpace(token_format))
66+
{
67+
builder.AppendLine("<SPNEGO Token>");
68+
builder.AppendLine(token_format.TrimEnd());
69+
builder.AppendLine("</SPNEGO Token>");
70+
}
71+
return builder.ToString();
72+
}
73+
74+
private protected abstract void FormatData(StringBuilder builder);
75+
76+
private static AuthenticationToken ParseToken(byte[] data, int token_count, bool client)
77+
{
78+
if (NtlmAuthenticationToken.TryParse(data, token_count, client, out NtlmAuthenticationToken ntlm_token))
79+
{
80+
return ntlm_token;
81+
}
82+
83+
if (KerberosAuthenticationToken.TryParse(data, token_count, client, out KerberosAuthenticationToken kerb_token))
84+
{
85+
return kerb_token;
86+
}
87+
88+
return new AuthenticationToken(data);
89+
}
90+
91+
private static IEnumerable<string> ParseMechList(DERValue[] values)
92+
{
93+
List<string> mech_list = new List<string>();
94+
if (values.CheckValueSequence())
95+
{
96+
foreach (var next in values[0].Children)
97+
{
98+
if (!next.CheckPrimitive(UniversalTag.OBJECT_IDENTIFIER))
99+
{
100+
throw new InvalidDataException();
101+
}
102+
mech_list.Add(next.ReadObjID());
103+
}
104+
}
105+
return mech_list.AsReadOnly();
106+
}
107+
108+
private static NegotiateContextFlags ConvertContextFlags(BitArray flags)
109+
{
110+
if (flags.Length > 32)
111+
throw new InvalidDataException();
112+
int ret = 0;
113+
for (int i = 0; i < flags.Length; ++i)
114+
{
115+
if (flags[i])
116+
ret |= (1 << i);
117+
}
118+
return (NegotiateContextFlags)ret;
119+
}
120+
121+
private static bool ParseInit(byte[] data, DERValue[] values, int token_count, bool client, out NegotiateAuthenticationToken token)
122+
{
123+
token = null;
124+
if (!values.CheckValueSequence())
125+
{
126+
return false;
127+
}
128+
129+
IEnumerable<string> mech_list = null;
130+
NegotiateContextFlags flags = NegotiateContextFlags.None;
131+
AuthenticationToken auth_token = null;
132+
byte[] mic = null;
133+
134+
foreach (var next in values[0].Children)
135+
{
136+
if (next.Type != DERTagType.ContextSpecific)
137+
return false;
138+
switch (next.Tag)
139+
{
140+
case 0:
141+
mech_list = ParseMechList(next.Children);
142+
break;
143+
case 1:
144+
flags = ConvertContextFlags(next.ReadChildBitString());
145+
break;
146+
case 2:
147+
auth_token = ParseToken(next.ReadChildOctetString(), token_count, client);
148+
break;
149+
case 3:
150+
// If NegTokenInit2 then just ignore neg hints.
151+
if (next.HasChildren() && next.Children[0].CheckSequence())
152+
break;
153+
mic = next.ReadChildOctetString();
154+
break;
155+
case 4:
156+
// Used if NegTokenInit2.
157+
mic = next.ReadChildOctetString();
158+
break;
159+
default:
160+
return false;
161+
}
162+
}
163+
164+
token = new NegotiateInitAuthenticationToken(data, mech_list, flags, auth_token, mic);
165+
return true;
166+
}
167+
168+
private static bool ParseResp(byte[] data, DERValue[] values, int token_count, bool client, out NegotiateAuthenticationToken token)
169+
{
170+
token = null;
171+
if (!values.CheckValueSequence())
172+
{
173+
return false;
174+
}
175+
176+
string mech = null;
177+
NegotiateAuthenticationState state = NegotiateAuthenticationState.Reject;
178+
AuthenticationToken auth_token = null;
179+
byte[] mic = null;
180+
181+
foreach (var next in values[0].Children)
182+
{
183+
if (next.Type != DERTagType.ContextSpecific)
184+
return false;
185+
switch (next.Tag)
186+
{
187+
case 0:
188+
state = (NegotiateAuthenticationState)next.ReadChildEnumerated();
189+
break;
190+
case 1:
191+
mech = next.ReadChildObjID();
192+
break;
193+
case 2:
194+
auth_token = ParseToken(next.ReadChildOctetString(), token_count, client);
195+
break;
196+
case 3:
197+
mic = next.ReadChildOctetString();
198+
break;
199+
default:
200+
return false;
201+
}
202+
}
203+
204+
token = new NegotiateResponseAuthenticationToken(data, mech, state, auth_token, mic);
205+
return true;
206+
}
207+
208+
private protected NegotiateAuthenticationToken(byte[] data, AuthenticationToken token, byte[] mic)
25209
: base(data)
26210
{
211+
Token = token;
212+
MessageIntegrityCode = mic;
213+
}
214+
215+
#region Public Static Methods
216+
/// <summary>
217+
/// Parse bytes into a negotiate token.
218+
/// </summary>
219+
/// <param name="data">The negotiate token in bytes.</param>
220+
/// <returns>The Negotiate token.</returns>
221+
public static NegotiateAuthenticationToken Parse(byte[] data)
222+
{
223+
if (!TryParse(data, 0, false, out NegotiateAuthenticationToken token))
224+
{
225+
throw new ArgumentException(nameof(data));
226+
}
227+
return token;
27228
}
229+
#endregion
28230

29231
#region Internal Static Methods
30232
/// <summary>
@@ -40,13 +242,45 @@ internal static bool TryParse(byte[] data, int token_count, bool client, out Neg
40242
token = null;
41243
try
42244
{
43-
token = new NegotiateAuthenticationToken(data);
44-
return true;
245+
byte[] token_data;
246+
if (GSSAPIUtils.TryParse(data, out token_data, out string oid))
247+
{
248+
if (oid != OIDValues.SPNEGO)
249+
{
250+
return false;
251+
}
252+
}
253+
else
254+
{
255+
token_data = data;
256+
}
257+
258+
DERValue[] values = DERParser.ParseData(token_data, 0);
259+
if (values.Length != 1 || values[0].Type != DERTagType.ContextSpecific)
260+
{
261+
return false;
262+
}
263+
264+
if (values[0].CheckContext(0))
265+
{
266+
return ParseInit(data, values[0].Children, token_count, client, out token);
267+
}
268+
else if (values[0].CheckContext(1))
269+
{
270+
return ParseResp(data, values[0].Children, token_count, client, out token);
271+
}
272+
else
273+
{
274+
return false;
275+
}
45276
}
46277
catch (EndOfStreamException)
47278
{
48-
return false;
49279
}
280+
catch (InvalidDataException)
281+
{
282+
}
283+
return false;
50284
}
51285
#endregion
52286
}

0 commit comments

Comments
 (0)