This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4.9k
/
Copy pathNTAuthentication.Common.cs
328 lines (275 loc) · 12.2 KB
/
NTAuthentication.Common.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
using System.Net.Security;
using System.Runtime.InteropServices;
using System.Security.Authentication.ExtendedProtection;
namespace System.Net
{
internal partial class NTAuthentication
{
private bool _isServer;
private SafeFreeCredentials _credentialsHandle;
private SafeDeleteContext _securityContext;
private string _spn;
private int _tokenSize;
private ContextFlagsPal _requestedContextFlags;
private ContextFlagsPal _contextFlags;
private bool _isCompleted;
private string _package;
private string _lastProtocolName;
private string _protocolName;
private string _clientSpecifiedSpn;
private ChannelBinding _channelBinding;
// If set, no more calls should be made.
internal bool IsCompleted => _isCompleted;
internal bool IsValidContext => !(_securityContext == null || _securityContext.IsInvalid);
internal string Package => _package;
// True indicates this instance is for Server and will use AcceptSecurityContext SSPI API.
internal bool IsServer => _isServer;
internal string ClientSpecifiedSpn
{
get
{
if (_clientSpecifiedSpn == null)
{
_clientSpecifiedSpn = GetClientSpecifiedSpn();
}
return _clientSpecifiedSpn;
}
}
internal string ProtocolName
{
get
{
// Note: May return string.Empty if the auth is not done yet or failed.
if (_protocolName == null)
{
string negotiationAuthenticationPackage = null;
if (IsValidContext)
{
negotiationAuthenticationPackage = NegotiateStreamPal.QueryContextAuthenticationPackage(_securityContext);
if (IsCompleted)
{
_protocolName = negotiationAuthenticationPackage;
}
}
return negotiationAuthenticationPackage ?? string.Empty;
}
return _protocolName;
}
}
internal bool IsKerberos
{
get
{
if (_lastProtocolName == null)
{
_lastProtocolName = ProtocolName;
}
return (object)_lastProtocolName == (object)NegotiationInfoClass.Kerberos;
}
}
//
// This overload does not attempt to impersonate because the caller either did it already or the original thread context is still preserved.
//
internal NTAuthentication(bool isServer, string package, NetworkCredential credential, string spn, ContextFlagsPal requestedContextFlags, ChannelBinding channelBinding)
{
Initialize(isServer, package, credential, spn, requestedContextFlags, channelBinding);
}
private void Initialize(bool isServer, string package, NetworkCredential credential, string spn, ContextFlagsPal requestedContextFlags, ChannelBinding channelBinding)
{
if (NetEventSource.IsEnabled) NetEventSource.Enter(this, package, spn, requestedContextFlags);
_tokenSize = NegotiateStreamPal.QueryMaxTokenSize(package);
_isServer = isServer;
_spn = spn;
_securityContext = null;
_requestedContextFlags = requestedContextFlags;
_package = package;
_channelBinding = channelBinding;
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"Peer SPN-> '{_spn}'");
//
// Check if we're using DefaultCredentials.
//
Debug.Assert(CredentialCache.DefaultCredentials == CredentialCache.DefaultNetworkCredentials);
if (credential == CredentialCache.DefaultCredentials)
{
if (NetEventSource.IsEnabled) NetEventSource.Info(this, "using DefaultCredentials");
_credentialsHandle = NegotiateStreamPal.AcquireDefaultCredential(package, _isServer);
}
else
{
_credentialsHandle = NegotiateStreamPal.AcquireCredentialsHandle(package, _isServer, credential);
}
}
internal SafeDeleteContext GetContext(out SecurityStatusPal status)
{
status = new SecurityStatusPal(SecurityStatusPalErrorCode.OK);
if (!(IsCompleted && IsValidContext))
{
NetEventSource.Fail(this, "Should be called only when completed with success, currently is not!");
}
if (!IsServer)
{
NetEventSource.Fail(this, "The method must not be called by the client side!");
}
if (!IsValidContext)
{
status = new SecurityStatusPal(SecurityStatusPalErrorCode.InvalidHandle);
return null;
}
return _securityContext;
}
internal void CloseContext()
{
if (_securityContext != null && !_securityContext.IsClosed)
{
_securityContext.Dispose();
}
}
internal int VerifySignature(byte[] buffer, int offset, int count)
{
return NegotiateStreamPal.VerifySignature(_securityContext, buffer, offset, count);
}
internal int MakeSignature(byte[] buffer, int offset, int count, ref byte[] output)
{
return NegotiateStreamPal.MakeSignature(_securityContext, buffer, offset, count, ref output);
}
internal string GetOutgoingBlob(string incomingBlob)
{
byte[] decodedIncomingBlob = null;
if (incomingBlob != null && incomingBlob.Length > 0)
{
decodedIncomingBlob = Convert.FromBase64String(incomingBlob);
}
byte[] decodedOutgoingBlob = null;
if ((IsValidContext || IsCompleted) && decodedIncomingBlob == null)
{
// we tried auth previously, now we got a null blob, we're done. this happens
// with Kerberos & valid credentials on the domain but no ACLs on the resource
_isCompleted = true;
}
else
{
SecurityStatusPal statusCode;
decodedOutgoingBlob = GetOutgoingBlob(decodedIncomingBlob, true, out statusCode);
}
string outgoingBlob = null;
if (decodedOutgoingBlob != null && decodedOutgoingBlob.Length > 0)
{
outgoingBlob = Convert.ToBase64String(decodedOutgoingBlob);
}
if (IsCompleted)
{
CloseContext();
}
return outgoingBlob;
}
internal byte[] GetOutgoingBlob(byte[] incomingBlob, bool thrownOnError)
{
SecurityStatusPal statusCode;
return GetOutgoingBlob(incomingBlob, thrownOnError, out statusCode);
}
// Accepts an incoming binary security blob and returns an outgoing binary security blob.
internal byte[] GetOutgoingBlob(byte[] incomingBlob, bool throwOnError, out SecurityStatusPal statusCode)
{
if (NetEventSource.IsEnabled) NetEventSource.Enter(this, incomingBlob);
var result = new byte[_tokenSize];
bool firstTime = _securityContext == null;
try
{
if (!_isServer)
{
// client session
statusCode = NegotiateStreamPal.InitializeSecurityContext(
ref _credentialsHandle,
ref _securityContext,
_spn,
_requestedContextFlags,
incomingBlob,
_channelBinding,
ref result,
ref _contextFlags);
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"SSPIWrapper.InitializeSecurityContext() returns statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})");
if (statusCode.ErrorCode == SecurityStatusPalErrorCode.CompleteNeeded)
{
statusCode = NegotiateStreamPal.CompleteAuthToken(ref _securityContext, result);
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"SSPIWrapper.CompleteAuthToken() returns statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})");
result = null;
}
}
else
{
// Server session.
statusCode = NegotiateStreamPal.AcceptSecurityContext(
_credentialsHandle,
ref _securityContext,
_requestedContextFlags,
incomingBlob,
_channelBinding,
ref result,
ref _contextFlags);
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"SSPIWrapper.AcceptSecurityContext() returns statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})");
}
}
finally
{
//
// Assuming the ISC or ASC has referenced the credential on the first successful call,
// we want to decrement the effective ref count by "disposing" it.
// The real dispose will happen when the security context is closed.
// Note if the first call was not successful the handle is physically destroyed here.
//
if (firstTime)
{
_credentialsHandle?.Dispose();
}
}
if (((int)statusCode.ErrorCode >= (int)SecurityStatusPalErrorCode.OutOfMemory))
{
CloseContext();
_isCompleted = true;
if (throwOnError)
{
Exception exception = NegotiateStreamPal.CreateExceptionFromError(statusCode);
if (NetEventSource.IsEnabled) NetEventSource.Exit(this, exception);
throw exception;
}
if (NetEventSource.IsEnabled) NetEventSource.Exit(this, $"null statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode})");
return null;
}
else if (firstTime && _credentialsHandle != null)
{
// Cache until it is pushed out by newly incoming handles.
SSPIHandleCache.CacheCredential(_credentialsHandle);
}
// The return value will tell us correctly if the handshake is over or not
if (statusCode.ErrorCode == SecurityStatusPalErrorCode.OK)
{
// Success.
_isCompleted = true;
}
else if (NetEventSource.IsEnabled)
{
// We need to continue.
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"need continue statusCode:0x{((int)statusCode.ErrorCode):x8} ({statusCode}) _securityContext:{_securityContext}");
}
if (NetEventSource.IsEnabled)
{
if (NetEventSource.IsEnabled) NetEventSource.Exit(this, $"IsCompleted: {IsCompleted}");
}
return result;
}
private string GetClientSpecifiedSpn()
{
if (!(IsValidContext && IsCompleted))
{
NetEventSource.Fail(this, "Trying to get the client SPN before handshaking is done!");
}
string spn = NegotiateStreamPal.QueryContextClientSpecifiedSpn(_securityContext);
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"The client specified SPN is [{spn}]");
return spn;
}
}
}