| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System.Buffers; |
| | | 5 | | using System.Buffers.Binary; |
| | | 6 | | using System.ComponentModel; |
| | | 7 | | using System.Diagnostics; |
| | | 8 | | using System.Diagnostics.CodeAnalysis; |
| | | 9 | | using System.IO; |
| | | 10 | | using System.Runtime.CompilerServices; |
| | | 11 | | using System.Runtime.ExceptionServices; |
| | | 12 | | using System.Runtime.Versioning; |
| | | 13 | | using System.Security.Authentication; |
| | | 14 | | using System.Security.Authentication.ExtendedProtection; |
| | | 15 | | using System.Security.Principal; |
| | | 16 | | using System.Threading; |
| | | 17 | | using System.Threading.Tasks; |
| | | 18 | | |
| | | 19 | | namespace System.Net.Security |
| | | 20 | | { |
| | | 21 | | /// <summary> |
| | | 22 | | /// Provides a stream that uses the Negotiate security protocol to authenticate the client, and optionally the serve |
| | | 23 | | /// </summary> |
| | | 24 | | public partial class NegotiateStream : AuthenticatedStream |
| | | 25 | | { |
| | | 26 | | /// <summary>Set as the _exception when the instance is disposed.</summary> |
| | 0 | 27 | | private static readonly ExceptionDispatchInfo s_disposedSentinel = ExceptionDispatchInfo.Capture(new ObjectDispo |
| | | 28 | | |
| | | 29 | | private const int ERROR_TRUST_FAILURE = 1790; // Used to serialize protectionLevel or impersonationLevel misma |
| | | 30 | | private const int MaxReadFrameSize = 64 * 1024; |
| | | 31 | | private const int MaxWriteDataSize = 63 * 1024; // 1k for the framing and trailer that is always less as per SSP |
| | | 32 | | private const string DefaultPackage = NegotiationInfoClass.Negotiate; |
| | | 33 | | |
| | | 34 | | #pragma warning disable CA1825 // used in reference comparison, requires unique object identity |
| | 0 | 35 | | private static readonly byte[] s_emptyMessage = new byte[0]; |
| | | 36 | | #pragma warning restore CA1825 |
| | | 37 | | |
| | | 38 | | private readonly byte[] _writeHeader; |
| | | 39 | | private readonly byte[] _readHeader; |
| | | 40 | | private byte[] _readBuffer; |
| | | 41 | | private int _readBufferOffset; |
| | | 42 | | private int _readBufferCount; |
| | | 43 | | private ArrayBufferWriter<byte>? _writeBuffer; |
| | | 44 | | |
| | | 45 | | private volatile bool _writeInProgress; |
| | | 46 | | private volatile bool _readInProgress; |
| | | 47 | | private volatile bool _authInProgress; |
| | | 48 | | |
| | | 49 | | private ExceptionDispatchInfo? _exception; |
| | | 50 | | private StreamFramer? _framer; |
| | | 51 | | private NegotiateAuthentication? _context; |
| | | 52 | | private bool _canRetryAuthentication; |
| | | 53 | | private ProtectionLevel _expectedProtectionLevel; |
| | | 54 | | private TokenImpersonationLevel _expectedImpersonationLevel; |
| | | 55 | | private ExtendedProtectionPolicy? _extendedProtectionPolicy; |
| | | 56 | | |
| | | 57 | | private bool isNtlm; |
| | | 58 | | |
| | | 59 | | /// <summary> |
| | | 60 | | /// SSPI does not send a server ack on successful auth. |
| | | 61 | | /// This is a state variable used to gracefully handle auth confirmation. |
| | | 62 | | /// </summary> |
| | | 63 | | private bool _remoteOk; |
| | | 64 | | |
| | 0 | 65 | | public NegotiateStream(Stream innerStream) : this(innerStream, false) |
| | 0 | 66 | | { |
| | 0 | 67 | | } |
| | | 68 | | |
| | 0 | 69 | | public NegotiateStream(Stream innerStream, bool leaveInnerStreamOpen) : base(innerStream, leaveInnerStreamOpen) |
| | 0 | 70 | | { |
| | 0 | 71 | | _writeHeader = new byte[4]; |
| | 0 | 72 | | _readHeader = new byte[4]; |
| | 0 | 73 | | _readBuffer = Array.Empty<byte>(); |
| | 0 | 74 | | } |
| | | 75 | | |
| | | 76 | | protected override void Dispose(bool disposing) |
| | 0 | 77 | | { |
| | | 78 | | try |
| | 0 | 79 | | { |
| | 0 | 80 | | _exception = s_disposedSentinel; |
| | 0 | 81 | | _context?.Dispose(); |
| | 0 | 82 | | } |
| | | 83 | | finally |
| | 0 | 84 | | { |
| | 0 | 85 | | base.Dispose(disposing); |
| | 0 | 86 | | } |
| | 0 | 87 | | } |
| | | 88 | | |
| | | 89 | | public override async ValueTask DisposeAsync() |
| | 0 | 90 | | { |
| | | 91 | | try |
| | 0 | 92 | | { |
| | 0 | 93 | | _exception = s_disposedSentinel; |
| | 0 | 94 | | _context?.Dispose(); |
| | 0 | 95 | | } |
| | | 96 | | finally |
| | 0 | 97 | | { |
| | 0 | 98 | | await base.DisposeAsync().ConfigureAwait(false); |
| | 0 | 99 | | } |
| | 0 | 100 | | } |
| | | 101 | | |
| | | 102 | | public virtual IAsyncResult BeginAuthenticateAsClient(AsyncCallback? asyncCallback, object? asyncState) => |
| | 0 | 103 | | BeginAuthenticateAsClient((NetworkCredential)CredentialCache.DefaultCredentials, binding: null, string.Empty |
| | 0 | 104 | | asyncCallback, asyncState); |
| | | 105 | | |
| | | 106 | | public virtual IAsyncResult BeginAuthenticateAsClient(NetworkCredential credential, string targetName, AsyncCall |
| | 0 | 107 | | BeginAuthenticateAsClient(credential, binding: null, targetName, ProtectionLevel.EncryptAndSign, TokenImpers |
| | 0 | 108 | | asyncCallback, asyncState); |
| | | 109 | | |
| | | 110 | | public virtual IAsyncResult BeginAuthenticateAsClient(NetworkCredential credential, ChannelBinding? binding, str |
| | 0 | 111 | | BeginAuthenticateAsClient(credential, binding, targetName, ProtectionLevel.EncryptAndSign, TokenImpersonatio |
| | 0 | 112 | | asyncCallback, asyncState); |
| | | 113 | | |
| | | 114 | | public virtual IAsyncResult BeginAuthenticateAsClient( |
| | | 115 | | NetworkCredential credential, string targetName, ProtectionLevel requiredProtectionLevel, TokenImpersonation |
| | | 116 | | AsyncCallback? asyncCallback, object? asyncState) => |
| | 0 | 117 | | BeginAuthenticateAsClient(credential, binding: null, targetName, requiredProtectionLevel, allowedImpersonati |
| | 0 | 118 | | asyncCallback, asyncState); |
| | | 119 | | |
| | | 120 | | public virtual IAsyncResult BeginAuthenticateAsClient( |
| | | 121 | | NetworkCredential credential, ChannelBinding? binding, string targetName, ProtectionLevel requiredProtection |
| | | 122 | | AsyncCallback? asyncCallback, object? asyncState) => |
| | 0 | 123 | | TaskToAsyncResult.Begin(AuthenticateAsClientAsync(credential, binding, targetName, requiredProtectionLevel, |
| | | 124 | | |
| | 0 | 125 | | public virtual void EndAuthenticateAsClient(IAsyncResult asyncResult) => TaskToAsyncResult.End(asyncResult); |
| | | 126 | | |
| | | 127 | | public virtual void AuthenticateAsServer() => |
| | 0 | 128 | | AuthenticateAsServer((NetworkCredential)CredentialCache.DefaultCredentials, policy: null, ProtectionLevel.En |
| | | 129 | | |
| | | 130 | | public virtual void AuthenticateAsServer(ExtendedProtectionPolicy? policy) => |
| | 0 | 131 | | AuthenticateAsServer((NetworkCredential)CredentialCache.DefaultCredentials, policy, ProtectionLevel.EncryptA |
| | | 132 | | |
| | | 133 | | public virtual void AuthenticateAsServer(NetworkCredential credential, ProtectionLevel requiredProtectionLevel, |
| | 0 | 134 | | AuthenticateAsServer(credential, policy: null, requiredProtectionLevel, requiredImpersonationLevel); |
| | | 135 | | |
| | | 136 | | public virtual void AuthenticateAsServer(NetworkCredential credential, ExtendedProtectionPolicy? policy, Protect |
| | 0 | 137 | | { |
| | 0 | 138 | | ValidateCreateContext(DefaultPackage, credential, string.Empty, policy, requiredProtectionLevel, requiredImp |
| | 0 | 139 | | AuthenticateAsync<SyncReadWriteAdapter>(default(CancellationToken)).GetAwaiter().GetResult(); |
| | 0 | 140 | | } |
| | | 141 | | |
| | | 142 | | public virtual IAsyncResult BeginAuthenticateAsServer(AsyncCallback? asyncCallback, object? asyncState) => |
| | 0 | 143 | | BeginAuthenticateAsServer((NetworkCredential)CredentialCache.DefaultCredentials, policy: null, ProtectionLev |
| | | 144 | | |
| | | 145 | | public virtual IAsyncResult BeginAuthenticateAsServer(ExtendedProtectionPolicy? policy, AsyncCallback? asyncCall |
| | 0 | 146 | | BeginAuthenticateAsServer((NetworkCredential)CredentialCache.DefaultCredentials, policy, ProtectionLevel.Enc |
| | | 147 | | |
| | | 148 | | public virtual IAsyncResult BeginAuthenticateAsServer( |
| | | 149 | | NetworkCredential credential, ProtectionLevel requiredProtectionLevel, TokenImpersonationLevel requiredImper |
| | | 150 | | AsyncCallback? asyncCallback, object? asyncState) => |
| | 0 | 151 | | BeginAuthenticateAsServer(credential, policy: null, requiredProtectionLevel, requiredImpersonationLevel, asy |
| | | 152 | | |
| | | 153 | | public virtual IAsyncResult BeginAuthenticateAsServer( |
| | | 154 | | NetworkCredential credential, ExtendedProtectionPolicy? policy, ProtectionLevel requiredProtectionLevel, Tok |
| | | 155 | | AsyncCallback? asyncCallback, object? asyncState) => |
| | 0 | 156 | | TaskToAsyncResult.Begin(AuthenticateAsServerAsync(credential, policy, requiredProtectionLevel, requiredImper |
| | | 157 | | |
| | 0 | 158 | | public virtual void EndAuthenticateAsServer(IAsyncResult asyncResult) => TaskToAsyncResult.End(asyncResult); |
| | | 159 | | |
| | | 160 | | public virtual void AuthenticateAsClient() => |
| | 0 | 161 | | AuthenticateAsClient((NetworkCredential)CredentialCache.DefaultCredentials, binding: null, string.Empty, Pro |
| | | 162 | | |
| | | 163 | | public virtual void AuthenticateAsClient(NetworkCredential credential, string targetName) => |
| | 0 | 164 | | AuthenticateAsClient(credential, binding: null, targetName, ProtectionLevel.EncryptAndSign, TokenImpersonati |
| | | 165 | | |
| | | 166 | | public virtual void AuthenticateAsClient(NetworkCredential credential, ChannelBinding? binding, string targetNam |
| | 0 | 167 | | AuthenticateAsClient(credential, binding, targetName, ProtectionLevel.EncryptAndSign, TokenImpersonationLeve |
| | | 168 | | |
| | | 169 | | public virtual void AuthenticateAsClient( |
| | | 170 | | NetworkCredential credential, string targetName, ProtectionLevel requiredProtectionLevel, TokenImpersonation |
| | 0 | 171 | | AuthenticateAsClient(credential, binding: null, targetName, requiredProtectionLevel, allowedImpersonationLev |
| | | 172 | | |
| | | 173 | | public virtual void AuthenticateAsClient( |
| | | 174 | | NetworkCredential credential, ChannelBinding? binding, string targetName, ProtectionLevel requiredProtection |
| | 0 | 175 | | { |
| | 0 | 176 | | ValidateCreateContext(DefaultPackage, isServer: false, credential, targetName, binding, requiredProtectionLe |
| | 0 | 177 | | AuthenticateAsync<SyncReadWriteAdapter>(default(CancellationToken)).GetAwaiter().GetResult(); |
| | 0 | 178 | | } |
| | | 179 | | |
| | | 180 | | public virtual Task AuthenticateAsClientAsync() => |
| | 0 | 181 | | AuthenticateAsClientAsync((NetworkCredential)CredentialCache.DefaultCredentials, binding: null, string.Empty |
| | | 182 | | |
| | | 183 | | public virtual Task AuthenticateAsClientAsync(NetworkCredential credential, string targetName) => |
| | 0 | 184 | | AuthenticateAsClientAsync(credential, binding: null, targetName, ProtectionLevel.EncryptAndSign, TokenImpers |
| | | 185 | | |
| | | 186 | | public virtual Task AuthenticateAsClientAsync( |
| | | 187 | | NetworkCredential credential, string targetName, |
| | | 188 | | ProtectionLevel requiredProtectionLevel, |
| | | 189 | | TokenImpersonationLevel allowedImpersonationLevel) => |
| | 0 | 190 | | AuthenticateAsClientAsync(credential, binding: null, targetName, requiredProtectionLevel, allowedImpersonati |
| | | 191 | | |
| | | 192 | | public virtual Task AuthenticateAsClientAsync(NetworkCredential credential, ChannelBinding? binding, string targ |
| | 0 | 193 | | AuthenticateAsClientAsync(credential, binding, targetName, ProtectionLevel.EncryptAndSign, TokenImpersonatio |
| | | 194 | | |
| | | 195 | | public virtual Task AuthenticateAsClientAsync( |
| | | 196 | | NetworkCredential credential, ChannelBinding? binding, string targetName, ProtectionLevel requiredProtection |
| | | 197 | | TokenImpersonationLevel allowedImpersonationLevel) |
| | 0 | 198 | | { |
| | 0 | 199 | | ValidateCreateContext(DefaultPackage, isServer: false, credential, targetName, binding, requiredProtectionLe |
| | 0 | 200 | | return AuthenticateAsync<AsyncReadWriteAdapter>(default(CancellationToken)); |
| | 0 | 201 | | } |
| | | 202 | | |
| | | 203 | | public virtual Task AuthenticateAsServerAsync() => |
| | 0 | 204 | | AuthenticateAsServerAsync((NetworkCredential)CredentialCache.DefaultCredentials, policy: null, ProtectionLev |
| | | 205 | | |
| | | 206 | | public virtual Task AuthenticateAsServerAsync(ExtendedProtectionPolicy? policy) => |
| | 0 | 207 | | AuthenticateAsServerAsync((NetworkCredential)CredentialCache.DefaultCredentials, policy, ProtectionLevel.Enc |
| | | 208 | | |
| | | 209 | | public virtual Task AuthenticateAsServerAsync(NetworkCredential credential, ProtectionLevel requiredProtectionLe |
| | 0 | 210 | | AuthenticateAsServerAsync(credential, policy: null, requiredProtectionLevel, requiredImpersonationLevel); |
| | | 211 | | |
| | | 212 | | public virtual Task AuthenticateAsServerAsync( |
| | | 213 | | NetworkCredential credential, ExtendedProtectionPolicy? policy, ProtectionLevel requiredProtectionLevel, Tok |
| | 0 | 214 | | { |
| | 0 | 215 | | ValidateCreateContext(DefaultPackage, credential, string.Empty, policy, requiredProtectionLevel, requiredImp |
| | 0 | 216 | | return AuthenticateAsync<AsyncReadWriteAdapter>(default(CancellationToken)); |
| | 0 | 217 | | } |
| | | 218 | | |
| | 0 | 219 | | public override bool IsAuthenticated => IsAuthenticatedCore; |
| | | 220 | | |
| | | 221 | | [MemberNotNullWhen(true, nameof(_context))] |
| | 0 | 222 | | private bool IsAuthenticatedCore => _context != null && HandshakeComplete && _exception == null && _remoteOk; |
| | | 223 | | |
| | 0 | 224 | | public override bool IsMutuallyAuthenticated => IsAuthenticatedCore && _context.IsMutuallyAuthenticated; |
| | | 225 | | |
| | 0 | 226 | | public override bool IsEncrypted => IsAuthenticatedCore && _context.IsEncrypted; |
| | | 227 | | |
| | 0 | 228 | | public override bool IsSigned => IsAuthenticatedCore && (_context.IsSigned || _context.IsEncrypted); |
| | | 229 | | |
| | 0 | 230 | | public override bool IsServer => _context != null && _context.IsServer; |
| | | 231 | | |
| | | 232 | | public virtual TokenImpersonationLevel ImpersonationLevel |
| | | 233 | | { |
| | | 234 | | get |
| | 0 | 235 | | { |
| | 0 | 236 | | ThrowIfFailed(authSuccessCheck: true); |
| | 0 | 237 | | return PrivateImpersonationLevel; |
| | 0 | 238 | | } |
| | | 239 | | } |
| | | 240 | | |
| | 0 | 241 | | private TokenImpersonationLevel PrivateImpersonationLevel => _context!.ImpersonationLevel; |
| | | 242 | | |
| | 0 | 243 | | private bool HandshakeComplete => _context!.IsAuthenticated; |
| | | 244 | | |
| | 0 | 245 | | private bool CanGetSecureStream => _context!.IsEncrypted || _context.IsSigned; |
| | | 246 | | |
| | | 247 | | public virtual IIdentity RemoteIdentity |
| | | 248 | | { |
| | | 249 | | get |
| | 0 | 250 | | { |
| | 0 | 251 | | ThrowIfFailed(authSuccessCheck: true); |
| | 0 | 252 | | return _context!.RemoteIdentity; |
| | 0 | 253 | | } |
| | | 254 | | } |
| | | 255 | | |
| | 0 | 256 | | public override bool CanSeek => false; |
| | | 257 | | |
| | 0 | 258 | | public override bool CanRead => IsAuthenticated && InnerStream.CanRead; |
| | | 259 | | |
| | 0 | 260 | | public override bool CanTimeout => InnerStream.CanTimeout; |
| | | 261 | | |
| | 0 | 262 | | public override bool CanWrite => IsAuthenticated && InnerStream.CanWrite; |
| | | 263 | | |
| | | 264 | | public override int ReadTimeout |
| | | 265 | | { |
| | 0 | 266 | | get => InnerStream.ReadTimeout; |
| | 0 | 267 | | set => InnerStream.ReadTimeout = value; |
| | | 268 | | } |
| | | 269 | | |
| | | 270 | | public override int WriteTimeout |
| | | 271 | | { |
| | 0 | 272 | | get => InnerStream.WriteTimeout; |
| | 0 | 273 | | set => InnerStream.WriteTimeout = value; |
| | | 274 | | } |
| | | 275 | | |
| | 0 | 276 | | public override long Length => InnerStream.Length; |
| | | 277 | | |
| | | 278 | | public override long Position |
| | | 279 | | { |
| | 0 | 280 | | get => InnerStream.Position; |
| | 0 | 281 | | set => throw new NotSupportedException(SR.net_noseek); |
| | | 282 | | } |
| | | 283 | | |
| | | 284 | | public override void SetLength(long value) => |
| | 0 | 285 | | InnerStream.SetLength(value); |
| | | 286 | | |
| | | 287 | | public override long Seek(long offset, SeekOrigin origin) => |
| | 0 | 288 | | throw new NotSupportedException(SR.net_noseek); |
| | | 289 | | |
| | | 290 | | public override void Flush() => |
| | 0 | 291 | | InnerStream.Flush(); |
| | | 292 | | |
| | | 293 | | public override Task FlushAsync(CancellationToken cancellationToken) => |
| | 0 | 294 | | InnerStream.FlushAsync(cancellationToken); |
| | | 295 | | |
| | | 296 | | public override int Read(byte[] buffer, int offset, int count) |
| | 0 | 297 | | { |
| | 0 | 298 | | ValidateBufferArguments(buffer, offset, count); |
| | | 299 | | |
| | 0 | 300 | | ThrowIfFailed(authSuccessCheck: true); |
| | 0 | 301 | | if (!CanGetSecureStream) |
| | 0 | 302 | | { |
| | 0 | 303 | | return InnerStream.Read(buffer, offset, count); |
| | | 304 | | } |
| | | 305 | | |
| | 0 | 306 | | ValueTask<int> vt = ReadAsync<SyncReadWriteAdapter>(new Memory<byte>(buffer, offset, count), default(Cancell |
| | 0 | 307 | | Debug.Assert(vt.IsCompleted, "Should have completed synchroously with sync adapter"); |
| | 0 | 308 | | return vt.GetAwaiter().GetResult(); |
| | 0 | 309 | | } |
| | | 310 | | |
| | | 311 | | public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) |
| | 0 | 312 | | { |
| | 0 | 313 | | ValidateBufferArguments(buffer, offset, count); |
| | | 314 | | |
| | 0 | 315 | | ThrowIfFailed(authSuccessCheck: true); |
| | 0 | 316 | | if (!CanGetSecureStream) |
| | 0 | 317 | | { |
| | 0 | 318 | | return InnerStream.ReadAsync(buffer, offset, count, cancellationToken); |
| | | 319 | | } |
| | | 320 | | |
| | 0 | 321 | | return ReadAsync<AsyncReadWriteAdapter>(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask() |
| | 0 | 322 | | } |
| | | 323 | | |
| | | 324 | | public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) |
| | 0 | 325 | | { |
| | 0 | 326 | | ThrowIfFailed(authSuccessCheck: true); |
| | 0 | 327 | | if (!CanGetSecureStream) |
| | 0 | 328 | | { |
| | 0 | 329 | | return InnerStream.ReadAsync(buffer, cancellationToken); |
| | | 330 | | } |
| | | 331 | | |
| | 0 | 332 | | return ReadAsync<AsyncReadWriteAdapter>(buffer, cancellationToken); |
| | 0 | 333 | | } |
| | | 334 | | |
| | | 335 | | private async ValueTask<int> ReadAsync<TIOAdapter>(Memory<byte> buffer, CancellationToken cancellationToken) |
| | | 336 | | where TIOAdapter : IReadWriteAdapter |
| | 0 | 337 | | { |
| | 0 | 338 | | Debug.Assert(_context is not null); |
| | | 339 | | |
| | 0 | 340 | | if (Interlocked.Exchange(ref _readInProgress, true)) |
| | 0 | 341 | | { |
| | 0 | 342 | | throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, "read")); |
| | | 343 | | } |
| | | 344 | | |
| | | 345 | | try |
| | 0 | 346 | | { |
| | 0 | 347 | | ThrowIfFailed(authSuccessCheck: true); |
| | | 348 | | |
| | 0 | 349 | | if (_readBufferCount != 0) |
| | 0 | 350 | | { |
| | 0 | 351 | | int copyBytes = Math.Min(_readBufferCount, buffer.Length); |
| | 0 | 352 | | if (copyBytes != 0) |
| | 0 | 353 | | { |
| | 0 | 354 | | _readBuffer.AsMemory(_readBufferOffset, copyBytes).CopyTo(buffer); |
| | 0 | 355 | | _readBufferOffset += copyBytes; |
| | 0 | 356 | | _readBufferCount -= copyBytes; |
| | 0 | 357 | | } |
| | 0 | 358 | | return copyBytes; |
| | | 359 | | } |
| | | 360 | | |
| | 0 | 361 | | while (true) |
| | 0 | 362 | | { |
| | 0 | 363 | | int readBytes = await ReadAllAsync(InnerStream, _readHeader, allowZeroRead: true, cancellationToken) |
| | 0 | 364 | | if (readBytes == 0) |
| | 0 | 365 | | { |
| | 0 | 366 | | return 0; |
| | | 367 | | } |
| | | 368 | | |
| | | 369 | | // Replace readBytes with the body size recovered from the header content. |
| | 0 | 370 | | readBytes = BinaryPrimitives.ReadInt32LittleEndian(_readHeader); |
| | | 371 | | |
| | | 372 | | // The body carries 4 bytes for trailer size slot plus trailer, hence <= 4 frame size is always an e |
| | | 373 | | // Additionally we'd like to restrict the read frame size to 64k. |
| | 0 | 374 | | if (readBytes <= 4 || readBytes > MaxReadFrameSize) |
| | 0 | 375 | | { |
| | 0 | 376 | | throw new IOException(SR.net_frame_read_size); |
| | | 377 | | } |
| | | 378 | | |
| | | 379 | | // Always pass InternalBuffer for SSPI "in place" decryption. |
| | | 380 | | // A user buffer can be shared by many threads in that case decryption/integrity check may fail caus |
| | 0 | 381 | | if (_readBuffer.Length < readBytes) |
| | 0 | 382 | | { |
| | 0 | 383 | | _readBuffer = new byte[readBytes]; |
| | 0 | 384 | | } |
| | | 385 | | |
| | | 386 | | // Note: do not assign _readBufferCount/_readBufferOffset before the read completes successfully. |
| | | 387 | | // If the read throws (e.g. due to the connection closing), a subsequent Read call would otherwise |
| | | 388 | | // observe a non-zero _readBufferCount and return stale/undecrypted buffer contents. |
| | 0 | 389 | | readBytes = await ReadAllAsync(InnerStream, new Memory<byte>(_readBuffer, 0, readBytes), allowZeroRe |
| | | 390 | | |
| | | 391 | | // Decrypt into the same buffer (decrypted data size can be shrunk after decryption). |
| | | 392 | | // Use locals so that on failure we do not leave _readBufferOffset/_readBufferCount in a state that |
| | | 393 | | // would expose stale or undecrypted data on a subsequent Read call. |
| | | 394 | | NegotiateAuthenticationStatusCode statusCode; |
| | 0 | 395 | | int decryptedOffset = 0; |
| | 0 | 396 | | int decryptedCount = 0; |
| | 0 | 397 | | if (isNtlm && !_context.IsEncrypted) |
| | 0 | 398 | | { |
| | | 399 | | // Non-encrypted NTLM uses an encoding quirk |
| | | 400 | | const int NtlmSignatureLength = 16; |
| | | 401 | | |
| | 0 | 402 | | if (readBytes < NtlmSignatureLength || |
| | 0 | 403 | | !_context.VerifyIntegrityCheck(_readBuffer.AsSpan(NtlmSignatureLength, readBytes - NtlmSigna |
| | 0 | 404 | | { |
| | 0 | 405 | | statusCode = NegotiateAuthenticationStatusCode.InvalidToken; |
| | 0 | 406 | | } |
| | | 407 | | else |
| | 0 | 408 | | { |
| | 0 | 409 | | decryptedOffset = NtlmSignatureLength; |
| | 0 | 410 | | decryptedCount = readBytes - NtlmSignatureLength; |
| | 0 | 411 | | statusCode = NegotiateAuthenticationStatusCode.Completed; |
| | 0 | 412 | | } |
| | 0 | 413 | | } |
| | | 414 | | else |
| | 0 | 415 | | { |
| | 0 | 416 | | statusCode = _context.UnwrapInPlace(_readBuffer.AsSpan(0, readBytes), out decryptedOffset, out d |
| | 0 | 417 | | } |
| | | 418 | | |
| | 0 | 419 | | if (statusCode != NegotiateAuthenticationStatusCode.Completed) |
| | 0 | 420 | | { |
| | | 421 | | // TODO: Better exception |
| | 0 | 422 | | throw new IOException(SR.net_io_read); |
| | | 423 | | } |
| | | 424 | | |
| | 0 | 425 | | _readBufferOffset = decryptedOffset; |
| | 0 | 426 | | _readBufferCount = decryptedCount; |
| | | 427 | | |
| | | 428 | | // Decrypted data can be shrunk after decryption. |
| | 0 | 429 | | if (_readBufferCount == 0 && buffer.Length != 0) |
| | 0 | 430 | | { |
| | | 431 | | // Read again. |
| | 0 | 432 | | continue; |
| | | 433 | | } |
| | | 434 | | |
| | 0 | 435 | | int copyBytes = Math.Min(_readBufferCount, buffer.Length); |
| | 0 | 436 | | _readBuffer.AsMemory(_readBufferOffset, copyBytes).CopyTo(buffer); |
| | 0 | 437 | | _readBufferOffset += copyBytes; |
| | 0 | 438 | | _readBufferCount -= copyBytes; |
| | | 439 | | |
| | 0 | 440 | | return copyBytes; |
| | | 441 | | } |
| | | 442 | | } |
| | 0 | 443 | | catch (Exception e) when (!(e is IOException || e is OperationCanceledException)) |
| | 0 | 444 | | { |
| | 0 | 445 | | throw new IOException(SR.net_io_read, e); |
| | | 446 | | } |
| | | 447 | | finally |
| | 0 | 448 | | { |
| | 0 | 449 | | _readInProgress = false; |
| | 0 | 450 | | } |
| | | 451 | | |
| | | 452 | | static async ValueTask<int> ReadAllAsync(Stream stream, Memory<byte> buffer, bool allowZeroRead, Cancellatio |
| | 0 | 453 | | { |
| | 0 | 454 | | int read = await TIOAdapter.ReadAtLeastAsync( |
| | 0 | 455 | | stream, buffer, buffer.Length, throwOnEndOfStream: false, cancellationToken).ConfigureAwait(false); |
| | 0 | 456 | | if (read < buffer.Length) |
| | 0 | 457 | | { |
| | 0 | 458 | | if (read != 0 || !allowZeroRead) |
| | 0 | 459 | | { |
| | 0 | 460 | | throw new IOException(SR.net_io_eof); |
| | | 461 | | } |
| | 0 | 462 | | } |
| | | 463 | | |
| | 0 | 464 | | return read; |
| | 0 | 465 | | } |
| | 0 | 466 | | } |
| | | 467 | | |
| | | 468 | | public override void Write(byte[] buffer, int offset, int count) |
| | 0 | 469 | | { |
| | 0 | 470 | | ValidateBufferArguments(buffer, offset, count); |
| | | 471 | | |
| | 0 | 472 | | ThrowIfFailed(authSuccessCheck: true); |
| | 0 | 473 | | if (!CanGetSecureStream) |
| | 0 | 474 | | { |
| | 0 | 475 | | InnerStream.Write(buffer, offset, count); |
| | 0 | 476 | | return; |
| | | 477 | | } |
| | | 478 | | |
| | 0 | 479 | | WriteAsync<SyncReadWriteAdapter>(new ReadOnlyMemory<byte>(buffer, offset, count), default(CancellationToken) |
| | 0 | 480 | | } |
| | | 481 | | |
| | | 482 | | /// <returns>A <see cref="Task"/> that represents the asynchronous read operation.</returns> |
| | | 483 | | public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) |
| | 0 | 484 | | { |
| | 0 | 485 | | ValidateBufferArguments(buffer, offset, count); |
| | | 486 | | |
| | 0 | 487 | | ThrowIfFailed(authSuccessCheck: true); |
| | 0 | 488 | | if (!CanGetSecureStream) |
| | 0 | 489 | | { |
| | 0 | 490 | | return InnerStream.WriteAsync(buffer, offset, count, cancellationToken); |
| | | 491 | | } |
| | | 492 | | |
| | 0 | 493 | | return WriteAsync<AsyncReadWriteAdapter>(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken) |
| | 0 | 494 | | } |
| | | 495 | | |
| | | 496 | | /// <returns>A <see cref="ValueTask"/> that represents the asynchronous read operation.</returns> |
| | | 497 | | public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default) |
| | 0 | 498 | | { |
| | 0 | 499 | | ThrowIfFailed(authSuccessCheck: true); |
| | 0 | 500 | | if (!CanGetSecureStream) |
| | 0 | 501 | | { |
| | 0 | 502 | | return InnerStream.WriteAsync(buffer, cancellationToken); |
| | | 503 | | } |
| | | 504 | | |
| | 0 | 505 | | return new ValueTask(WriteAsync<AsyncReadWriteAdapter>(buffer, cancellationToken)); |
| | 0 | 506 | | } |
| | | 507 | | |
| | | 508 | | private async Task WriteAsync<TIOAdapter>(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken) |
| | | 509 | | where TIOAdapter : IReadWriteAdapter |
| | 0 | 510 | | { |
| | 0 | 511 | | Debug.Assert(_context is not null); |
| | 0 | 512 | | Debug.Assert(_writeBuffer is not null); |
| | | 513 | | |
| | 0 | 514 | | if (Interlocked.Exchange(ref _writeInProgress, true)) |
| | 0 | 515 | | { |
| | 0 | 516 | | throw new NotSupportedException(SR.Format(SR.net_io_invalidnestedcall, "write")); |
| | | 517 | | } |
| | | 518 | | |
| | | 519 | | try |
| | 0 | 520 | | { |
| | 0 | 521 | | ThrowIfFailed(authSuccessCheck: true); |
| | | 522 | | |
| | 0 | 523 | | while (!buffer.IsEmpty) |
| | 0 | 524 | | { |
| | 0 | 525 | | int chunkBytes = Math.Min(buffer.Length, MaxWriteDataSize); |
| | | 526 | | |
| | 0 | 527 | | bool isEncrypted = _context.IsEncrypted; |
| | | 528 | | NegotiateAuthenticationStatusCode statusCode; |
| | 0 | 529 | | ReadOnlyMemory<byte> bufferToWrap = buffer.Slice(0, chunkBytes); |
| | | 530 | | |
| | 0 | 531 | | if (isNtlm && !isEncrypted) |
| | 0 | 532 | | { |
| | | 533 | | // Non-encrypted NTLM uses an encoding quirk |
| | 0 | 534 | | _context.ComputeIntegrityCheck(bufferToWrap.Span, _writeBuffer); |
| | 0 | 535 | | _writeBuffer.Write(bufferToWrap.Span); |
| | 0 | 536 | | statusCode = NegotiateAuthenticationStatusCode.Completed; |
| | 0 | 537 | | } |
| | | 538 | | else |
| | 0 | 539 | | { |
| | 0 | 540 | | statusCode = _context.Wrap(bufferToWrap.Span, _writeBuffer, isEncrypted, out _); |
| | 0 | 541 | | } |
| | | 542 | | |
| | 0 | 543 | | if (statusCode != NegotiateAuthenticationStatusCode.Completed) |
| | 0 | 544 | | { |
| | | 545 | | // TODO: Trace the error |
| | 0 | 546 | | throw new IOException(SR.net_io_encrypt); |
| | | 547 | | } |
| | | 548 | | |
| | 0 | 549 | | BinaryPrimitives.WriteInt32LittleEndian(_writeHeader, _writeBuffer.WrittenCount); |
| | 0 | 550 | | await TIOAdapter.WriteAsync(InnerStream, _writeHeader, cancellationToken).ConfigureAwait(false); |
| | | 551 | | |
| | 0 | 552 | | await TIOAdapter.WriteAsync(InnerStream, _writeBuffer.WrittenMemory, cancellationToken).ConfigureAwa |
| | 0 | 553 | | buffer = buffer.Slice(chunkBytes); |
| | 0 | 554 | | _writeBuffer.Clear(); |
| | 0 | 555 | | } |
| | 0 | 556 | | } |
| | 0 | 557 | | catch (Exception e) when (!(e is IOException || e is OperationCanceledException)) |
| | 0 | 558 | | { |
| | 0 | 559 | | throw new IOException(SR.net_io_write, e); |
| | | 560 | | } |
| | | 561 | | finally |
| | 0 | 562 | | { |
| | 0 | 563 | | _writeBuffer.Clear(); |
| | 0 | 564 | | _writeInProgress = false; |
| | 0 | 565 | | } |
| | 0 | 566 | | } |
| | | 567 | | |
| | | 568 | | public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? asyncCallback, objec |
| | 0 | 569 | | TaskToAsyncResult.Begin(ReadAsync(buffer, offset, count), asyncCallback, asyncState); |
| | | 570 | | |
| | | 571 | | public override int EndRead(IAsyncResult asyncResult) => |
| | 0 | 572 | | TaskToAsyncResult.End<int>(asyncResult); |
| | | 573 | | |
| | | 574 | | public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? asyncCallback, obje |
| | 0 | 575 | | TaskToAsyncResult.Begin(WriteAsync(buffer, offset, count), asyncCallback, asyncState); |
| | | 576 | | |
| | | 577 | | public override void EndWrite(IAsyncResult asyncResult) => |
| | 0 | 578 | | TaskToAsyncResult.End(asyncResult); |
| | | 579 | | |
| | | 580 | | private void ThrowIfExceptional() |
| | 0 | 581 | | { |
| | 0 | 582 | | ExceptionDispatchInfo? e = _exception; |
| | 0 | 583 | | if (e != null) |
| | 0 | 584 | | { |
| | 0 | 585 | | ThrowExceptional(e); |
| | 0 | 586 | | } |
| | | 587 | | |
| | | 588 | | // Local function to make the check method more inline friendly. |
| | | 589 | | void ThrowExceptional(ExceptionDispatchInfo e) |
| | 0 | 590 | | { |
| | | 591 | | // If the stored exception just indicates disposal, throw a new ODE rather than the stored one, |
| | | 592 | | // so as to not continually build onto the shared exception's stack. |
| | 0 | 593 | | ObjectDisposedException.ThrowIf(ReferenceEquals(e, s_disposedSentinel), this); |
| | | 594 | | |
| | | 595 | | // Throw the stored exception. |
| | 0 | 596 | | e.Throw(); |
| | | 597 | | } |
| | 0 | 598 | | } |
| | | 599 | | |
| | | 600 | | private void ValidateCreateContext( |
| | | 601 | | string package, |
| | | 602 | | NetworkCredential credential, |
| | | 603 | | string servicePrincipalName, |
| | | 604 | | ExtendedProtectionPolicy? policy, |
| | | 605 | | ProtectionLevel protectionLevel, |
| | | 606 | | TokenImpersonationLevel impersonationLevel) |
| | 0 | 607 | | { |
| | 0 | 608 | | if (policy != null) |
| | 0 | 609 | | { |
| | | 610 | | // One of these must be set if EP is turned on |
| | 0 | 611 | | if (policy.CustomChannelBinding == null && policy.CustomServiceNames == null) |
| | 0 | 612 | | { |
| | 0 | 613 | | throw new ArgumentException(SR.net_auth_must_specify_extended_protection_scheme, nameof(policy)); |
| | | 614 | | } |
| | | 615 | | |
| | 0 | 616 | | _extendedProtectionPolicy = policy; |
| | 0 | 617 | | } |
| | | 618 | | else |
| | 0 | 619 | | { |
| | 0 | 620 | | _extendedProtectionPolicy = new ExtendedProtectionPolicy(PolicyEnforcement.Never); |
| | 0 | 621 | | } |
| | | 622 | | |
| | 0 | 623 | | ValidateCreateContext(package, isServer: true, credential, servicePrincipalName, _extendedProtectionPolicy.C |
| | 0 | 624 | | } |
| | | 625 | | |
| | | 626 | | private void ValidateCreateContext( |
| | | 627 | | string package, |
| | | 628 | | bool isServer, |
| | | 629 | | NetworkCredential credential, |
| | | 630 | | string? servicePrincipalName, |
| | | 631 | | ChannelBinding? channelBinding, |
| | | 632 | | ProtectionLevel protectionLevel, |
| | | 633 | | TokenImpersonationLevel impersonationLevel) |
| | 0 | 634 | | { |
| | 0 | 635 | | if (!_canRetryAuthentication) |
| | 0 | 636 | | { |
| | 0 | 637 | | ThrowIfExceptional(); |
| | 0 | 638 | | } |
| | | 639 | | |
| | 0 | 640 | | if (_context != null) |
| | 0 | 641 | | { |
| | 0 | 642 | | throw new InvalidOperationException(SR.net_auth_reauth); |
| | | 643 | | } |
| | | 644 | | |
| | 0 | 645 | | ArgumentNullException.ThrowIfNull(credential); |
| | 0 | 646 | | ArgumentNullException.ThrowIfNull(servicePrincipalName); |
| | | 647 | | |
| | 0 | 648 | | if (impersonationLevel != TokenImpersonationLevel.Identification && |
| | 0 | 649 | | impersonationLevel != TokenImpersonationLevel.Impersonation && |
| | 0 | 650 | | impersonationLevel != TokenImpersonationLevel.Delegation) |
| | 0 | 651 | | { |
| | 0 | 652 | | throw new ArgumentOutOfRangeException(nameof(impersonationLevel), impersonationLevel.ToString(), SR.net_ |
| | | 653 | | } |
| | | 654 | | |
| | 0 | 655 | | if (_context is not null && IsServer != isServer) |
| | 0 | 656 | | { |
| | 0 | 657 | | throw new InvalidOperationException(SR.net_auth_client_server); |
| | | 658 | | } |
| | | 659 | | |
| | 0 | 660 | | _exception = null; |
| | 0 | 661 | | _remoteOk = false; |
| | 0 | 662 | | _framer = new StreamFramer(); |
| | 0 | 663 | | _framer.WriteHeader.MessageId = FrameHeader.HandshakeId; |
| | | 664 | | |
| | 0 | 665 | | _canRetryAuthentication = false; |
| | | 666 | | |
| | | 667 | | // A workaround for the client when talking to Win9x on the server side. |
| | 0 | 668 | | if (protectionLevel == ProtectionLevel.None && !isServer) |
| | 0 | 669 | | { |
| | 0 | 670 | | package = NegotiationInfoClass.NTLM; |
| | 0 | 671 | | } |
| | | 672 | | |
| | 0 | 673 | | if (isServer) |
| | 0 | 674 | | { |
| | 0 | 675 | | _expectedProtectionLevel = protectionLevel; |
| | 0 | 676 | | _expectedImpersonationLevel = impersonationLevel; |
| | 0 | 677 | | _context = new NegotiateAuthentication( |
| | 0 | 678 | | new NegotiateAuthenticationServerOptions |
| | 0 | 679 | | { |
| | 0 | 680 | | Package = package, |
| | 0 | 681 | | Credential = credential, |
| | 0 | 682 | | Binding = channelBinding, |
| | 0 | 683 | | RequiredProtectionLevel = protectionLevel, |
| | 0 | 684 | | RequiredImpersonationLevel = impersonationLevel, |
| | 0 | 685 | | Policy = _extendedProtectionPolicy, |
| | 0 | 686 | | }); |
| | 0 | 687 | | } |
| | | 688 | | else |
| | 0 | 689 | | { |
| | 0 | 690 | | _expectedProtectionLevel = protectionLevel; |
| | 0 | 691 | | _expectedImpersonationLevel = TokenImpersonationLevel.None; |
| | 0 | 692 | | _context = new NegotiateAuthentication( |
| | 0 | 693 | | new NegotiateAuthenticationClientOptions |
| | 0 | 694 | | { |
| | 0 | 695 | | Package = package, |
| | 0 | 696 | | Credential = credential, |
| | 0 | 697 | | TargetName = servicePrincipalName, |
| | 0 | 698 | | Binding = channelBinding, |
| | 0 | 699 | | RequiredProtectionLevel = protectionLevel, |
| | 0 | 700 | | AllowedImpersonationLevel = impersonationLevel, |
| | 0 | 701 | | RequireMutualAuthentication = protectionLevel != ProtectionLevel.None |
| | 0 | 702 | | }, |
| | 0 | 703 | | enforceMutualAuthentication: false); |
| | 0 | 704 | | } |
| | 0 | 705 | | } |
| | | 706 | | |
| | | 707 | | private void SetFailed(Exception e) |
| | 0 | 708 | | { |
| | 0 | 709 | | if (_exception == null || !(_exception.SourceException is ObjectDisposedException)) |
| | 0 | 710 | | { |
| | 0 | 711 | | _exception = ExceptionDispatchInfo.Capture(e); |
| | 0 | 712 | | } |
| | | 713 | | |
| | 0 | 714 | | _context?.Dispose(); |
| | 0 | 715 | | } |
| | | 716 | | |
| | | 717 | | private void ThrowIfFailed(bool authSuccessCheck) |
| | 0 | 718 | | { |
| | 0 | 719 | | ThrowIfExceptional(); |
| | | 720 | | |
| | 0 | 721 | | if (authSuccessCheck && !IsAuthenticatedCore) |
| | 0 | 722 | | { |
| | 0 | 723 | | throw new InvalidOperationException(SR.net_auth_noauth); |
| | | 724 | | } |
| | 0 | 725 | | } |
| | | 726 | | |
| | | 727 | | private async Task AuthenticateAsync<TIOAdapter>(CancellationToken cancellationToken) |
| | | 728 | | where TIOAdapter : IReadWriteAdapter |
| | 0 | 729 | | { |
| | 0 | 730 | | Debug.Assert(_context != null); |
| | | 731 | | |
| | 0 | 732 | | ThrowIfFailed(authSuccessCheck: false); |
| | 0 | 733 | | if (Interlocked.Exchange(ref _authInProgress, true)) |
| | 0 | 734 | | { |
| | 0 | 735 | | throw new InvalidOperationException(SR.Format(SR.net_io_invalidnestedcall, "authenticate")); |
| | | 736 | | } |
| | | 737 | | |
| | | 738 | | try |
| | 0 | 739 | | { |
| | 0 | 740 | | await (_context.IsServer ? |
| | 0 | 741 | | ReceiveBlobAsync<TIOAdapter>(cancellationToken) : // server should listen for a client blob |
| | 0 | 742 | | SendBlobAsync<TIOAdapter>(message: null, cancellationToken)).ConfigureAwait(false); // client should |
| | 0 | 743 | | } |
| | 0 | 744 | | catch (Exception e) |
| | 0 | 745 | | { |
| | 0 | 746 | | SetFailed(e); |
| | 0 | 747 | | throw; |
| | | 748 | | } |
| | | 749 | | finally |
| | 0 | 750 | | { |
| | 0 | 751 | | _authInProgress = false; |
| | 0 | 752 | | } |
| | 0 | 753 | | } |
| | | 754 | | |
| | | 755 | | // Client authentication starts here, but server also loops through this method. |
| | | 756 | | private async Task SendBlobAsync<TIOAdapter>(byte[]? message, CancellationToken cancellationToken) |
| | | 757 | | where TIOAdapter : IReadWriteAdapter |
| | 0 | 758 | | { |
| | 0 | 759 | | Debug.Assert(_context != null); |
| | | 760 | | |
| | 0 | 761 | | NegotiateAuthenticationStatusCode statusCode = NegotiateAuthenticationStatusCode.Completed; |
| | 0 | 762 | | if (message != s_emptyMessage) |
| | 0 | 763 | | { |
| | 0 | 764 | | message = _context.GetOutgoingBlob(message, out statusCode); |
| | 0 | 765 | | } |
| | | 766 | | |
| | 0 | 767 | | if (statusCode is NegotiateAuthenticationStatusCode.BadBinding or |
| | 0 | 768 | | NegotiateAuthenticationStatusCode.TargetUnknown or |
| | 0 | 769 | | NegotiateAuthenticationStatusCode.ImpersonationValidationFailed or |
| | 0 | 770 | | NegotiateAuthenticationStatusCode.SecurityQosFailed) |
| | 0 | 771 | | { |
| | 0 | 772 | | Exception exception = statusCode switch |
| | 0 | 773 | | { |
| | 0 | 774 | | NegotiateAuthenticationStatusCode.BadBinding => |
| | 0 | 775 | | new AuthenticationException(SR.net_auth_bad_client_creds_or_target_mismatch), |
| | 0 | 776 | | NegotiateAuthenticationStatusCode.TargetUnknown => |
| | 0 | 777 | | new AuthenticationException(SR.net_auth_bad_client_creds_or_target_mismatch), |
| | 0 | 778 | | NegotiateAuthenticationStatusCode.ImpersonationValidationFailed => |
| | 0 | 779 | | new AuthenticationException(SR.Format(SR.net_auth_context_expectation, _expectedImpersonationLev |
| | 0 | 780 | | _ => // NegotiateAuthenticationStatusCode.SecurityQosFailed |
| | 0 | 781 | | new AuthenticationException(SR.Format(SR.net_auth_context_expectation, _context.ProtectionLevel. |
| | 0 | 782 | | }; |
| | | 783 | | |
| | 0 | 784 | | message = new byte[sizeof(long)]; |
| | 0 | 785 | | BinaryPrimitives.WriteInt64LittleEndian(message, ERROR_TRUST_FAILURE); |
| | | 786 | | |
| | 0 | 787 | | await SendAuthResetSignalAndThrowAsync<TIOAdapter>(message, exception, cancellationToken).ConfigureAwait |
| | 0 | 788 | | Debug.Fail("Unreachable"); |
| | | 789 | | } |
| | 0 | 790 | | else if (statusCode == NegotiateAuthenticationStatusCode.Completed) |
| | 0 | 791 | | { |
| | 0 | 792 | | _writeBuffer = new ArrayBufferWriter<byte>(); |
| | | 793 | | |
| | 0 | 794 | | isNtlm = string.Equals(_context.Package, NegotiationInfoClass.NTLM); |
| | | 795 | | |
| | | 796 | | // Signal remote party that we are done |
| | 0 | 797 | | _framer!.WriteHeader.MessageId = FrameHeader.HandshakeDoneId; |
| | 0 | 798 | | if (_context.IsServer) |
| | 0 | 799 | | { |
| | | 800 | | // Server may complete now because client SSPI would not complain at this point. |
| | 0 | 801 | | _remoteOk = true; |
| | | 802 | | |
| | | 803 | | // However the client will wait for server to send this ACK |
| | | 804 | | // Force signaling server OK to the client |
| | 0 | 805 | | message ??= s_emptyMessage; |
| | 0 | 806 | | } |
| | | 807 | | |
| | 0 | 808 | | if (message != null) |
| | 0 | 809 | | { |
| | | 810 | | //even if we are completed, there could be a blob for sending. |
| | 0 | 811 | | await _framer!.WriteMessageAsync<TIOAdapter>(InnerStream, message, cancellationToken).ConfigureAwait |
| | 0 | 812 | | } |
| | | 813 | | |
| | 0 | 814 | | if (_remoteOk) |
| | 0 | 815 | | { |
| | | 816 | | // We are done with success. |
| | 0 | 817 | | return; |
| | | 818 | | } |
| | 0 | 819 | | } |
| | 0 | 820 | | else if (statusCode != NegotiateAuthenticationStatusCode.ContinueNeeded) |
| | 0 | 821 | | { |
| | 0 | 822 | | int errorCode = statusCode switch |
| | 0 | 823 | | { |
| | 0 | 824 | | NegotiateAuthenticationStatusCode.BadBinding => (int)Interop.SECURITY_STATUS.BadBinding, |
| | 0 | 825 | | NegotiateAuthenticationStatusCode.Unsupported => (int)Interop.SECURITY_STATUS.Unsupported, |
| | 0 | 826 | | NegotiateAuthenticationStatusCode.MessageAltered => (int)Interop.SECURITY_STATUS.MessageAltered, |
| | 0 | 827 | | NegotiateAuthenticationStatusCode.ContextExpired => (int)Interop.SECURITY_STATUS.ContextExpired, |
| | 0 | 828 | | NegotiateAuthenticationStatusCode.CredentialsExpired => (int)Interop.SECURITY_STATUS.CertExpired, |
| | 0 | 829 | | NegotiateAuthenticationStatusCode.InvalidCredentials => (int)Interop.SECURITY_STATUS.LogonDenied, |
| | 0 | 830 | | NegotiateAuthenticationStatusCode.InvalidToken => (int)Interop.SECURITY_STATUS.InvalidToken, |
| | 0 | 831 | | NegotiateAuthenticationStatusCode.UnknownCredentials => (int)Interop.SECURITY_STATUS.UnknownCredenti |
| | 0 | 832 | | NegotiateAuthenticationStatusCode.QopNotSupported => (int)Interop.SECURITY_STATUS.QopNotSupported, |
| | 0 | 833 | | NegotiateAuthenticationStatusCode.OutOfSequence => (int)Interop.SECURITY_STATUS.OutOfSequence, |
| | 0 | 834 | | _ => (int)Interop.SECURITY_STATUS.InternalError |
| | 0 | 835 | | }; |
| | 0 | 836 | | Win32Exception win32Exception = new Win32Exception(errorCode); |
| | 0 | 837 | | Exception exception = statusCode switch |
| | 0 | 838 | | { |
| | 0 | 839 | | NegotiateAuthenticationStatusCode.InvalidCredentials => |
| | 0 | 840 | | new InvalidCredentialException(IsServer ? SR.net_auth_bad_client_creds : SR.net_auth_bad_client_ |
| | 0 | 841 | | _ => new AuthenticationException(SR.net_auth_SSPI, win32Exception) |
| | 0 | 842 | | }; |
| | | 843 | | |
| | 0 | 844 | | message = new byte[sizeof(long)]; |
| | 0 | 845 | | BinaryPrimitives.WriteInt64LittleEndian(message, errorCode); |
| | | 846 | | |
| | | 847 | | // Signal remote side on a failed attempt. |
| | 0 | 848 | | await SendAuthResetSignalAndThrowAsync<TIOAdapter>(message!, exception, cancellationToken).ConfigureAwai |
| | 0 | 849 | | Debug.Fail("Unreachable"); |
| | | 850 | | } |
| | | 851 | | else |
| | 0 | 852 | | { |
| | 0 | 853 | | if (message == null || message == s_emptyMessage) |
| | 0 | 854 | | { |
| | 0 | 855 | | throw new InternalException(); |
| | | 856 | | } |
| | | 857 | | |
| | 0 | 858 | | await _framer!.WriteMessageAsync<TIOAdapter>(InnerStream, message, cancellationToken).ConfigureAwait(fal |
| | 0 | 859 | | } |
| | | 860 | | |
| | 0 | 861 | | await ReceiveBlobAsync<TIOAdapter>(cancellationToken).ConfigureAwait(false); |
| | 0 | 862 | | } |
| | | 863 | | |
| | | 864 | | // Server authentication starts here, but client also loops through this method. |
| | | 865 | | private async Task ReceiveBlobAsync<TIOAdapter>(CancellationToken cancellationToken) |
| | | 866 | | where TIOAdapter : IReadWriteAdapter |
| | 0 | 867 | | { |
| | 0 | 868 | | Debug.Assert(_framer != null); |
| | | 869 | | |
| | 0 | 870 | | byte[]? message = await _framer.ReadMessageAsync<TIOAdapter>(InnerStream, cancellationToken).ConfigureAwait( |
| | 0 | 871 | | if (message == null) |
| | 0 | 872 | | { |
| | | 873 | | // This is an EOF otherwise we would get at least *empty* message but not a null one. |
| | 0 | 874 | | throw new AuthenticationException(SR.net_auth_eof); |
| | | 875 | | } |
| | | 876 | | |
| | | 877 | | // Process Header information. |
| | 0 | 878 | | if (_framer.ReadHeader.MessageId == FrameHeader.HandshakeErrId) |
| | 0 | 879 | | { |
| | 0 | 880 | | if (message.Length >= sizeof(long)) |
| | 0 | 881 | | { |
| | | 882 | | // Try to recover remote win32 Exception. |
| | 0 | 883 | | long error = BinaryPrimitives.ReadInt64LittleEndian(message); |
| | 0 | 884 | | ThrowCredentialException(error); |
| | 0 | 885 | | } |
| | | 886 | | |
| | 0 | 887 | | throw new AuthenticationException(SR.net_auth_alert); |
| | | 888 | | } |
| | | 889 | | |
| | 0 | 890 | | if (_framer.ReadHeader.MessageId == FrameHeader.HandshakeDoneId) |
| | 0 | 891 | | { |
| | 0 | 892 | | if (HandshakeComplete && message.Length > 0) |
| | 0 | 893 | | { |
| | 0 | 894 | | Debug.Assert(_context != null); |
| | 0 | 895 | | _context.GetOutgoingBlob(message, out NegotiateAuthenticationStatusCode statusCode); |
| | 0 | 896 | | _remoteOk = statusCode is NegotiateAuthenticationStatusCode.Completed; |
| | 0 | 897 | | } |
| | | 898 | | else |
| | 0 | 899 | | { |
| | 0 | 900 | | _remoteOk = true; |
| | 0 | 901 | | } |
| | 0 | 902 | | } |
| | 0 | 903 | | else if (_framer.ReadHeader.MessageId != FrameHeader.HandshakeId) |
| | 0 | 904 | | { |
| | 0 | 905 | | throw new AuthenticationException(SR.Format(SR.net_io_header_id, nameof(FrameHeader.MessageId), _framer. |
| | | 906 | | } |
| | | 907 | | |
| | | 908 | | // If we are done don't go into send. |
| | 0 | 909 | | if (HandshakeComplete) |
| | 0 | 910 | | { |
| | 0 | 911 | | if (!_remoteOk) |
| | 0 | 912 | | { |
| | 0 | 913 | | throw new AuthenticationException(SR.Format(SR.net_io_header_id, nameof(FrameHeader.MessageId), _fra |
| | | 914 | | } |
| | | 915 | | |
| | 0 | 916 | | return; |
| | | 917 | | } |
| | | 918 | | |
| | | 919 | | // Not yet done, get a new blob and send it if any. |
| | 0 | 920 | | await SendBlobAsync<TIOAdapter>(message, cancellationToken).ConfigureAwait(false); |
| | 0 | 921 | | } |
| | | 922 | | |
| | | 923 | | // This is to reset auth state on the remote side. |
| | | 924 | | // If this write succeeds we will allow auth retrying. |
| | | 925 | | private async Task SendAuthResetSignalAndThrowAsync<TIOAdapter>(byte[] message, Exception exception, Cancellatio |
| | | 926 | | where TIOAdapter : IReadWriteAdapter |
| | 0 | 927 | | { |
| | 0 | 928 | | _framer!.WriteHeader.MessageId = FrameHeader.HandshakeErrId; |
| | | 929 | | |
| | 0 | 930 | | await _framer.WriteMessageAsync<TIOAdapter>(InnerStream, message, cancellationToken).ConfigureAwait(false); |
| | | 931 | | |
| | 0 | 932 | | _canRetryAuthentication = true; |
| | 0 | 933 | | ExceptionDispatchInfo.Throw(exception); |
| | | 934 | | } |
| | | 935 | | |
| | | 936 | | private static void ThrowCredentialException(long error) |
| | 0 | 937 | | { |
| | 0 | 938 | | var e = new Win32Exception((int)error); |
| | 0 | 939 | | throw e.NativeErrorCode switch |
| | 0 | 940 | | { |
| | 0 | 941 | | // Compatibility quirk: .NET Core and .NET 5/6 incorrectly report internal status code instead of Win32 |
| | 0 | 942 | | (int)SecurityStatusPalErrorCode.LogonDenied => new InvalidCredentialException(SR.net_auth_bad_client_cre |
| | 0 | 943 | | (int)Interop.SECURITY_STATUS.LogonDenied => new InvalidCredentialException(SR.net_auth_bad_client_creds, |
| | 0 | 944 | | ERROR_TRUST_FAILURE => new AuthenticationException(SR.net_auth_context_expectation_remote, e), |
| | 0 | 945 | | _ => new AuthenticationException(SR.net_auth_alert, e) |
| | 0 | 946 | | }; |
| | | 947 | | } |
| | | 948 | | } |
| | | 949 | | } |
| | | 950 | | |