From 4ac2501dad3b0bb4694a54daabe37556b4890371 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Fri, 3 Jun 2022 12:49:06 +0200 Subject: [PATCH 01/28] WIP: Add implementation of NegotiateAuthentication Switch System.Net.Http to use NegotiateAuthentication Fix IsCompleted in managed NTLM implementation --- .../GssSafeHandles.PlatformNotSupported.cs | 59 -------- ...extFlagsAdapterPal.PlatformNotSupported.cs | 22 --- .../src/System/Net/NTAuthentication.Common.cs | 45 +++++++ .../System/Net/NTAuthentication.Managed.cs | 98 +++++++++++--- ...NegotiateStreamPal.PlatformNotSupported.cs | 124 ----------------- .../src/System/Net/SecurityStatusPal.cs | 1 + .../src/Resources/Strings.resx | 36 ----- .../src/System.Net.Http.csproj | 97 -------------- .../AuthenticationHelper.NtAuth.cs | 83 ++++++------ .../AuthenticationHelper.NtAuth.tvOS.cs | 30 ----- .../ref/System.Net.Security.cs | 82 ++++++++++++ .../src/System.Net.Security.csproj | 27 ++-- .../src/System/Net/NTAuthentication.cs | 93 +------------ .../Net/Security/NegotiateAuthentication.cs | 126 ++++++++++++++++++ .../NegotiateAuthenticationClientOptions.cs | 38 ++++++ .../NegotiateAuthenticationServerOptions.cs | 33 +++++ .../NegotiateAuthenticationStatusCode.cs | 57 ++++++++ ...NegotiateStreamPal.PlatformNotSupported.cs | 5 + .../Net/Security/NegotiateStreamPal.Unix.cs | 1 - 19 files changed, 537 insertions(+), 520 deletions(-) delete mode 100644 src/libraries/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.PlatformNotSupported.cs delete mode 100644 src/libraries/Common/src/System/Net/ContextFlagsAdapterPal.PlatformNotSupported.cs delete mode 100644 src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.PlatformNotSupported.cs delete mode 100644 src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.tvOS.cs create mode 100644 src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs create mode 100644 src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationClientOptions.cs create mode 100644 src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationServerOptions.cs create mode 100644 src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.PlatformNotSupported.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.PlatformNotSupported.cs deleted file mode 100644 index 20eeb6dfbf818..0000000000000 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.PlatformNotSupported.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; -using System.Text; - -namespace Microsoft.Win32.SafeHandles -{ - [UnsupportedOSPlatform("tvos")] - internal sealed class SafeGssNameHandle : SafeHandle - { - public override bool IsInvalid - { - get { throw new PlatformNotSupportedException(); } - } - - protected override bool ReleaseHandle() => throw new PlatformNotSupportedException(); - - public SafeGssNameHandle() - : base(IntPtr.Zero, true) - { - } - } - - [UnsupportedOSPlatform("tvos")] - internal sealed class SafeGssCredHandle : SafeHandle - { - public SafeGssCredHandle() - : base(IntPtr.Zero, true) - { - } - - public override bool IsInvalid - { - get { throw new PlatformNotSupportedException(); } - } - - protected override bool ReleaseHandle() => throw new PlatformNotSupportedException(); - } - - [UnsupportedOSPlatform("tvos")] - internal sealed class SafeGssContextHandle : SafeHandle - { - public SafeGssContextHandle() - : base(IntPtr.Zero, true) - { - } - - public override bool IsInvalid - { - get { throw new PlatformNotSupportedException(); } - } - - protected override bool ReleaseHandle() => throw new PlatformNotSupportedException(); - } -} diff --git a/src/libraries/Common/src/System/Net/ContextFlagsAdapterPal.PlatformNotSupported.cs b/src/libraries/Common/src/System/Net/ContextFlagsAdapterPal.PlatformNotSupported.cs deleted file mode 100644 index ee4d9cb16dd4e..0000000000000 --- a/src/libraries/Common/src/System/Net/ContextFlagsAdapterPal.PlatformNotSupported.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Runtime.Versioning; - -namespace System.Net -{ - [UnsupportedOSPlatform("tvos")] - internal static class ContextFlagsAdapterPal - { - internal static ContextFlagsPal GetContextFlagsPalFromInterop(Interop.NetSecurityNative.GssFlags gssFlags, bool isServer) - { - throw new PlatformNotSupportedException(); - } - - internal static Interop.NetSecurityNative.GssFlags GetInteropFromContextFlagsPal(ContextFlagsPal flags, bool isServer) - { - throw new PlatformNotSupportedException(); - } - } -} diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs index 7d733b9f4912d..3a5546c1f3e38 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Net.Security; @@ -90,6 +91,34 @@ internal bool IsKerberos } } + internal bool IsNTLM + { + get + { + if (_lastProtocolName == null) + { + _lastProtocolName = ProtocolName; + } + + return (object)_lastProtocolName == (object)NegotiationInfoClass.NTLM; + } + } + + internal string? AssociatedName + { + get + { + if (!(IsValidContext && IsCompleted)) + { + throw new Win32Exception((int)SecurityStatusPalErrorCode.InvalidHandle); + } + + string? name = NegotiateStreamPal.QueryContextAssociatedName(_securityContext!); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"NTAuthentication: The context is associated with [{name}]"); + return name; + } + } + // // This overload does not attempt to impersonate because the caller either did it already or the original thread context is still preserved. // @@ -312,5 +341,21 @@ internal int MakeSignature(byte[] buffer, int offset, int count, [AllowNull] ref return spn; } + + internal int Encrypt(ReadOnlySpan buffer, [NotNull] ref byte[]? output, uint sequenceNumber) + { + return NegotiateStreamPal.Encrypt( + _securityContext!, + buffer, + IsConfidentialityFlag, + IsNTLM, + ref output, + sequenceNumber); + } + + internal int Decrypt(byte[] payload, int offset, int count, out int newOffset, uint expectedSeqNumber) + { + return NegotiateStreamPal.Decrypt(_securityContext!, payload, offset, count, IsConfidentialityFlag, IsNTLM, out newOffset, expectedSeqNumber); + } } } diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs index d3ed0a07df98f..ce34a1458e473 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Formats.Asn1; -using System.Net.Http.Headers; using System.Net.Security; using System.Runtime.InteropServices; using System.Runtime.Versioning; @@ -27,6 +26,7 @@ internal sealed partial class NTAuthentication private readonly NetworkCredential _credential; private readonly string? _spn; private readonly ChannelBinding? _channelBinding; + private readonly ContextFlagsPal _contextFlags; // State parameters private byte[]? _spnegoMechList; @@ -278,10 +278,10 @@ internal NTAuthentication(bool isServer, string package, NetworkCredential crede if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"package={package}, spn={spn}, requestedContextFlags={requestedContextFlags}"); - // TODO: requestedContextFlags _credential = credential; _spn = spn; _channelBinding = channelBinding; + _contextFlags = requestedContextFlags; } internal void CloseContext() @@ -313,43 +313,60 @@ internal void CloseContext() { decodedIncomingBlob = Convert.FromBase64String(incomingBlob); } - byte[]? decodedOutgoingBlob; + byte[]? decodedOutgoingBlob = GetOutgoingBlob(decodedIncomingBlob, true); + string? outgoingBlob = null; + if (decodedOutgoingBlob != null && decodedOutgoingBlob.Length > 0) + { + outgoingBlob = Convert.ToBase64String(decodedOutgoingBlob); + } + + if (IsCompleted) + { + CloseContext(); + } + + return outgoingBlob; + } + + internal byte[]? GetOutgoingBlob(byte[]? incomingBlob, bool throwOnError) + { + return GetOutgoingBlob(incomingBlob, throwOnError, out _); + } + + // Accepts an incoming binary security blob and returns an outgoing binary security blob. + internal unsafe byte[]? GetOutgoingBlob(byte[]? incomingBlob, bool throwOnError, out SecurityStatusPal statusCode) + { + byte[]? outgoingBlob; // TODO: Logging, validation if (_negotiateMessage == null) { - Debug.Assert(decodedIncomingBlob == null); + Debug.Assert(incomingBlob == null); _negotiateMessage = new byte[sizeof(NegotiateMessage)]; CreateNtlmNegotiateMessage(_negotiateMessage); - decodedOutgoingBlob = _isSpNego ? CreateSpNegoNegotiateMessage(_negotiateMessage) : _negotiateMessage; + outgoingBlob = _isSpNego ? CreateSpNegoNegotiateMessage(_negotiateMessage) : _negotiateMessage; statusCode = SecurityStatusPalContinueNeeded; } else { - Debug.Assert(decodedIncomingBlob != null); + Debug.Assert(incomingBlob != null); if (!_isSpNego) { IsCompleted = true; - decodedOutgoingBlob = ProcessChallenge(decodedIncomingBlob, out statusCode); + outgoingBlob = ProcessChallenge(incomingBlob, out statusCode); } else { - decodedOutgoingBlob = ProcessSpNegoChallenge(decodedIncomingBlob, out statusCode); + outgoingBlob = ProcessSpNegoChallenge(incomingBlob, out statusCode); } } - string? outgoingBlob = null; - if (decodedOutgoingBlob != null && decodedOutgoingBlob.Length > 0) + if (statusCode.ErrorCode >= SecurityStatusPalErrorCode.OutOfMemory && throwOnError) { - outgoingBlob = Convert.ToBase64String(decodedOutgoingBlob); - } - - if (IsCompleted) - { - CloseContext(); + throw new Win32Exception(NTE_FAIL, statusCode.ErrorCode.ToString()); } return outgoingBlob; @@ -917,8 +934,13 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan ntlmNegoti } catch (AsnContentException) { +<<<<<<< HEAD statusCode = SecurityStatusPalInvalidToken; return null; +======= + statusCode = new SecurityStatusPal(SecurityStatusPalErrorCode.IllegalMessage, e); + return Array.Empty(); +>>>>>>> WIP: Add implementation of NegotiateAuthentication } if (blob?.Length > 0) @@ -927,9 +949,14 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan ntlmNegoti // message with the challenge blob. if (!NtlmOid.Equals(mech)) { +<<<<<<< HEAD if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Server requested unknown mechanism {mech}"); statusCode = SecurityStatusPalPackageNotFound; return null; +======= + statusCode = new SecurityStatusPal(SecurityStatusPalErrorCode.PackageNotFound); + return Array.Empty(); +>>>>>>> WIP: Add implementation of NegotiateAuthentication } // Process decoded NTLM blob. @@ -960,7 +987,11 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan ntlmNegoti } } +<<<<<<< HEAD statusCode = state == NegState.RequestMic ? SecurityStatusPalContinueNeeded : SecurityStatusPalOk; +======= + statusCode = SecurityStatusPalContinueNeeded; +>>>>>>> WIP: Add implementation of NegotiateAuthentication return writer.Encode(); } } @@ -969,6 +1000,7 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan ntlmNegoti { if (_spnegoMechList == null || state != NegState.AcceptCompleted) { +<<<<<<< HEAD statusCode = SecurityStatusPalInternalError; return null; } @@ -977,10 +1009,15 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan ntlmNegoti { statusCode = SecurityStatusPalMessageAltered; return null; +======= + statusCode = new SecurityStatusPal(SecurityStatusPalErrorCode.MessageAltered); + return Array.Empty(); +>>>>>>> WIP: Add implementation of NegotiateAuthentication } } IsCompleted = state == NegState.AcceptCompleted || state == NegState.Reject; +<<<<<<< HEAD statusCode = state switch { NegState.AcceptCompleted => SecurityStatusPalOk, NegState.AcceptIncomplete => SecurityStatusPalContinueNeeded, @@ -989,6 +1026,35 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan ntlmNegoti }; return null; +======= + + statusCode = IsCompleted ? SecurityStatusPalOk : new SecurityStatusPal(SecurityStatusPalErrorCode.LogonDenied); + return Array.Empty(); +>>>>>>> WIP: Add implementation of NegotiateAuthentication + } + +#pragma warning disable CA1822 + internal int Encrypt(ReadOnlySpan buffer, [NotNull] ref byte[]? output, uint sequenceNumber) + { + throw new PlatformNotSupportedException(); } + + internal int Decrypt(byte[] payload, int offset, int count, out int newOffset, uint expectedSeqNumber) + { + throw new PlatformNotSupportedException(); + } + + internal string ProtocolName => _isSpNego ? NegotiationInfoClass.Negotiate : NegotiationInfoClass.NTLM; + + internal bool IsNTLM => true; + + internal bool IsKerberos => false; + + internal bool IsServer => false; + + internal bool IsValidContext => true; + + internal string? ClientSpecifiedSpn => _spn; +#pragma warning restore CA1822 } } diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.PlatformNotSupported.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.PlatformNotSupported.cs deleted file mode 100644 index 00eb37b101b52..0000000000000 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.PlatformNotSupported.cs +++ /dev/null @@ -1,124 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.IO; -using System.ComponentModel; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; -using System.Security; -using System.Security.Authentication; -using System.Security.Authentication.ExtendedProtection; -using System.Security.Principal; -using System.Text; -using System.Threading; -using Microsoft.Win32.SafeHandles; - -namespace System.Net.Security -{ - // - // The class maintains the state of the authentication process and the security context. - // It encapsulates security context and does the real work in authentication and - // user data encryption with NEGO SSPI package. - // - [UnsupportedOSPlatform("tvos")] - internal static partial class NegotiateStreamPal - { - internal static string QueryContextClientSpecifiedSpn(SafeDeleteContext securityContext) - { - throw new PlatformNotSupportedException(SR.net_nego_server_not_supported); - } - - internal static string QueryContextAuthenticationPackage(SafeDeleteContext securityContext) - { - throw new PlatformNotSupportedException(); - } - - internal static SecurityStatusPal InitializeSecurityContext( - ref SafeFreeCredentials credentialsHandle, - ref SafeDeleteContext? securityContext, - string? spn, - ContextFlagsPal requestedContextFlags, - byte[]? incomingBlob, - ChannelBinding? channelBinding, - ref byte[]? resultBlob, - ref ContextFlagsPal contextFlags) - { - throw new PlatformNotSupportedException(); - } - - internal static SecurityStatusPal AcceptSecurityContext( - SafeFreeCredentials? credentialsHandle, - ref SafeDeleteContext? securityContext, - ContextFlagsPal requestedContextFlags, - byte[]? incomingBlob, - ChannelBinding? channelBinding, - ref byte[] resultBlob, - ref ContextFlagsPal contextFlags) - { - throw new PlatformNotSupportedException(); - } - - internal static Win32Exception CreateExceptionFromError(SecurityStatusPal statusCode) - { - throw new PlatformNotSupportedException(); - } - - internal static int QueryMaxTokenSize(string package) - { - throw new PlatformNotSupportedException(); - } - - internal static SafeFreeCredentials AcquireDefaultCredential(string package, bool isServer) - { - throw new PlatformNotSupportedException(); - } - - internal static SafeFreeCredentials AcquireCredentialsHandle(string package, bool isServer, NetworkCredential credential) - { - throw new PlatformNotSupportedException(); - } - - internal static SecurityStatusPal CompleteAuthToken( - ref SafeDeleteContext? securityContext, - byte[]? incomingBlob) - { - throw new PlatformNotSupportedException(); - } - - internal static int Encrypt( - SafeDeleteContext securityContext, - ReadOnlySpan buffer, - bool isConfidential, - bool isNtlm, - [NotNull] ref byte[]? output, - uint sequenceNumber) - { - throw new PlatformNotSupportedException(); - } - - internal static int Decrypt( - SafeDeleteContext securityContext, - byte[]? buffer, - int offset, - int count, - bool isConfidential, - bool isNtlm, - out int newOffset, - uint sequenceNumber) - { - throw new PlatformNotSupportedException(); - } - - internal static int VerifySignature(SafeDeleteContext securityContext, byte[] buffer, int offset, int count) - { - throw new PlatformNotSupportedException(); - } - - internal static int MakeSignature(SafeDeleteContext securityContext, byte[] buffer, int offset, int count, [AllowNull] ref byte[] output) - { - throw new PlatformNotSupportedException(); - } - } -} diff --git a/src/libraries/Common/src/System/Net/SecurityStatusPal.cs b/src/libraries/Common/src/System/Net/SecurityStatusPal.cs index 064aa974d6bb2..cfb8b4c946fc2 100644 --- a/src/libraries/Common/src/System/Net/SecurityStatusPal.cs +++ b/src/libraries/Common/src/System/Net/SecurityStatusPal.cs @@ -22,6 +22,7 @@ public override string ToString() } } + // Matches NegotiateAuthenticationStatusCode internal enum SecurityStatusPalErrorCode { NotSet = 0, diff --git a/src/libraries/System.Net.Http/src/Resources/Strings.resx b/src/libraries/System.Net.Http/src/Resources/Strings.resx index d5ff73663f820..20a30c23e9a9f 100644 --- a/src/libraries/System.Net.Http/src/Resources/Strings.resx +++ b/src/libraries/System.Net.Http/src/Resources/Strings.resx @@ -432,48 +432,12 @@ Protocol error: A received message contains a valid signature but it was not encrypted as required by the effective Protection Level. - - The requested security package is not supported. - - - '{0}' is not a supported handle type. - Authentication failed because the connection could not be reused. Authentication validation failed with error - {0}. - - Server implementation is not supported - - - Requested protection level is not supported with the GSSAPI implementation currently installed. - - - Insufficient buffer space. Required: {0} Actual: {1}. - - - GSSAPI operation failed with error - {0} ({1}). - - - GSSAPI operation failed with status: {0} (Minor status: {1}). - - - GSSAPI operation failed with error - {0}. - - - GSSAPI operation failed with status: {0}. - - - NTLM authentication requires the GSSAPI plugin 'gss-ntlmssp'. - - - NTLM authentication is not possible with default credentials on this platform. - - - Target name should be non empty if default credentials are passed. - Huffman-coded literal string failed to decode. diff --git a/src/libraries/System.Net.Http/src/System.Net.Http.csproj b/src/libraries/System.Net.Http/src/System.Net.Http.csproj index 709ac8d11b022..1aabb7ad01e22 100644 --- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj +++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj @@ -8,7 +8,6 @@ $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) - true SR.PlatformNotSupported_NetHttp $(DefineConstants);SYSNETHTTP_NO_OPENSSL $(DefineConstants);TARGET_MOBILE @@ -212,16 +211,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -468,56 +419,8 @@ Link="Common\System\Runtime\ExceptionServices\ExceptionStackTrace.cs" /> - - - - - - - - - - - - - - - - - - - - - - - - SendWithNtAuthAsync(HttpRequestMe NetEventSource.Info(connection, $"Authentication: {challenge.AuthenticationType}, SPN: {spn}"); } - ContextFlagsPal contextFlags = ContextFlagsPal.Connection; + ProtectionLevel requiredProtectionLevel = ProtectionLevel.None; // When connecting to proxy server don't enforce the integrity to avoid // compatibility issues. The assumption is that the proxy server comes // from a trusted source. On macOS we always need to enforce the integrity @@ -162,56 +165,58 @@ private static async Task SendWithNtAuthAsync(HttpRequestMe // tokens. if (!isProxyAuth || OperatingSystem.IsMacOS()) { - contextFlags |= ContextFlagsPal.InitIntegrity; + requiredProtectionLevel = ProtectionLevel.Sign; } - ChannelBinding? channelBinding = connection.TransportContext?.GetChannelBinding(ChannelBindingKind.Endpoint); - NTAuthentication authContext = new NTAuthentication(isServer: false, challenge.SchemeName, challenge.Credential, spn, contextFlags, channelBinding); + NegotiateAuthenticationClientOptions authClientOptions = new NegotiateAuthenticationClientOptions + { + Package = challenge.SchemeName, + Credential = challenge.Credential, + TargetName = spn, + RequiredProtectionLevel = requiredProtectionLevel, + Binding = connection.TransportContext?.GetChannelBinding(ChannelBindingKind.Endpoint) + }; + + using NegotiateAuthentication authContext = new NegotiateAuthentication(authClientOptions); string? challengeData = challenge.ChallengeData; - try + NegotiateAuthenticationStatusCode statusCode; + while (true) { - while (true) + SecurityStatusPal statusCode; + string? challengeResponse = authContext.GetOutgoingBlob(challengeData, throwOnError: false, out statusCode); + if (statusCode.ErrorCode > SecurityStatusPalErrorCode.TryAgain || challengeResponse == null) { - SecurityStatusPal statusCode; - string? challengeResponse = authContext.GetOutgoingBlob(challengeData, throwOnError: false, out statusCode); - if (statusCode.ErrorCode > SecurityStatusPalErrorCode.TryAgain || challengeResponse == null) - { - // Response indicated denial even after login, so stop processing and return current response. - break; - } + // Response indicated denial even after login, so stop processing and return current response. + break; + } - if (needDrain) - { - await connection.DrainResponseAsync(response!, cancellationToken).ConfigureAwait(false); - } + if (needDrain) + { + await connection.DrainResponseAsync(response!, cancellationToken).ConfigureAwait(false); + } - SetRequestAuthenticationHeaderValue(request, new AuthenticationHeaderValue(challenge.SchemeName, challengeResponse), isProxyAuth); + SetRequestAuthenticationHeaderValue(request, new AuthenticationHeaderValue(challenge.SchemeName, challengeResponse), isProxyAuth); - response = await InnerSendAsync(request, async, isProxyAuth, connectionPool, connection, cancellationToken).ConfigureAwait(false); - if (authContext.IsCompleted || !TryGetChallengeDataForScheme(challenge.SchemeName, GetResponseAuthenticationHeaderValues(response, isProxyAuth), out challengeData)) - { - break; - } + response = await InnerSendAsync(request, async, isProxyAuth, connectionPool, connection, cancellationToken).ConfigureAwait(false); + if (authContext.IsCompleted || !TryGetChallengeDataForScheme(challenge.SchemeName, GetResponseAuthenticationHeaderValues(response, isProxyAuth), out challengeData)) + { + break; + } - if (!IsAuthenticationChallenge(response, isProxyAuth)) + if (!IsAuthenticationChallenge(response, isProxyAuth)) + { + // Tail response for Negoatiate on successful authentication. Validate it before we proceed. + authContext.GetOutgoingBlob(challengeData, throwOnError: false, out statusCode); + if (statusCode.ErrorCode != SecurityStatusPalErrorCode.OK) { - // Tail response for Negoatiate on successful authentication. Validate it before we proceed. - authContext.GetOutgoingBlob(challengeData, throwOnError: false, out statusCode); - if (statusCode.ErrorCode != SecurityStatusPalErrorCode.OK) - { - isNewConnection = false; - connection.Dispose(); - throw new HttpRequestException(SR.Format(SR.net_http_authvalidationfailure, statusCode.ErrorCode), null, HttpStatusCode.Unauthorized); - } - break; + isNewConnection = false; + connection.Dispose(); + throw new HttpRequestException(SR.Format(SR.net_http_authvalidationfailure, statusCode.ErrorCode), null, HttpStatusCode.Unauthorized); } - - needDrain = true; + break; } - } - finally - { - authContext.CloseContext(); + + needDrain = true; } } finally diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.tvOS.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.tvOS.cs deleted file mode 100644 index 2b5b5a4fe49f9..0000000000000 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.tvOS.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; - -namespace System.Net.Http -{ - internal static partial class AuthenticationHelper - { - private static Task InnerSendAsync(HttpRequestMessage request, Uri authUri, bool async, ICredentials credentials, bool isProxyAuth, HttpConnection connection, HttpConnectionPool connectionPool, CancellationToken cancellationToken) - { - return isProxyAuth ? - SendWithProxyAuthAsync(request, authUri, async, credentials, false, connectionPool, cancellationToken).AsTask() : - SendWithRequestAuthAsync(request, async, credentials, false, connectionPool, cancellationToken).AsTask(); - } - - public static Task SendWithNtProxyAuthAsync(HttpRequestMessage request, Uri proxyUri, bool async, ICredentials proxyCredentials, HttpConnection connection, HttpConnectionPool connectionPool, CancellationToken cancellationToken) - { - return InnerSendAsync(request, proxyUri, async, proxyCredentials, isProxyAuth: true, connection, connectionPool, cancellationToken); - } - - public static Task SendWithNtConnectionAuthAsync(HttpRequestMessage request, bool async, ICredentials credentials, HttpConnection connection, HttpConnectionPool connectionPool, CancellationToken cancellationToken) - { - Debug.Assert(request.RequestUri != null); - return InnerSendAsync(request, request.RequestUri, async, credentials, isProxyAuth: false, connection, connectionPool, cancellationToken); - } - } -} diff --git a/src/libraries/System.Net.Security/ref/System.Net.Security.cs b/src/libraries/System.Net.Security/ref/System.Net.Security.cs index 76589add6b201..300746e10032e 100644 --- a/src/libraries/System.Net.Security/ref/System.Net.Security.cs +++ b/src/libraries/System.Net.Security/ref/System.Net.Security.cs @@ -35,6 +35,88 @@ public enum EncryptionPolicy NoEncryption = 2, } public delegate System.Security.Cryptography.X509Certificates.X509Certificate LocalCertificateSelectionCallback(object sender, string targetHost, System.Security.Cryptography.X509Certificates.X509CertificateCollection localCertificates, System.Security.Cryptography.X509Certificates.X509Certificate? remoteCertificate, string[] acceptableIssuers); + public sealed partial class NegotiateAuthentication : System.IDisposable + { + public NegotiateAuthentication(System.Net.Security.NegotiateAuthenticationClientOptions clientOptions) { } + public NegotiateAuthentication(System.Net.Security.NegotiateAuthenticationServerOptions serverOptions) { } + public bool IsAuthenticated { get { throw null; } } + public bool IsEncrypted { get { throw null; } } + public bool IsMutuallyAuthenticated { get { throw null; } } + public bool IsServer { get { throw null; } } + public bool IsSigned { get { throw null; } } + public string Package { get { throw null; } } + public System.Net.Security.ProtectionLevel ProtectionLevel { get { throw null; } } + public System.Security.Principal.IIdentity RemoteIdentity { get { throw null; } } + public string? TargetName { get { throw null; } } + public void Dispose() { } + public byte[]? GetOutgoingBlob(System.ReadOnlySpan incomingBlob, out System.Net.Security.NegotiateAuthenticationStatusCode statusCode) { throw null; } + public string? GetOutgoingBlob(string? incomingBlob, out System.Net.Security.NegotiateAuthenticationStatusCode statusCode) { throw null; } + } + public partial class NegotiateAuthenticationClientOptions + { + public NegotiateAuthenticationClientOptions() { } + public System.Security.Authentication.ExtendedProtection.ChannelBinding? Binding { get { throw null; } set { } } + public System.Net.NetworkCredential Credential { get { throw null; } set { } } + public string Package { get { throw null; } set { } } + public System.Net.Security.ProtectionLevel RequiredProtectionLevel { get { throw null; } set { } } + public string? TargetName { get { throw null; } set { } } + } + public partial class NegotiateAuthenticationServerOptions + { + public NegotiateAuthenticationServerOptions() { } + public System.Security.Authentication.ExtendedProtection.ChannelBinding? Binding { get { throw null; } set { } } + public System.Net.NetworkCredential Credential { get { throw null; } set { } } + public string Package { get { throw null; } set { } } + public System.Net.Security.ProtectionLevel RequiredProtectionLevel { get { throw null; } set { } } + } + public enum NegotiateAuthenticationStatusCode + { + NotSet = 0, + OK = 1, + ContinueNeeded = 2, + CompleteNeeded = 3, + CompleteAndContinue = 4, + ContextExpired = 5, + CredentialsNeeded = 6, + Renegotiate = 7, + TryAgain = 8, + OutOfMemory = 9, + InvalidHandle = 10, + Unsupported = 11, + TargetUnknown = 12, + InternalError = 13, + PackageNotFound = 14, + NotOwner = 15, + CannotInstall = 16, + InvalidToken = 17, + CannotPack = 18, + QopNotSupported = 19, + NoImpersonation = 20, + LogonDenied = 21, + UnknownCredentials = 22, + NoCredentials = 23, + MessageAltered = 24, + OutOfSequence = 25, + NoAuthenticatingAuthority = 26, + IncompleteMessage = 27, + IncompleteCredentials = 28, + BufferNotEnough = 29, + WrongPrincipal = 30, + TimeSkew = 31, + UntrustedRoot = 32, + IllegalMessage = 33, + CertUnknown = 34, + CertExpired = 35, + DecryptFailure = 36, + AlgorithmMismatch = 37, + SecurityQosFailed = 38, + SmartcardLogonRequired = 39, + UnsupportedPreauth = 40, + BadBinding = 41, + DowngradeDetected = 42, + ApplicationProtocolMismatch = 43, + NoRenegotiation = 44, + } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] public partial class NegotiateStream : System.Net.Security.AuthenticatedStream { diff --git a/src/libraries/System.Net.Security/src/System.Net.Security.csproj b/src/libraries/System.Net.Security/src/System.Net.Security.csproj index 50a94c18ae064..8dc0edfef137c 100644 --- a/src/libraries/System.Net.Security/src/System.Net.Security.csproj +++ b/src/libraries/System.Net.Security/src/System.Net.Security.csproj @@ -12,6 +12,7 @@ $(DefineConstants);TARGET_WINDOWS true true + true $(DefineConstants);SYSNETSECURITY_NO_OPENSSL ReferenceAssemblyExclusions.txt @@ -22,6 +23,10 @@ + + + + @@ -87,7 +92,8 @@ + Link="Common\System\Net\NTAuthentication.Common.cs" + Condition="'$(UseManagedNtlm)' != 'true'" /> - + - - - - + + + + @@ -435,4 +441,7 @@ + + + diff --git a/src/libraries/System.Net.Security/src/System/Net/NTAuthentication.cs b/src/libraries/System.Net.Security/src/System/Net/NTAuthentication.cs index af10117c3adf1..f67274fe139bd 100644 --- a/src/libraries/System.Net.Security/src/System/Net/NTAuthentication.cs +++ b/src/libraries/System.Net.Security/src/System/Net/NTAuthentication.cs @@ -9,99 +9,18 @@ namespace System.Net { - [UnsupportedOSPlatform("tvos")] internal sealed partial class NTAuthentication { - internal string? AssociatedName - { - get - { - if (!(IsValidContext && IsCompleted)) - { - throw new Win32Exception((int)SecurityStatusPalErrorCode.InvalidHandle); - } + internal bool IsConfidentialityFlag => (_contextFlags & ContextFlagsPal.Confidentiality) != 0; - string? name = NegotiateStreamPal.QueryContextAssociatedName(_securityContext!); - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"NTAuthentication: The context is associated with [{name}]"); - return name; - } - } + internal bool IsIntegrityFlag => (_contextFlags & (IsServer ? ContextFlagsPal.AcceptIntegrity : ContextFlagsPal.InitIntegrity)) != 0; - internal bool IsConfidentialityFlag - { - get - { - return (_contextFlags & ContextFlagsPal.Confidentiality) != 0; - } - } + internal bool IsMutualAuthFlag => (_contextFlags & ContextFlagsPal.MutualAuth) != 0; - internal bool IsIntegrityFlag - { - get - { - return (_contextFlags & (_isServer ? ContextFlagsPal.AcceptIntegrity : ContextFlagsPal.InitIntegrity)) != 0; - } - } + internal bool IsDelegationFlag => (_contextFlags & ContextFlagsPal.Delegate) != 0; - internal bool IsMutualAuthFlag - { - get - { - return (_contextFlags & ContextFlagsPal.MutualAuth) != 0; - } - } + internal bool IsIdentifyFlag => (_contextFlags & (IsServer ? ContextFlagsPal.AcceptIdentify : ContextFlagsPal.InitIdentify)) != 0; - internal bool IsDelegationFlag - { - get - { - return (_contextFlags & ContextFlagsPal.Delegate) != 0; - } - } - - internal bool IsIdentifyFlag - { - get - { - return (_contextFlags & (_isServer ? ContextFlagsPal.AcceptIdentify : ContextFlagsPal.InitIdentify)) != 0; - } - } - - internal string? Spn - { - get - { - return _spn; - } - } - - internal bool IsNTLM - { - get - { - if (_lastProtocolName == null) - { - _lastProtocolName = ProtocolName; - } - - return (object)_lastProtocolName == (object)NegotiationInfoClass.NTLM; - } - } - - internal int Encrypt(ReadOnlySpan buffer, [NotNull] ref byte[]? output, uint sequenceNumber) - { - return NegotiateStreamPal.Encrypt( - _securityContext!, - buffer, - IsConfidentialityFlag, - IsNTLM, - ref output, - sequenceNumber); - } - - internal int Decrypt(byte[] payload, int offset, int count, out int newOffset, uint expectedSeqNumber) - { - return NegotiateStreamPal.Decrypt(_securityContext!, payload, offset, count, IsConfidentialityFlag, IsNTLM, out newOffset, expectedSeqNumber); - } + internal string? Spn => _spn; } } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs new file mode 100644 index 0000000000000..d2f366842a531 --- /dev/null +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs @@ -0,0 +1,126 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Security.Principal; +using System.Runtime.Versioning; + +namespace System.Net.Security +{ + public sealed class NegotiateAuthentication : IDisposable + { + private NTAuthentication _ntAuthentication; + private bool _isServer; + private IIdentity? _remoteIdentity; + + public NegotiateAuthentication(NegotiateAuthenticationClientOptions clientOptions) + { + ContextFlagsPal contextFlags = ContextFlagsPal.Connection | clientOptions.RequiredProtectionLevel switch + { + ProtectionLevel.Sign => ContextFlagsPal.InitIntegrity, + ProtectionLevel.EncryptAndSign => ContextFlagsPal.InitIntegrity | ContextFlagsPal.Confidentiality, + _ => 0 + }; + + _isServer = false; + _ntAuthentication = new NTAuthentication( + isServer: false, + clientOptions.Package, + clientOptions.Credential, + clientOptions.TargetName, + contextFlags, + clientOptions.Binding); + } + + public NegotiateAuthentication(NegotiateAuthenticationServerOptions serverOptions) + { + ContextFlagsPal contextFlags = ContextFlagsPal.Connection | serverOptions.RequiredProtectionLevel switch + { + ProtectionLevel.Sign => ContextFlagsPal.AcceptIntegrity, + ProtectionLevel.EncryptAndSign => ContextFlagsPal.AcceptIntegrity | ContextFlagsPal.Confidentiality, + _ => 0 + }; + + _isServer = true; + _ntAuthentication = new NTAuthentication( + isServer: true, + serverOptions.Package, + serverOptions.Credential, + null, + contextFlags, + serverOptions.Binding); + } + + public void Dispose() + { + _ntAuthentication.CloseContext(); + } + + public bool IsAuthenticated => _ntAuthentication.IsCompleted; + + public ProtectionLevel ProtectionLevel + { + get => IsSigned ? (IsEncrypted ? ProtectionLevel.EncryptAndSign : ProtectionLevel.Sign) : ProtectionLevel.None; + } + + public bool IsSigned => _ntAuthentication.IsIntegrityFlag; + + public bool IsEncrypted => _ntAuthentication.IsConfidentialityFlag; + + public bool IsMutuallyAuthenticated => _ntAuthentication.IsMutualAuthFlag; + + public bool IsServer => _isServer; + + public string Package => _ntAuthentication.ProtocolName; + + public string? TargetName => IsServer ? _ntAuthentication.ClientSpecifiedSpn : _ntAuthentication.Spn; + + public IIdentity RemoteIdentity + { + get + { + IIdentity? identity = _remoteIdentity; + if (identity is null) + { + if (IsServer) + { + // Server authentication is not supported on tvOS + Debug.Assert(!OperatingSystem.IsTvOS()); + _remoteIdentity = identity = NegotiateStreamPal.GetIdentity(_ntAuthentication); + } + else + { + return new GenericIdentity(TargetName ?? string.Empty, Package); + } + } + return identity; + } + } + + public byte[]? GetOutgoingBlob(ReadOnlySpan incomingBlob, out NegotiateAuthenticationStatusCode statusCode) + { + SecurityStatusPal securityStatus; + byte[]? blob = _ntAuthentication.GetOutgoingBlob(incomingBlob.ToArray(), false, out securityStatus); + statusCode = (NegotiateAuthenticationStatusCode)securityStatus.ErrorCode; + return blob; + } + + public string? GetOutgoingBlob(string? incomingBlob, out NegotiateAuthenticationStatusCode statusCode) + { + byte[]? decodedIncomingBlob = null; + if (incomingBlob != null && incomingBlob.Length > 0) + { + decodedIncomingBlob = Convert.FromBase64String(incomingBlob); + } + byte[]? decodedOutgoingBlob = GetOutgoingBlob(decodedIncomingBlob, out statusCode); + + string? outgoingBlob = null; + if (decodedOutgoingBlob != null && decodedOutgoingBlob.Length > 0) + { + outgoingBlob = Convert.ToBase64String(decodedOutgoingBlob); + } + + return outgoingBlob; + } + } +} diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationClientOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationClientOptions.cs new file mode 100644 index 0000000000000..8dc0007d54a5f --- /dev/null +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationClientOptions.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Security.Authentication.ExtendedProtection; + +namespace System.Net.Security +{ + public class NegotiateAuthenticationClientOptions + { + /// + /// Specifies the GSSAPI authentication package used for the authentication. + /// Common values are Negotiate, NTLM or Kerberos. Default value is Negotiate. + /// + public string Package { get; set; } = NegotiationInfoClass.Negotiate; + + /// + /// The NetworkCredential that is used to establish the identity of the client. + /// Default value is CredentialCache.DefaultNetworkCredentials. + /// + public NetworkCredential Credential { get; set; } = CredentialCache.DefaultNetworkCredentials; + + /// + /// The Service Principal Name (SPN) that uniquely identifies the server to authenticate. + /// + public string? TargetName { get; set; } + + /// + /// Channel binding that is used for extended protection. + /// + public ChannelBinding? Binding { get; set; } + + /// + /// Indicates the requires level of protection of the authentication exchange + /// and any further data exchange. Default value is None. + /// + public ProtectionLevel RequiredProtectionLevel { get; set; } = ProtectionLevel.None; + } +} diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationServerOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationServerOptions.cs new file mode 100644 index 0000000000000..42182773100cd --- /dev/null +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationServerOptions.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Security.Authentication.ExtendedProtection; + +namespace System.Net.Security +{ + public class NegotiateAuthenticationServerOptions + { + /// + /// Specifies the GSSAPI authentication package used for the authentication. + /// Common values are Negotiate, NTLM or Kerberos. Default value is Negotiate. + /// + public string Package { get; set; } = NegotiationInfoClass.Negotiate; + + /// + /// The NetworkCredential that is used to establish the identity of the client. + /// Default value is CredentialCache.DefaultNetworkCredentials. + /// + public NetworkCredential Credential { get; set; } = CredentialCache.DefaultNetworkCredentials; + + /// + /// Channel binding that is used for extended protection. + /// + public ChannelBinding? Binding { get; set; } + + /// + /// Indicates the requires level of protection of the authentication exchange + /// and any further data exchange. Default value is None. + /// + public ProtectionLevel RequiredProtectionLevel { get; set; } = ProtectionLevel.None; + } +} diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs new file mode 100644 index 0000000000000..88347cad64b91 --- /dev/null +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Net.Security +{ + // Matches SecurityStatusPalErrorCode + public enum NegotiateAuthenticationStatusCode + { + NotSet = 0, + OK, + ContinueNeeded, + CompleteNeeded, + CompleteAndContinue, + ContextExpired, + CredentialsNeeded, + Renegotiate, + TryAgain, + + // Errors + OutOfMemory, + InvalidHandle, + Unsupported, + TargetUnknown, + InternalError, + PackageNotFound, + NotOwner, + CannotInstall, + InvalidToken, + CannotPack, + QopNotSupported, + NoImpersonation, + LogonDenied, + UnknownCredentials, + NoCredentials, + MessageAltered, + OutOfSequence, + NoAuthenticatingAuthority, + IncompleteMessage, + IncompleteCredentials, + BufferNotEnough, + WrongPrincipal, + TimeSkew, + UntrustedRoot, + IllegalMessage, + CertUnknown, + CertExpired, + DecryptFailure, + AlgorithmMismatch, + SecurityQosFailed, + SmartcardLogonRequired, + UnsupportedPreauth, + BadBinding, + DowngradeDetected, + ApplicationProtocolMismatch, + NoRenegotiation + } +} diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.PlatformNotSupported.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.PlatformNotSupported.cs index b41a28ccb9e10..056449dbc407c 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.PlatformNotSupported.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.PlatformNotSupported.cs @@ -29,5 +29,10 @@ internal static void ValidateImpersonationLevel(TokenImpersonationLevel imperson { throw new PlatformNotSupportedException(); } + + internal static Win32Exception CreateExceptionFromError(SecurityStatusPal statusCode) + { + throw new PlatformNotSupportedException(); + } } } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Unix.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Unix.cs index 720fae1a6d01f..04054004c9d79 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Unix.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Unix.cs @@ -29,7 +29,6 @@ internal static IIdentity GetIdentity(NTAuthentication context) } return new GenericIdentity(name, protocol); - } internal static string QueryContextAssociatedName(SafeDeleteContext? securityContext) From c7a6bce71ac6f157b2422ff988e3ddd3c34b9426 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sat, 4 Jun 2022 11:42:01 +0200 Subject: [PATCH 02/28] WIP: Update error code mapping --- .../src/System/Net/SecurityStatusPal.cs | 1 - .../System.Net.Http/ref/System.Net.Http.cs | 161 ++++++++---------- .../ref/System.Net.Security.cs | 58 ++----- .../Net/Security/NegotiateAuthentication.cs | 41 ++++- .../NegotiateAuthenticationStatusCode.cs | 60 ++----- 5 files changed, 137 insertions(+), 184 deletions(-) diff --git a/src/libraries/Common/src/System/Net/SecurityStatusPal.cs b/src/libraries/Common/src/System/Net/SecurityStatusPal.cs index cfb8b4c946fc2..064aa974d6bb2 100644 --- a/src/libraries/Common/src/System/Net/SecurityStatusPal.cs +++ b/src/libraries/Common/src/System/Net/SecurityStatusPal.cs @@ -22,7 +22,6 @@ public override string ToString() } } - // Matches NegotiateAuthenticationStatusCode internal enum SecurityStatusPalErrorCode { NotSet = 0, diff --git a/src/libraries/System.Net.Http/ref/System.Net.Http.cs b/src/libraries/System.Net.Http/ref/System.Net.Http.cs index debcc28d0119a..5c10452325c38 100644 --- a/src/libraries/System.Net.Http/ref/System.Net.Http.cs +++ b/src/libraries/System.Net.Http/ref/System.Net.Http.cs @@ -4,9 +4,6 @@ // Changes to this file must follow the https://aka.ms/api-review process. // ------------------------------------------------------------------------------ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - namespace System.Net.Http { public partial class ByteArrayContent : System.Net.Http.HttpContent @@ -37,12 +34,7 @@ protected override void Dispose(bool disposing) { } } public partial class FormUrlEncodedContent : System.Net.Http.ByteArrayContent { - public FormUrlEncodedContent( - System.Collections.Generic.IEnumerable> nameValueCollection) : base (default(byte[])) { } + public FormUrlEncodedContent(System.Collections.Generic.IEnumerable> nameValueCollection) : base (default(byte[])) { } protected override System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext? context, System.Threading.CancellationToken cancellationToken) { throw null; } } public delegate System.Text.Encoding? HeaderEncodingSelector(string headerName, TContext context); @@ -156,14 +148,7 @@ public HttpClientHandler() { } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public bool UseProxy { get { throw null; } set { } } protected override void Dispose(bool disposing) { } - // - // Attributes are commented out due to https://github.com/dotnet/arcade/issues/7585 - // API compat will fail until this is fixed - // - //[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")] [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] - //[System.Runtime.Versioning.UnsupportedOSPlatformAttributeUnsupportedOSPlatform("ios")] - //[System.Runtime.Versioning.UnsupportedOSPlatformAttributeUnsupportedOSPlatform("tvos")] protected internal override System.Net.Http.HttpResponseMessage Send(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { throw null; } protected internal override System.Threading.Tasks.Task SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { throw null; } } @@ -201,6 +186,11 @@ protected virtual void SerializeToStream(System.IO.Stream stream, System.Net.Tra protected virtual System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext? context, System.Threading.CancellationToken cancellationToken) { throw null; } protected internal abstract bool TryComputeLength(out long length); } + public enum HttpKeepAlivePingPolicy + { + WithActiveRequests = 0, + Always = 1, + } public abstract partial class HttpMessageHandler : System.IDisposable { protected HttpMessageHandler() { } @@ -231,8 +221,8 @@ public HttpMethod(string method) { } public static System.Net.Http.HttpMethod Post { get { throw null; } } public static System.Net.Http.HttpMethod Put { get { throw null; } } public static System.Net.Http.HttpMethod Trace { get { throw null; } } - public bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] System.Net.Http.HttpMethod? other) { throw null; } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] System.Net.Http.HttpMethod? other) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static bool operator ==(System.Net.Http.HttpMethod? left, System.Net.Http.HttpMethod? right) { throw null; } public static bool operator !=(System.Net.Http.HttpMethod? left, System.Net.Http.HttpMethod? right) { throw null; } @@ -254,9 +244,9 @@ public HttpRequestMessage(System.Net.Http.HttpMethod method, System.Uri? request public System.Net.Http.HttpContent? Content { get { throw null; } set { } } public System.Net.Http.Headers.HttpRequestHeaders Headers { get { throw null; } } public System.Net.Http.HttpMethod Method { get { throw null; } set { } } + public System.Net.Http.HttpRequestOptions Options { get { throw null; } } [System.ObsoleteAttribute("HttpRequestMessage.Properties has been deprecated. Use Options instead.")] public System.Collections.Generic.IDictionary Properties { get { throw null; } } - public HttpRequestOptions Options { get { throw null; } } public System.Uri? RequestUri { get { throw null; } set { } } public System.Version Version { get { throw null; } set { } } public System.Net.Http.HttpVersionPolicy VersionPolicy { get { throw null; } set { } } @@ -264,35 +254,35 @@ public void Dispose() { } protected virtual void Dispose(bool disposing) { } public override string ToString() { throw null; } } - - public readonly struct HttpRequestOptionsKey + public sealed partial class HttpRequestOptions : System.Collections.Generic.ICollection>, System.Collections.Generic.IDictionary, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable { - public HttpRequestOptionsKey(string key) {} - public string Key { get { throw null; } } - } - - public sealed class HttpRequestOptions : System.Collections.Generic.IDictionary - { - void System.Collections.Generic.IDictionary.Add(string key, object? value) { throw null; } + public HttpRequestOptions() { } + int System.Collections.Generic.ICollection>.Count { get { throw null; } } + bool System.Collections.Generic.ICollection>.IsReadOnly { get { throw null; } } + object? System.Collections.Generic.IDictionary.this[string key] { get { throw null; } set { } } System.Collections.Generic.ICollection System.Collections.Generic.IDictionary.Keys { get { throw null; } } System.Collections.Generic.ICollection System.Collections.Generic.IDictionary.Values { get { throw null; } } - bool System.Collections.Generic.IDictionary.Remove(string key) { throw null; } - bool System.Collections.Generic.ICollection>.Remove(System.Collections.Generic.KeyValuePair item) { throw null; } - bool System.Collections.Generic.IDictionary.TryGetValue(string key, out object? value) { throw null; } - object? System.Collections.Generic.IDictionary.this[string key] { get { throw null; } set { } } - void System.Collections.Generic.ICollection>.Add(System.Collections.Generic.KeyValuePair item) { throw null; } - void System.Collections.Generic.ICollection>.Clear() { throw null; } - bool System.Collections.Generic.ICollection>.Contains(System.Collections.Generic.KeyValuePair item) { throw null; } + public void Set(System.Net.Http.HttpRequestOptionsKey key, TValue value) { } + void System.Collections.Generic.ICollection>.Add(System.Collections.Generic.KeyValuePair item) { } + void System.Collections.Generic.ICollection>.Clear() { } + bool System.Collections.Generic.ICollection>.Contains(System.Collections.Generic.KeyValuePair item) { throw null; } + void System.Collections.Generic.ICollection>.CopyTo(System.Collections.Generic.KeyValuePair[] array, int arrayIndex) { } + bool System.Collections.Generic.ICollection>.Remove(System.Collections.Generic.KeyValuePair item) { throw null; } + void System.Collections.Generic.IDictionary.Add(string key, object value) { } bool System.Collections.Generic.IDictionary.ContainsKey(string key) { throw null; } - void System.Collections.Generic.ICollection>.CopyTo(System.Collections.Generic.KeyValuePair[] array, int arrayIndex) { throw null; } - int System.Collections.Generic.ICollection>.Count { get { throw null; } } - bool System.Collections.Generic.ICollection>.IsReadOnly { get { throw null; } } - System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; } + bool System.Collections.Generic.IDictionary.Remove(string key) { throw null; } + bool System.Collections.Generic.IDictionary.TryGetValue(string key, out object value) { throw null; } + System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } - public bool TryGetValue(HttpRequestOptionsKey key, [MaybeNullWhen(false)] out TValue value) { throw null; } - public void Set(HttpRequestOptionsKey key, TValue value) { throw null; } + public bool TryGetValue(System.Net.Http.HttpRequestOptionsKey key, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TValue value) { throw null; } + } + public readonly partial struct HttpRequestOptionsKey + { + private readonly object _dummy; + private readonly int _dummyPrimitive; + public HttpRequestOptionsKey(string key) { throw null; } + public string Key { get { throw null; } } } - public partial class HttpResponseMessage : System.IDisposable { public HttpResponseMessage() { } @@ -363,28 +353,39 @@ protected override void SerializeToStream(System.IO.Stream stream, System.Net.Tr protected override System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext? context, System.Threading.CancellationToken cancellationToken) { throw null; } protected internal override bool TryComputeLength(out long length) { throw null; } } + public sealed partial class SocketsHttpConnectionContext + { + internal SocketsHttpConnectionContext() { } + public System.Net.DnsEndPoint DnsEndPoint { get { throw null; } } + public System.Net.Http.HttpRequestMessage InitialRequestMessage { get { throw null; } } + } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public sealed partial class SocketsHttpHandler : System.Net.Http.HttpMessageHandler { public SocketsHttpHandler() { } - public int InitialHttp2StreamWindowSize { get { throw null; } set { } } - [System.Runtime.Versioning.UnsupportedOSPlatformGuardAttribute("browser")] - public static bool IsSupported { get { throw null; } } + [System.CLSCompliantAttribute(false)] + public System.Diagnostics.DistributedContextPropagator? ActivityHeadersPropagator { get { throw null; } set { } } public bool AllowAutoRedirect { get { throw null; } set { } } public System.Net.DecompressionMethods AutomaticDecompression { get { throw null; } set { } } + public System.Func>? ConnectCallback { get { throw null; } set { } } public System.TimeSpan ConnectTimeout { get { throw null; } set { } } [System.Diagnostics.CodeAnalysis.AllowNullAttribute] public System.Net.CookieContainer CookieContainer { get { throw null; } set { } } public System.Net.ICredentials? Credentials { get { throw null; } set { } } public System.Net.ICredentials? DefaultProxyCredentials { get { throw null; } set { } } + public bool EnableMultipleHttp2Connections { get { throw null; } set { } } public System.TimeSpan Expect100ContinueTimeout { get { throw null; } set { } } + public int InitialHttp2StreamWindowSize { get { throw null; } set { } } + [System.Runtime.Versioning.UnsupportedOSPlatformGuardAttribute("browser")] + public static bool IsSupported { get { throw null; } } public System.TimeSpan KeepAlivePingDelay { get { throw null; } set { } } + public System.Net.Http.HttpKeepAlivePingPolicy KeepAlivePingPolicy { get { throw null; } set { } } public System.TimeSpan KeepAlivePingTimeout { get { throw null; } set { } } - public HttpKeepAlivePingPolicy KeepAlivePingPolicy { get { throw null; } set { } } public int MaxAutomaticRedirections { get { throw null; } set { } } public int MaxConnectionsPerServer { get { throw null; } set { } } public int MaxResponseDrainSize { get { throw null; } set { } } public int MaxResponseHeadersLength { get { throw null; } set { } } + public System.Func>? PlaintextStreamFilter { get { throw null; } set { } } public System.TimeSpan PooledConnectionIdleTimeout { get { throw null; } set { } } public System.TimeSpan PooledConnectionLifetime { get { throw null; } set { } } public bool PreAuthenticate { get { throw null; } set { } } @@ -400,29 +401,13 @@ public SocketsHttpHandler() { } protected override void Dispose(bool disposing) { } protected internal override System.Net.Http.HttpResponseMessage Send(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { throw null; } protected internal override System.Threading.Tasks.Task SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { throw null; } - public bool EnableMultipleHttp2Connections { get { throw null; } set { } } - public Func>? ConnectCallback { get { throw null; } set { } } - public Func>? PlaintextStreamFilter { get { throw null; } set { } } - [System.CLSCompliantAttribute(false)] - public System.Diagnostics.DistributedContextPropagator? ActivityHeadersPropagator { get { throw null; } set { } } } - public sealed class SocketsHttpConnectionContext - { - internal SocketsHttpConnectionContext() { } - public DnsEndPoint DnsEndPoint { get { throw null; } } - public HttpRequestMessage InitialRequestMessage { get { throw null; } } - } - public sealed class SocketsHttpPlaintextStreamFilterContext + public sealed partial class SocketsHttpPlaintextStreamFilterContext { internal SocketsHttpPlaintextStreamFilterContext() { } + public System.Net.Http.HttpRequestMessage InitialRequestMessage { get { throw null; } } + public System.Version NegotiatedHttpVersion { get { throw null; } } public System.IO.Stream PlaintextStream { get { throw null; } } - public Version NegotiatedHttpVersion { get { throw null; } } - public HttpRequestMessage InitialRequestMessage { get { throw null; } } - } - public enum HttpKeepAlivePingPolicy - { - WithActiveRequests, - Always } public partial class StreamContent : System.Net.Http.HttpContent { @@ -454,7 +439,7 @@ public AuthenticationHeaderValue(string scheme) { } public AuthenticationHeaderValue(string scheme, string? parameter) { } public string? Parameter { get { throw null; } } public string Scheme { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.AuthenticationHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -480,7 +465,7 @@ public CacheControlHeaderValue() { } public bool ProxyRevalidate { get { throw null; } set { } } public bool Public { get { throw null; } set { } } public System.TimeSpan? SharedMaxAge { get { throw null; } set { } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.CacheControlHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -500,10 +485,10 @@ public ContentDispositionHeaderValue(string dispositionType) { } public System.Collections.Generic.ICollection Parameters { get { throw null; } } public System.DateTimeOffset? ReadDate { get { throw null; } set { } } public long? Size { get { throw null; } set { } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.ContentDispositionHeaderValue Parse(string? input) { throw null; } - object System.ICloneable.Clone() { throw null; } + object? System.ICloneable.Clone() { throw null; } public override string ToString() { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? input, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Net.Http.Headers.ContentDispositionHeaderValue? parsedValue) { throw null; } } @@ -518,7 +503,7 @@ public ContentRangeHeaderValue(long from, long to, long length) { } public long? Length { get { throw null; } } public long? To { get { throw null; } } public string Unit { get { throw null; } set { } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.ContentRangeHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -532,7 +517,7 @@ public EntityTagHeaderValue(string tag, bool isWeak) { } public static System.Net.Http.Headers.EntityTagHeaderValue Any { get { throw null; } } public bool IsWeak { get { throw null; } } public string Tag { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.EntityTagHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -600,12 +585,12 @@ public void Clear() { } System.Collections.Generic.IEnumerable System.Collections.Generic.IReadOnlyDictionary.Keys { get { throw null; } } System.Collections.Generic.IEnumerable System.Collections.Generic.IReadOnlyDictionary.Values { get { throw null; } } public bool Contains(string headerName) { throw null; } - bool System.Collections.Generic.IReadOnlyDictionary.ContainsKey(string key) { throw null; } public System.Net.Http.Headers.HttpHeadersNonValidated.Enumerator GetEnumerator() { throw null; } System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; } + bool System.Collections.Generic.IReadOnlyDictionary.ContainsKey(string key) { throw null; } + bool System.Collections.Generic.IReadOnlyDictionary.TryGetValue(string key, out System.Net.Http.Headers.HeaderStringValues value) { throw null; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } public bool TryGetValues(string headerName, out System.Net.Http.Headers.HeaderStringValues values) { throw null; } - bool System.Collections.Generic.IReadOnlyDictionary.TryGetValue(string key, out System.Net.Http.Headers.HeaderStringValues value) { throw null; } public partial struct Enumerator : System.Collections.Generic.IEnumerator>, System.Collections.IEnumerator, System.IDisposable { private object _dummy; @@ -701,10 +686,10 @@ public MediaTypeHeaderValue(string mediaType, string? charSet) { } [System.Diagnostics.CodeAnalysis.DisallowNullAttribute] public string? MediaType { get { throw null; } set { } } public System.Collections.Generic.ICollection Parameters { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.MediaTypeHeaderValue Parse(string? input) { throw null; } - object System.ICloneable.Clone() { throw null; } + object? System.ICloneable.Clone() { throw null; } public override string ToString() { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? input, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Net.Http.Headers.MediaTypeHeaderValue? parsedValue) { throw null; } } @@ -719,12 +704,12 @@ public MediaTypeWithQualityHeaderValue(string mediaType, double quality) : base } public partial class NameValueHeaderValue : System.ICloneable { - protected NameValueHeaderValue(System.Net.Http.Headers.NameValueHeaderValue source) { } + protected internal NameValueHeaderValue(System.Net.Http.Headers.NameValueHeaderValue source) { } public NameValueHeaderValue(string name) { } public NameValueHeaderValue(string name, string? value) { } public string Name { get { throw null; } } public string? Value { get { throw null; } set { } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.NameValueHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -737,7 +722,7 @@ protected NameValueWithParametersHeaderValue(System.Net.Http.Headers.NameValueWi public NameValueWithParametersHeaderValue(string name) : base (default(string)) { } public NameValueWithParametersHeaderValue(string name, string? value) : base (default(string)) { } public System.Collections.Generic.ICollection Parameters { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static new System.Net.Http.Headers.NameValueWithParametersHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -750,7 +735,7 @@ public ProductHeaderValue(string name) { } public ProductHeaderValue(string name, string? version) { } public string Name { get { throw null; } } public string? Version { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.ProductHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -764,7 +749,7 @@ public ProductInfoHeaderValue(string comment) { } public ProductInfoHeaderValue(string productName, string? productVersion) { } public string? Comment { get { throw null; } } public System.Net.Http.Headers.ProductHeaderValue? Product { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.ProductInfoHeaderValue Parse(string input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -778,7 +763,7 @@ public RangeConditionHeaderValue(System.Net.Http.Headers.EntityTagHeaderValue en public RangeConditionHeaderValue(string entityTag) { } public System.DateTimeOffset? Date { get { throw null; } } public System.Net.Http.Headers.EntityTagHeaderValue? EntityTag { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.RangeConditionHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -791,7 +776,7 @@ public RangeHeaderValue() { } public RangeHeaderValue(long? from, long? to) { } public System.Collections.Generic.ICollection Ranges { get { throw null; } } public string Unit { get { throw null; } set { } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.RangeHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -803,7 +788,7 @@ public partial class RangeItemHeaderValue : System.ICloneable public RangeItemHeaderValue(long? from, long? to) { } public long? From { get { throw null; } } public long? To { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } object System.ICloneable.Clone() { throw null; } public override string ToString() { throw null; } @@ -814,7 +799,7 @@ public RetryConditionHeaderValue(System.DateTimeOffset date) { } public RetryConditionHeaderValue(System.TimeSpan delta) { } public System.DateTimeOffset? Date { get { throw null; } } public System.TimeSpan? Delta { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.RetryConditionHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -827,7 +812,7 @@ public StringWithQualityHeaderValue(string value) { } public StringWithQualityHeaderValue(string value, double quality) { } public double? Quality { get { throw null; } } public string Value { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.StringWithQualityHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -840,7 +825,7 @@ protected TransferCodingHeaderValue(System.Net.Http.Headers.TransferCodingHeader public TransferCodingHeaderValue(string value) { } public System.Collections.Generic.ICollection Parameters { get { throw null; } } public string Value { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.TransferCodingHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -865,7 +850,7 @@ public ViaHeaderValue(string protocolVersion, string receivedBy, string? protoco public string? ProtocolName { get { throw null; } } public string ProtocolVersion { get { throw null; } } public string ReceivedBy { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.ViaHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -880,7 +865,7 @@ public WarningHeaderValue(int code, string agent, string text, System.DateTimeOf public int Code { get { throw null; } } public System.DateTimeOffset? Date { get { throw null; } } public string Text { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.WarningHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } diff --git a/src/libraries/System.Net.Security/ref/System.Net.Security.cs b/src/libraries/System.Net.Security/ref/System.Net.Security.cs index 300746e10032e..a5a9f3083a686 100644 --- a/src/libraries/System.Net.Security/ref/System.Net.Security.cs +++ b/src/libraries/System.Net.Security/ref/System.Net.Security.cs @@ -71,51 +71,19 @@ public NegotiateAuthenticationServerOptions() { } } public enum NegotiateAuthenticationStatusCode { - NotSet = 0, - OK = 1, - ContinueNeeded = 2, - CompleteNeeded = 3, - CompleteAndContinue = 4, - ContextExpired = 5, - CredentialsNeeded = 6, - Renegotiate = 7, - TryAgain = 8, - OutOfMemory = 9, - InvalidHandle = 10, - Unsupported = 11, - TargetUnknown = 12, - InternalError = 13, - PackageNotFound = 14, - NotOwner = 15, - CannotInstall = 16, - InvalidToken = 17, - CannotPack = 18, - QopNotSupported = 19, - NoImpersonation = 20, - LogonDenied = 21, - UnknownCredentials = 22, - NoCredentials = 23, - MessageAltered = 24, - OutOfSequence = 25, - NoAuthenticatingAuthority = 26, - IncompleteMessage = 27, - IncompleteCredentials = 28, - BufferNotEnough = 29, - WrongPrincipal = 30, - TimeSkew = 31, - UntrustedRoot = 32, - IllegalMessage = 33, - CertUnknown = 34, - CertExpired = 35, - DecryptFailure = 36, - AlgorithmMismatch = 37, - SecurityQosFailed = 38, - SmartcardLogonRequired = 39, - UnsupportedPreauth = 40, - BadBinding = 41, - DowngradeDetected = 42, - ApplicationProtocolMismatch = 43, - NoRenegotiation = 44, + Completed = 0, + ContinueNeeded = 1, + GenericFailure = 2, + BadBinding = 3, + Unsupported = 4, + MessageAltered = 5, + ContextExpired = 6, + CredentialsExpired = 7, + InvalidCredentials = 8, + InvalidToken = 9, + UnknownCredentials = 10, + QopNotSupported = 11, + OutOfSequence = 12, } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] public partial class NegotiateStream : System.Net.Security.AuthenticatedStream diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs index d2f366842a531..d09e792119075 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs @@ -99,9 +99,44 @@ public IIdentity RemoteIdentity public byte[]? GetOutgoingBlob(ReadOnlySpan incomingBlob, out NegotiateAuthenticationStatusCode statusCode) { - SecurityStatusPal securityStatus; - byte[]? blob = _ntAuthentication.GetOutgoingBlob(incomingBlob.ToArray(), false, out securityStatus); - statusCode = (NegotiateAuthenticationStatusCode)securityStatus.ErrorCode; + byte[]? blob = _ntAuthentication.GetOutgoingBlob(incomingBlob.ToArray(), false, out SecurityStatusPal securityStatus); + + // Map error codes + statusCode = securityStatus.ErrorCode switch + { + SecurityStatusPalErrorCode.OK => NegotiateAuthenticationStatusCode.Completed, + SecurityStatusPalErrorCode.ContinueNeeded => NegotiateAuthenticationStatusCode.ContinueNeeded, + + // These code should never be returned and they should be handled internally + SecurityStatusPalErrorCode.CompleteNeeded => NegotiateAuthenticationStatusCode.Completed, + SecurityStatusPalErrorCode.CompAndContinue => NegotiateAuthenticationStatusCode.ContinueNeeded, + + SecurityStatusPalErrorCode.ContextExpired => NegotiateAuthenticationStatusCode.ContextExpired, + SecurityStatusPalErrorCode.Unsupported => NegotiateAuthenticationStatusCode.Unsupported, + SecurityStatusPalErrorCode.PackageNotFound => NegotiateAuthenticationStatusCode.Unsupported, + SecurityStatusPalErrorCode.CannotInstall => NegotiateAuthenticationStatusCode.Unsupported, + SecurityStatusPalErrorCode.InvalidToken => NegotiateAuthenticationStatusCode.InvalidToken, + SecurityStatusPalErrorCode.QopNotSupported => NegotiateAuthenticationStatusCode.QopNotSupported, + SecurityStatusPalErrorCode.NoImpersonation => NegotiateAuthenticationStatusCode.UnknownCredentials, + SecurityStatusPalErrorCode.LogonDenied => NegotiateAuthenticationStatusCode.UnknownCredentials, + SecurityStatusPalErrorCode.UnknownCredentials => NegotiateAuthenticationStatusCode.UnknownCredentials, + SecurityStatusPalErrorCode.NoCredentials => NegotiateAuthenticationStatusCode.UnknownCredentials, + SecurityStatusPalErrorCode.MessageAltered => NegotiateAuthenticationStatusCode.MessageAltered, + SecurityStatusPalErrorCode.OutOfSequence => NegotiateAuthenticationStatusCode.OutOfSequence, + SecurityStatusPalErrorCode.NoAuthenticatingAuthority => NegotiateAuthenticationStatusCode.InvalidCredentials, + SecurityStatusPalErrorCode.IncompleteCredentials => NegotiateAuthenticationStatusCode.InvalidCredentials, + SecurityStatusPalErrorCode.IllegalMessage => NegotiateAuthenticationStatusCode.InvalidToken, + SecurityStatusPalErrorCode.CertExpired => NegotiateAuthenticationStatusCode.CredentialsExpired, + SecurityStatusPalErrorCode.SecurityQosFailed => NegotiateAuthenticationStatusCode.QopNotSupported, + SecurityStatusPalErrorCode.UnsupportedPreauth => NegotiateAuthenticationStatusCode.Unsupported, + SecurityStatusPalErrorCode.BadBinding => NegotiateAuthenticationStatusCode.BadBinding, + + // Processing partial inputs is not supported, so this is result of incorrect input + SecurityStatusPalErrorCode.IncompleteMessage => NegotiateAuthenticationStatusCode.InvalidToken, + + _ => NegotiateAuthenticationStatusCode.GenericFailure, + }; + return blob; } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs index 88347cad64b91..4f0e1a3ea4a18 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs @@ -3,55 +3,21 @@ namespace System.Net.Security { - // Matches SecurityStatusPalErrorCode public enum NegotiateAuthenticationStatusCode { - NotSet = 0, - OK, - ContinueNeeded, - CompleteNeeded, - CompleteAndContinue, - ContextExpired, - CredentialsNeeded, - Renegotiate, - TryAgain, + Completed = 0, // GSS_S_COMPLETE + ContinueNeeded, // GSS_S_CONTINUE_NEEDED - // Errors - OutOfMemory, - InvalidHandle, - Unsupported, - TargetUnknown, - InternalError, - PackageNotFound, - NotOwner, - CannotInstall, - InvalidToken, - CannotPack, - QopNotSupported, - NoImpersonation, - LogonDenied, - UnknownCredentials, - NoCredentials, - MessageAltered, - OutOfSequence, - NoAuthenticatingAuthority, - IncompleteMessage, - IncompleteCredentials, - BufferNotEnough, - WrongPrincipal, - TimeSkew, - UntrustedRoot, - IllegalMessage, - CertUnknown, - CertExpired, - DecryptFailure, - AlgorithmMismatch, - SecurityQosFailed, - SmartcardLogonRequired, - UnsupportedPreauth, - BadBinding, - DowngradeDetected, - ApplicationProtocolMismatch, - NoRenegotiation + GenericFailure, // GSS_S_FAILURE/GSS_S_NO_CONTEXT + BadBinding, // GSS_S_BAD_BINDINGS + Unsupported, // GSS_S_BAD_MECH (Unsupported mechanism) + MessageAltered, // GSS_S_BAD_SIG = GSS_S_BAD_MIC + ContextExpired, // GSS_S_CONTEXT_EXPIRED + CredentialsExpired, // GSS_S_CREDENTIALS_EXPIRED + InvalidCredentials, // GSS_S_DEFECTIVE_CREDENTIAL + InvalidToken, // GSS_S_DEFECTIVE_TOKEN + UnknownCredentials, // GSS_S_NO_CRED + QopNotSupported, // GSS_S_BAD_QOP + OutOfSequence, // GSS_S_DUPLICATE_TOKEN/GSS_S_OLD_TOKEN/GSS_S_UNSEQ_TOKEN/GSS_S_GAP_TOKEN + GSS_E_FAILURE } } From 5d5c61f10c8e651d0bf9019d3f6149f75f1794a9 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sat, 4 Jun 2022 12:26:08 +0200 Subject: [PATCH 03/28] Spanify input of GetOutgoingBlob --- .../Interop.NetSecurityNative.cs | 88 +++++++++++++++++-- .../src/System/Net/NTAuthentication.Common.cs | 7 +- .../System/Net/NTAuthentication.Managed.cs | 65 +++++--------- .../Net/Security/NegotiateStreamPal.Unix.cs | 13 ++- .../Security/NegotiateStreamPal.Windows.cs | 8 +- .../Cryptography/Asn1Reader/AsnValueReader.cs | 7 ++ .../src/Resources/Strings.resx | 3 + .../src/System.Net.Security.csproj | 4 + .../Net/Security/NegotiateAuthentication.cs | 2 +- 9 files changed, 134 insertions(+), 63 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs b/src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs index 292523ff7bdd5..ecb5adceb51b9 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs @@ -73,21 +73,21 @@ internal static partial Status ReleaseCred( ref IntPtr credHandle); [LibraryImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_InitSecContext")] - internal static partial Status InitSecContext( + private static partial Status InitSecContext( out Status minorStatus, SafeGssCredHandle initiatorCredHandle, ref SafeGssContextHandle contextHandle, [MarshalAs(UnmanagedType.Bool)] bool isNtlmOnly, SafeGssNameHandle? targetName, uint reqFlags, - byte[]? inputBytes, + ref byte inputBytes, int inputLength, ref GssBuffer token, out uint retFlags, [MarshalAs(UnmanagedType.Bool)] out bool isNtlmUsed); [LibraryImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_InitSecContextEx")] - internal static partial Status InitSecContext( + private static partial Status InitSecContext( out Status minorStatus, SafeGssCredHandle initiatorCredHandle, ref SafeGssContextHandle contextHandle, @@ -96,23 +96,99 @@ internal static partial Status InitSecContext( int cbtSize, SafeGssNameHandle? targetName, uint reqFlags, - byte[]? inputBytes, + ref byte inputBytes, int inputLength, ref GssBuffer token, out uint retFlags, [MarshalAs(UnmanagedType.Bool)] out bool isNtlmUsed); + internal static Status InitSecContext( + out Status minorStatus, + SafeGssCredHandle initiatorCredHandle, + ref SafeGssContextHandle contextHandle, + bool isNtlmOnly, + SafeGssNameHandle? targetName, + uint reqFlags, + ReadOnlySpan inputBytes, + ref GssBuffer token, + out uint retFlags, + out bool isNtlmUsed) + { + return InitSecContext( + out minorStatus, + initiatorCredHandle, + ref contextHandle, + isNtlmOnly, + targetName, + reqFlags, + ref MemoryMarshal.GetReference(inputBytes), + inputBytes.Length, + ref token, + out retFlags, + out isNtlmUsed); + } + + internal static Status InitSecContext( + out Status minorStatus, + SafeGssCredHandle initiatorCredHandle, + ref SafeGssContextHandle contextHandle, + bool isNtlmOnly, + IntPtr cbt, + int cbtSize, + SafeGssNameHandle? targetName, + uint reqFlags, + ReadOnlySpan inputBytes, + ref GssBuffer token, + out uint retFlags, + out bool isNtlmUsed) + { + return InitSecContext( + out minorStatus, + initiatorCredHandle, + ref contextHandle, + isNtlmOnly, + cbt, + cbtSize, + targetName, + reqFlags, + ref MemoryMarshal.GetReference(inputBytes), + inputBytes.Length, + ref token, + out retFlags, + out isNtlmUsed); + } + [LibraryImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_AcceptSecContext")] - internal static partial Status AcceptSecContext( + private static partial Status AcceptSecContext( out Status minorStatus, SafeGssCredHandle acceptorCredHandle, ref SafeGssContextHandle acceptContextHandle, - byte[]? inputBytes, + ref byte inputBytes, int inputLength, ref GssBuffer token, out uint retFlags, [MarshalAs(UnmanagedType.Bool)] out bool isNtlmUsed); + internal static Status AcceptSecContext( + out Status minorStatus, + SafeGssCredHandle acceptorCredHandle, + ref SafeGssContextHandle acceptContextHandle, + ReadOnlySpan inputBytes, + ref GssBuffer token, + out uint retFlags, + out bool isNtlmUsed) + { + return AcceptSecContext( + out minorStatus, + acceptorCredHandle, + ref acceptContextHandle, + ref MemoryMarshal.GetReference(inputBytes), + inputBytes.Length, + ref token, + out retFlags, + out isNtlmUsed); + } + [LibraryImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_DeleteSecContext")] internal static partial Status DeleteSecContext( out Status minorStatus, diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs index 3a5546c1f3e38..3ee417f509c92 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs @@ -233,11 +233,16 @@ internal int MakeSignature(byte[] buffer, int offset, int count, [AllowNull] ref internal byte[]? GetOutgoingBlob(byte[]? incomingBlob, bool throwOnError) { - return GetOutgoingBlob(incomingBlob, throwOnError, out _); + return GetOutgoingBlob(incomingBlob != null ? incomingBlob.AsSpan() : default, throwOnError, out _); } // Accepts an incoming binary security blob and returns an outgoing binary security blob. internal byte[]? GetOutgoingBlob(byte[]? incomingBlob, bool throwOnError, out SecurityStatusPal statusCode) + { + return GetOutgoingBlob(incomingBlob != null ? incomingBlob.AsSpan() : default, throwOnError, out statusCode); + } + + internal byte[]? GetOutgoingBlob(ReadOnlySpan incomingBlob, bool throwOnError, out SecurityStatusPal statusCode) { byte[]? result = new byte[_tokenSize]; diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs index ce34a1458e473..3b355818ddf7b 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs @@ -330,18 +330,23 @@ internal void CloseContext() internal byte[]? GetOutgoingBlob(byte[]? incomingBlob, bool throwOnError) { - return GetOutgoingBlob(incomingBlob, throwOnError, out _); + return GetOutgoingBlob(incomingBlob != null ? incomingBlob.AsSpan() : default, throwOnError, out _); } // Accepts an incoming binary security blob and returns an outgoing binary security blob. - internal unsafe byte[]? GetOutgoingBlob(byte[]? incomingBlob, bool throwOnError, out SecurityStatusPal statusCode) + internal byte[]? GetOutgoingBlob(byte[]? incomingBlob, bool throwOnError, out SecurityStatusPal statusCode) + { + return GetOutgoingBlob(incomingBlob != null ? incomingBlob.AsSpan() : default, throwOnError, out statusCode); + } + + internal unsafe byte[]? GetOutgoingBlob(ReadOnlySpan incomingBlob, bool throwOnError, out SecurityStatusPal statusCode) { byte[]? outgoingBlob; // TODO: Logging, validation if (_negotiateMessage == null) { - Debug.Assert(incomingBlob == null); + Debug.Assert(incomingBlob.IsEmpty); _negotiateMessage = new byte[sizeof(NegotiateMessage)]; CreateNtlmNegotiateMessage(_negotiateMessage); @@ -351,7 +356,7 @@ internal void CloseContext() } else { - Debug.Assert(incomingBlob != null); + Debug.Assert(!incomingBlob.IsEmpty); if (!_isSpNego) { @@ -638,23 +643,22 @@ private static byte[] DeriveKey(ReadOnlySpan exportedSessionKey, ReadOnlyS } // This gets decoded byte blob and returns response in binary form. - private unsafe byte[]? ProcessChallenge(byte[] blob, out SecurityStatusPal statusCode) + private unsafe byte[]? ProcessChallenge(ReadOnlySpan blob, out SecurityStatusPal statusCode) { // TODO: Validate size and offsets - ReadOnlySpan asBytes = new ReadOnlySpan(blob); - ref readonly ChallengeMessage challengeMessage = ref MemoryMarshal.AsRef(asBytes.Slice(0, sizeof(ChallengeMessage))); + ref readonly ChallengeMessage challengeMessage = ref MemoryMarshal.AsRef(blob.Slice(0, sizeof(ChallengeMessage))); // Verify message type and signature if (challengeMessage.Header.MessageType != MessageType.Challenge || - !NtlmHeader.SequenceEqual(asBytes.Slice(0, NtlmHeader.Length))) + !NtlmHeader.SequenceEqual(blob.Slice(0, NtlmHeader.Length))) { statusCode = SecurityStatusPalInvalidToken; return null; } Flags flags = BitConverter.IsLittleEndian ? challengeMessage.Flags : (Flags)BinaryPrimitives.ReverseEndianness((uint)challengeMessage.Flags); - ReadOnlySpan targetName = GetField(challengeMessage.TargetName, asBytes); + ReadOnlySpan targetName = GetField(challengeMessage.TargetName, blob); // Only NTLMv2 with MIC is supported // @@ -666,7 +670,7 @@ private static byte[] DeriveKey(ReadOnlySpan exportedSessionKey, ReadOnlyS return null; } - ReadOnlySpan targetInfo = GetField(challengeMessage.TargetInfo, asBytes); + ReadOnlySpan targetInfo = GetField(challengeMessage.TargetInfo, blob); byte[] targetInfoBuffer = ProcessTargetInfo(targetInfo, out DateTime time, out bool hasNbNames); // If NTLM v2 authentication is used and the CHALLENGE_MESSAGE does not contain both @@ -717,7 +721,7 @@ private static byte[] DeriveKey(ReadOnlySpan exportedSessionKey, ReadOnlyS payloadOffset += ChallengeResponseLength; // Create NTLM2 response - ReadOnlySpan serverChallenge = asBytes.Slice(24, 8); + ReadOnlySpan serverChallenge = blob.Slice(24, 8); makeNtlm2ChallengeResponse(time, ntlm2hash, serverChallenge, clientChallenge, targetInfoBuffer, ref response.NtChallengeResponse, payload, ref payloadOffset); Debug.Assert(payloadOffset == sizeof(AuthenticateMessage) + ChallengeResponseLength + sizeof(NtChallengeResponse) + targetInfoBuffer.Length); @@ -872,7 +876,7 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan ntlmNegoti return writer.Encode(); } - private unsafe byte[]? ProcessSpNegoChallenge(byte[] challenge, out SecurityStatusPal statusCode) + private unsafe byte[]? ProcessSpNegoChallenge(ReadOnlySpan challenge, out SecurityStatusPal statusCode) { NegState state = NegState.Unknown; string? mech = null; @@ -881,8 +885,8 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan ntlmNegoti try { - AsnReader reader = new AsnReader(challenge, AsnEncodingRules.DER); - AsnReader challengeReader = reader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegotiationToken.NegTokenResp)); + AsnValueReader reader = new AsnValueReader(challenge, AsnEncodingRules.DER); + AsnValueReader challengeReader = reader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegotiationToken.NegTokenResp)); reader.ThrowIfNotEmpty(); // NegTokenResp ::= SEQUENCE { @@ -904,28 +908,28 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan ntlmNegoti if (challengeReader.HasData && challengeReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.NegState))) { - AsnReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.NegState)); + AsnValueReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.NegState)); state = valueReader.ReadEnumeratedValue(); valueReader.ThrowIfNotEmpty(); } if (challengeReader.HasData && challengeReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.SupportedMech))) { - AsnReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.SupportedMech)); + AsnValueReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.SupportedMech)); mech = valueReader.ReadObjectIdentifier(); valueReader.ThrowIfNotEmpty(); } if (challengeReader.HasData && challengeReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.ResponseToken))) { - AsnReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.ResponseToken)); + AsnValueReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.ResponseToken)); blob = valueReader.ReadOctetString(); valueReader.ThrowIfNotEmpty(); } if (challengeReader.HasData && challengeReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.MechListMIC))) { - AsnReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.MechListMIC)); + AsnValueReader valueReader = challengeReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, (int)NegTokenResp.MechListMIC)); mechListMIC = valueReader.ReadOctetString(); valueReader.ThrowIfNotEmpty(); } @@ -934,13 +938,8 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan ntlmNegoti } catch (AsnContentException) { -<<<<<<< HEAD statusCode = SecurityStatusPalInvalidToken; return null; -======= - statusCode = new SecurityStatusPal(SecurityStatusPalErrorCode.IllegalMessage, e); - return Array.Empty(); ->>>>>>> WIP: Add implementation of NegotiateAuthentication } if (blob?.Length > 0) @@ -949,14 +948,9 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan ntlmNegoti // message with the challenge blob. if (!NtlmOid.Equals(mech)) { -<<<<<<< HEAD if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Server requested unknown mechanism {mech}"); statusCode = SecurityStatusPalPackageNotFound; return null; -======= - statusCode = new SecurityStatusPal(SecurityStatusPalErrorCode.PackageNotFound); - return Array.Empty(); ->>>>>>> WIP: Add implementation of NegotiateAuthentication } // Process decoded NTLM blob. @@ -987,11 +981,7 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan ntlmNegoti } } -<<<<<<< HEAD statusCode = state == NegState.RequestMic ? SecurityStatusPalContinueNeeded : SecurityStatusPalOk; -======= - statusCode = SecurityStatusPalContinueNeeded; ->>>>>>> WIP: Add implementation of NegotiateAuthentication return writer.Encode(); } } @@ -1000,7 +990,6 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan ntlmNegoti { if (_spnegoMechList == null || state != NegState.AcceptCompleted) { -<<<<<<< HEAD statusCode = SecurityStatusPalInternalError; return null; } @@ -1009,15 +998,10 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan ntlmNegoti { statusCode = SecurityStatusPalMessageAltered; return null; -======= - statusCode = new SecurityStatusPal(SecurityStatusPalErrorCode.MessageAltered); - return Array.Empty(); ->>>>>>> WIP: Add implementation of NegotiateAuthentication } } IsCompleted = state == NegState.AcceptCompleted || state == NegState.Reject; -<<<<<<< HEAD statusCode = state switch { NegState.AcceptCompleted => SecurityStatusPalOk, NegState.AcceptIncomplete => SecurityStatusPalContinueNeeded, @@ -1026,11 +1010,6 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan ntlmNegoti }; return null; -======= - - statusCode = IsCompleted ? SecurityStatusPalOk : new SecurityStatusPal(SecurityStatusPalErrorCode.LogonDenied); - return Array.Empty(); ->>>>>>> WIP: Add implementation of NegotiateAuthentication } #pragma warning disable CA1822 diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs index 291795b418b38..89d465086114d 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs @@ -97,7 +97,7 @@ private static bool GssInitSecurityContext( ChannelBinding? channelBinding, SafeGssNameHandle? targetName, Interop.NetSecurityNative.GssFlags inFlags, - byte[]? buffer, + ReadOnlySpan buffer, out byte[]? outputBuffer, out uint outFlags, out bool isNtlmUsed) @@ -139,7 +139,6 @@ private static bool GssInitSecurityContext( targetName, (uint)inFlags, buffer, - (buffer == null) ? 0 : buffer.Length, ref token, out outFlags, out isNtlmUsed); @@ -153,7 +152,6 @@ private static bool GssInitSecurityContext( targetName, (uint)inFlags, buffer, - (buffer == null) ? 0 : buffer.Length, ref token, out outFlags, out isNtlmUsed); @@ -183,7 +181,7 @@ private static bool GssInitSecurityContext( private static bool GssAcceptSecurityContext( ref SafeGssContextHandle? context, SafeGssCredHandle credential, - byte[]? buffer, + ReadOnlySpan buffer, out byte[] outputBuffer, out uint outFlags, out bool isNtlmUsed) @@ -207,7 +205,6 @@ private static bool GssAcceptSecurityContext( credential, ref context, buffer, - buffer?.Length ?? 0, ref token, out outFlags, out isNtlmUsed); @@ -276,7 +273,7 @@ private static SecurityStatusPal EstablishSecurityContext( ChannelBinding? channelBinding, string? targetName, ContextFlagsPal inFlags, - byte[]? incomingBlob, + ReadOnlySpan incomingBlob, ref byte[]? resultBuffer, ref ContextFlagsPal outFlags) { @@ -354,7 +351,7 @@ internal static SecurityStatusPal InitializeSecurityContext( ref SafeDeleteContext? securityContext, string? spn, ContextFlagsPal requestedContextFlags, - byte[]? incomingBlob, + ReadOnlySpan incomingBlob, ChannelBinding? channelBinding, ref byte[]? resultBlob, ref ContextFlagsPal contextFlags) @@ -383,7 +380,7 @@ internal static SecurityStatusPal AcceptSecurityContext( SafeFreeCredentials? credentialsHandle, ref SafeDeleteContext? securityContext, ContextFlagsPal requestedContextFlags, - byte[]? incomingBlob, + ReadOnlySpan incomingBlob, ChannelBinding? channelBinding, ref byte[] resultBlob, ref ContextFlagsPal contextFlags) diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs index a145951254a76..df2d702195b46 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs @@ -75,14 +75,14 @@ internal static SecurityStatusPal InitializeSecurityContext( ref SafeDeleteContext? securityContext, string? spn, ContextFlagsPal requestedContextFlags, - byte[]? incomingBlob, + ReadOnlySpan incomingBlob, ChannelBinding? channelBinding, ref byte[]? resultBlob, ref ContextFlagsPal contextFlags) { InputSecurityBuffers inputBuffers = default; - if (incomingBlob != null) + if (!incomingBlob.IsEmpty) { inputBuffers.SetNextBuffer(new InputSecurityBuffer(incomingBlob, SecurityBufferType.SECBUFFER_TOKEN)); } @@ -132,13 +132,13 @@ internal static SecurityStatusPal AcceptSecurityContext( SafeFreeCredentials? credentialsHandle, ref SafeDeleteContext? securityContext, ContextFlagsPal requestedContextFlags, - byte[]? incomingBlob, + ReadOnlySpan incomingBlob, ChannelBinding? channelBinding, ref byte[]? resultBlob, ref ContextFlagsPal contextFlags) { InputSecurityBuffers inputBuffers = default; - if (incomingBlob != null) + if (incomingBlob.IsEmpty) { inputBuffers.SetNextBuffer(new InputSecurityBuffer(incomingBlob, SecurityBufferType.SECBUFFER_TOKEN)); } diff --git a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnValueReader.cs b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnValueReader.cs index 67e9d869a68db..076a01d142d12 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnValueReader.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Asn1Reader/AsnValueReader.cs @@ -208,6 +208,13 @@ internal string ReadCharacterString(UniversalTagNumber encodingType, Asn1Tag? ex _span = _span.Slice(consumed); return ret; } + + internal TEnum ReadEnumeratedValue(Asn1Tag? expectedTag = null) where TEnum : Enum + { + TEnum ret = AsnDecoder.ReadEnumeratedValue(_span, _ruleSet, out int consumed, expectedTag); + _span = _span.Slice(consumed); + return ret; + } } internal static class AsnWriterExtensions diff --git a/src/libraries/System.Net.Security/src/Resources/Strings.resx b/src/libraries/System.Net.Security/src/Resources/Strings.resx index 0aef32953c3b0..2e4075f7d5e2a 100644 --- a/src/libraries/System.Net.Security/src/Resources/Strings.resx +++ b/src/libraries/System.Net.Security/src/Resources/Strings.resx @@ -389,4 +389,7 @@ Sending trust in handshake is not supported on this platform. + + ASN1 corrupted data. + diff --git a/src/libraries/System.Net.Security/src/System.Net.Security.csproj b/src/libraries/System.Net.Security/src/System.Net.Security.csproj index 8dc0edfef137c..139ea7ce4ba06 100644 --- a/src/libraries/System.Net.Security/src/System.Net.Security.csproj +++ b/src/libraries/System.Net.Security/src/System.Net.Security.csproj @@ -16,6 +16,7 @@ $(DefineConstants);SYSNETSECURITY_NO_OPENSSL ReferenceAssemblyExclusions.txt + @@ -295,6 +296,8 @@ Link="Common\System\Net\Security\MD4.cs" /> + @@ -443,5 +446,6 @@ + diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs index d09e792119075..24383587fb592 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs @@ -99,7 +99,7 @@ public IIdentity RemoteIdentity public byte[]? GetOutgoingBlob(ReadOnlySpan incomingBlob, out NegotiateAuthenticationStatusCode statusCode) { - byte[]? blob = _ntAuthentication.GetOutgoingBlob(incomingBlob.ToArray(), false, out SecurityStatusPal securityStatus); + byte[]? blob = _ntAuthentication.GetOutgoingBlob(incomingBlob, false, out SecurityStatusPal securityStatus); // Map error codes statusCode = securityStatus.ErrorCode switch From d0d1bba548063347b1efadacc58469a789b29717 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sat, 4 Jun 2022 12:32:17 +0200 Subject: [PATCH 04/28] Update comments --- .../src/System/Net/Security/NegotiateAuthentication.cs | 4 ++++ .../System/Net/Security/NegotiateAuthenticationStatusCode.cs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs index 24383587fb592..be67dc0476343 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs @@ -7,6 +7,10 @@ namespace System.Net.Security { + /// + /// Represents a stateful authentication exchange that uses the Negotiate, NTLM or Kerberos security protocols + /// to authenticate the client or server, in client-server communication. + /// public sealed class NegotiateAuthentication : IDisposable { private NTAuthentication _ntAuthentication; diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs index 4f0e1a3ea4a18..24b02014d26d2 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs @@ -11,7 +11,7 @@ public enum NegotiateAuthenticationStatusCode GenericFailure, // GSS_S_FAILURE/GSS_S_NO_CONTEXT BadBinding, // GSS_S_BAD_BINDINGS Unsupported, // GSS_S_BAD_MECH (Unsupported mechanism) - MessageAltered, // GSS_S_BAD_SIG = GSS_S_BAD_MIC + MessageAltered, // GSS_S_BAD_SIG/GSS_S_BAD_MIC ContextExpired, // GSS_S_CONTEXT_EXPIRED CredentialsExpired, // GSS_S_CREDENTIALS_EXPIRED InvalidCredentials, // GSS_S_DEFECTIVE_CREDENTIAL From 982e7c32297619acb0356515b94e5c7f4a988daf Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sat, 4 Jun 2022 12:40:07 +0200 Subject: [PATCH 05/28] Move NegotiateStreamPal.Encrypt/Decrypt to shared sources. Unix implementation already had them and they get trimmed anyway. --- .../Security/NegotiateStreamPal.Windows.cs | 196 ++++++++++++++++++ .../Security/NegotiateStreamPal.Windows.cs | 196 ------------------ 2 files changed, 196 insertions(+), 196 deletions(-) diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs index df2d702195b46..26f2b0367149f 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs @@ -263,5 +263,201 @@ internal static int MakeSignature(SafeDeleteContext securityContext, byte[] buff // return signed size return securityBuffer[0].size + securityBuffer[1].size; } + + internal static int Encrypt( + SafeDeleteContext securityContext, + ReadOnlySpan buffer, + bool isConfidential, + bool isNtlm, + [NotNull] ref byte[]? output, + uint sequenceNumber) + { + SecPkgContext_Sizes sizes = default; + bool success = SSPIWrapper.QueryBlittableContextAttributes(GlobalSSPI.SSPIAuth, securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_SIZES, ref sizes); + Debug.Assert(success); + + int maxCount = checked(int.MaxValue - 4 - sizes.cbBlockSize - sizes.cbSecurityTrailer); + if (buffer.Length > maxCount) + { + throw new ArgumentOutOfRangeException(nameof(buffer.Length), SR.Format(SR.net_io_out_range, maxCount)); + } + + int resultSize = buffer.Length + sizes.cbSecurityTrailer + sizes.cbBlockSize; + if (output == null || output.Length < resultSize + 4) + { + output = new byte[resultSize + 4]; + } + + // Make a copy of user data for in-place encryption. + buffer.CopyTo(output.AsSpan(4 + sizes.cbSecurityTrailer)); + + // Prepare buffers TOKEN(signature), DATA and Padding. + ThreeSecurityBuffers buffers = default; + var securityBuffer = MemoryMarshal.CreateSpan(ref buffers._item0, 3); + securityBuffer[0] = new SecurityBuffer(output, 4, sizes.cbSecurityTrailer, SecurityBufferType.SECBUFFER_TOKEN); + securityBuffer[1] = new SecurityBuffer(output, 4 + sizes.cbSecurityTrailer, buffer.Length, SecurityBufferType.SECBUFFER_DATA); + securityBuffer[2] = new SecurityBuffer(output, 4 + sizes.cbSecurityTrailer + buffer.Length, sizes.cbBlockSize, SecurityBufferType.SECBUFFER_PADDING); + + int errorCode; + if (isConfidential) + { + errorCode = SSPIWrapper.EncryptMessage(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); + } + else + { + if (isNtlm) + { + securityBuffer[1].type |= SecurityBufferType.SECBUFFER_READONLY; + } + + errorCode = SSPIWrapper.MakeSignature(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, 0); + } + + if (errorCode != 0) + { + Exception e = new Win32Exception(errorCode); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, e); + throw e; + } + + // Compacting the result. + resultSize = securityBuffer[0].size; + bool forceCopy = false; + if (resultSize != sizes.cbSecurityTrailer) + { + forceCopy = true; + Buffer.BlockCopy(output, securityBuffer[1].offset, output, 4 + resultSize, securityBuffer[1].size); + } + + resultSize += securityBuffer[1].size; + if (securityBuffer[2].size != 0 && (forceCopy || resultSize != (buffer.Length + sizes.cbSecurityTrailer))) + { + Buffer.BlockCopy(output, securityBuffer[2].offset, output, 4 + resultSize, securityBuffer[2].size); + } + + resultSize += securityBuffer[2].size; + unchecked + { + output[0] = (byte)((resultSize) & 0xFF); + output[1] = (byte)(((resultSize) >> 8) & 0xFF); + output[2] = (byte)(((resultSize) >> 16) & 0xFF); + output[3] = (byte)(((resultSize) >> 24) & 0xFF); + } + + return resultSize + 4; + } + + internal static int Decrypt( + SafeDeleteContext securityContext, + byte[]? buffer, + int offset, + int count, + bool isConfidential, + bool isNtlm, + out int newOffset, + uint sequenceNumber) + { + if (offset < 0 || offset > (buffer == null ? 0 : buffer.Length)) + { + Debug.Fail("Argument 'offset' out of range."); + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + if (count < 0 || count > (buffer == null ? 0 : buffer.Length - offset)) + { + Debug.Fail("Argument 'count' out of range."); + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (isNtlm) + { + return DecryptNtlm(securityContext, buffer, offset, count, isConfidential, out newOffset, sequenceNumber); + } + + // + // Kerberos and up + // + TwoSecurityBuffers buffers = default; + var securityBuffer = MemoryMarshal.CreateSpan(ref buffers._item0, 2); + securityBuffer[0] = new SecurityBuffer(buffer, offset, count, SecurityBufferType.SECBUFFER_STREAM); + securityBuffer[1] = new SecurityBuffer(0, SecurityBufferType.SECBUFFER_DATA); + + int errorCode; + if (isConfidential) + { + errorCode = SSPIWrapper.DecryptMessage(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); + } + else + { + errorCode = SSPIWrapper.VerifySignature(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); + } + + if (errorCode != 0) + { + Exception e = new Win32Exception(errorCode); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, e); + throw e; + } + + if (securityBuffer[1].type != SecurityBufferType.SECBUFFER_DATA) + { + throw new InternalException(securityBuffer[1].type); + } + + newOffset = securityBuffer[1].offset; + return securityBuffer[1].size; + } + + private static int DecryptNtlm( + SafeDeleteContext securityContext, + byte[]? buffer, + int offset, + int count, + bool isConfidential, + out int newOffset, + uint sequenceNumber) + { + const int ntlmSignatureLength = 16; + // For the most part the arguments are verified in Decrypt(). + if (count < ntlmSignatureLength) + { + Debug.Fail("Argument 'count' out of range."); + throw new ArgumentOutOfRangeException(nameof(count)); + } + + TwoSecurityBuffers buffers = default; + var securityBuffer = MemoryMarshal.CreateSpan(ref buffers._item0, 2); + securityBuffer[0] = new SecurityBuffer(buffer, offset, ntlmSignatureLength, SecurityBufferType.SECBUFFER_TOKEN); + securityBuffer[1] = new SecurityBuffer(buffer, offset + ntlmSignatureLength, count - ntlmSignatureLength, SecurityBufferType.SECBUFFER_DATA); + + int errorCode; + SecurityBufferType realDataType = SecurityBufferType.SECBUFFER_DATA; + + if (isConfidential) + { + errorCode = SSPIWrapper.DecryptMessage(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); + } + else + { + realDataType |= SecurityBufferType.SECBUFFER_READONLY; + securityBuffer[1].type = realDataType; + errorCode = SSPIWrapper.VerifySignature(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); + } + + if (errorCode != 0) + { + Exception e = new Win32Exception(errorCode); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, e); + throw new Win32Exception(errorCode); + } + + if (securityBuffer[1].type != realDataType) + { + throw new InternalException(securityBuffer[1].type); + } + + newOffset = securityBuffer[1].offset; + return securityBuffer[1].size; + } } } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Windows.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Windows.cs index 10ba2f5c772a7..844d87034fb34 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Windows.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Windows.cs @@ -82,201 +82,5 @@ internal static void ValidateImpersonationLevel(TokenImpersonationLevel imperson throw new ArgumentOutOfRangeException(nameof(impersonationLevel), impersonationLevel.ToString(), SR.net_auth_supported_impl_levels); } } - - internal static int Encrypt( - SafeDeleteContext securityContext, - ReadOnlySpan buffer, - bool isConfidential, - bool isNtlm, - [NotNull] ref byte[]? output, - uint sequenceNumber) - { - SecPkgContext_Sizes sizes = default; - bool success = SSPIWrapper.QueryBlittableContextAttributes(GlobalSSPI.SSPIAuth, securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_SIZES, ref sizes); - Debug.Assert(success); - - int maxCount = checked(int.MaxValue - 4 - sizes.cbBlockSize - sizes.cbSecurityTrailer); - if (buffer.Length > maxCount) - { - throw new ArgumentOutOfRangeException(nameof(buffer.Length), SR.Format(SR.net_io_out_range, maxCount)); - } - - int resultSize = buffer.Length + sizes.cbSecurityTrailer + sizes.cbBlockSize; - if (output == null || output.Length < resultSize + 4) - { - output = new byte[resultSize + 4]; - } - - // Make a copy of user data for in-place encryption. - buffer.CopyTo(output.AsSpan(4 + sizes.cbSecurityTrailer)); - - // Prepare buffers TOKEN(signature), DATA and Padding. - ThreeSecurityBuffers buffers = default; - var securityBuffer = MemoryMarshal.CreateSpan(ref buffers._item0, 3); - securityBuffer[0] = new SecurityBuffer(output, 4, sizes.cbSecurityTrailer, SecurityBufferType.SECBUFFER_TOKEN); - securityBuffer[1] = new SecurityBuffer(output, 4 + sizes.cbSecurityTrailer, buffer.Length, SecurityBufferType.SECBUFFER_DATA); - securityBuffer[2] = new SecurityBuffer(output, 4 + sizes.cbSecurityTrailer + buffer.Length, sizes.cbBlockSize, SecurityBufferType.SECBUFFER_PADDING); - - int errorCode; - if (isConfidential) - { - errorCode = SSPIWrapper.EncryptMessage(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); - } - else - { - if (isNtlm) - { - securityBuffer[1].type |= SecurityBufferType.SECBUFFER_READONLY; - } - - errorCode = SSPIWrapper.MakeSignature(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, 0); - } - - if (errorCode != 0) - { - Exception e = new Win32Exception(errorCode); - if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, e); - throw e; - } - - // Compacting the result. - resultSize = securityBuffer[0].size; - bool forceCopy = false; - if (resultSize != sizes.cbSecurityTrailer) - { - forceCopy = true; - Buffer.BlockCopy(output, securityBuffer[1].offset, output, 4 + resultSize, securityBuffer[1].size); - } - - resultSize += securityBuffer[1].size; - if (securityBuffer[2].size != 0 && (forceCopy || resultSize != (buffer.Length + sizes.cbSecurityTrailer))) - { - Buffer.BlockCopy(output, securityBuffer[2].offset, output, 4 + resultSize, securityBuffer[2].size); - } - - resultSize += securityBuffer[2].size; - unchecked - { - output[0] = (byte)((resultSize) & 0xFF); - output[1] = (byte)(((resultSize) >> 8) & 0xFF); - output[2] = (byte)(((resultSize) >> 16) & 0xFF); - output[3] = (byte)(((resultSize) >> 24) & 0xFF); - } - - return resultSize + 4; - } - - internal static int Decrypt( - SafeDeleteContext securityContext, - byte[]? buffer, - int offset, - int count, - bool isConfidential, - bool isNtlm, - out int newOffset, - uint sequenceNumber) - { - if (offset < 0 || offset > (buffer == null ? 0 : buffer.Length)) - { - Debug.Fail("Argument 'offset' out of range."); - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if (count < 0 || count > (buffer == null ? 0 : buffer.Length - offset)) - { - Debug.Fail("Argument 'count' out of range."); - throw new ArgumentOutOfRangeException(nameof(count)); - } - - if (isNtlm) - { - return DecryptNtlm(securityContext, buffer, offset, count, isConfidential, out newOffset, sequenceNumber); - } - - // - // Kerberos and up - // - TwoSecurityBuffers buffers = default; - var securityBuffer = MemoryMarshal.CreateSpan(ref buffers._item0, 2); - securityBuffer[0] = new SecurityBuffer(buffer, offset, count, SecurityBufferType.SECBUFFER_STREAM); - securityBuffer[1] = new SecurityBuffer(0, SecurityBufferType.SECBUFFER_DATA); - - int errorCode; - if (isConfidential) - { - errorCode = SSPIWrapper.DecryptMessage(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); - } - else - { - errorCode = SSPIWrapper.VerifySignature(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); - } - - if (errorCode != 0) - { - Exception e = new Win32Exception(errorCode); - if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, e); - throw e; - } - - if (securityBuffer[1].type != SecurityBufferType.SECBUFFER_DATA) - { - throw new InternalException(securityBuffer[1].type); - } - - newOffset = securityBuffer[1].offset; - return securityBuffer[1].size; - } - - private static int DecryptNtlm( - SafeDeleteContext securityContext, - byte[]? buffer, - int offset, - int count, - bool isConfidential, - out int newOffset, - uint sequenceNumber) - { - const int ntlmSignatureLength = 16; - // For the most part the arguments are verified in Decrypt(). - if (count < ntlmSignatureLength) - { - Debug.Fail("Argument 'count' out of range."); - throw new ArgumentOutOfRangeException(nameof(count)); - } - - TwoSecurityBuffers buffers = default; - var securityBuffer = MemoryMarshal.CreateSpan(ref buffers._item0, 2); - securityBuffer[0] = new SecurityBuffer(buffer, offset, ntlmSignatureLength, SecurityBufferType.SECBUFFER_TOKEN); - securityBuffer[1] = new SecurityBuffer(buffer, offset + ntlmSignatureLength, count - ntlmSignatureLength, SecurityBufferType.SECBUFFER_DATA); - - int errorCode; - SecurityBufferType realDataType = SecurityBufferType.SECBUFFER_DATA; - - if (isConfidential) - { - errorCode = SSPIWrapper.DecryptMessage(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); - } - else - { - realDataType |= SecurityBufferType.SECBUFFER_READONLY; - securityBuffer[1].type = realDataType; - errorCode = SSPIWrapper.VerifySignature(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); - } - - if (errorCode != 0) - { - Exception e = new Win32Exception(errorCode); - if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, e); - throw new Win32Exception(errorCode); - } - - if (securityBuffer[1].type != realDataType) - { - throw new InternalException(securityBuffer[1].type); - } - - newOffset = securityBuffer[1].offset; - return securityBuffer[1].size; - } } } From cbd24a2c4e4c2110a0232ca4d5b77528f1832f19 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sat, 4 Jun 2022 12:44:56 +0200 Subject: [PATCH 06/28] Revert accidental change --- .../System.Net.Http/ref/System.Net.Http.cs | 161 ++++++++++-------- 1 file changed, 88 insertions(+), 73 deletions(-) diff --git a/src/libraries/System.Net.Http/ref/System.Net.Http.cs b/src/libraries/System.Net.Http/ref/System.Net.Http.cs index 5c10452325c38..debcc28d0119a 100644 --- a/src/libraries/System.Net.Http/ref/System.Net.Http.cs +++ b/src/libraries/System.Net.Http/ref/System.Net.Http.cs @@ -4,6 +4,9 @@ // Changes to this file must follow the https://aka.ms/api-review process. // ------------------------------------------------------------------------------ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + namespace System.Net.Http { public partial class ByteArrayContent : System.Net.Http.HttpContent @@ -34,7 +37,12 @@ protected override void Dispose(bool disposing) { } } public partial class FormUrlEncodedContent : System.Net.Http.ByteArrayContent { - public FormUrlEncodedContent(System.Collections.Generic.IEnumerable> nameValueCollection) : base (default(byte[])) { } + public FormUrlEncodedContent( + System.Collections.Generic.IEnumerable> nameValueCollection) : base (default(byte[])) { } protected override System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext? context, System.Threading.CancellationToken cancellationToken) { throw null; } } public delegate System.Text.Encoding? HeaderEncodingSelector(string headerName, TContext context); @@ -148,7 +156,14 @@ public HttpClientHandler() { } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public bool UseProxy { get { throw null; } set { } } protected override void Dispose(bool disposing) { } + // + // Attributes are commented out due to https://github.com/dotnet/arcade/issues/7585 + // API compat will fail until this is fixed + // + //[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")] [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] + //[System.Runtime.Versioning.UnsupportedOSPlatformAttributeUnsupportedOSPlatform("ios")] + //[System.Runtime.Versioning.UnsupportedOSPlatformAttributeUnsupportedOSPlatform("tvos")] protected internal override System.Net.Http.HttpResponseMessage Send(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { throw null; } protected internal override System.Threading.Tasks.Task SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { throw null; } } @@ -186,11 +201,6 @@ protected virtual void SerializeToStream(System.IO.Stream stream, System.Net.Tra protected virtual System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext? context, System.Threading.CancellationToken cancellationToken) { throw null; } protected internal abstract bool TryComputeLength(out long length); } - public enum HttpKeepAlivePingPolicy - { - WithActiveRequests = 0, - Always = 1, - } public abstract partial class HttpMessageHandler : System.IDisposable { protected HttpMessageHandler() { } @@ -221,8 +231,8 @@ public HttpMethod(string method) { } public static System.Net.Http.HttpMethod Post { get { throw null; } } public static System.Net.Http.HttpMethod Put { get { throw null; } } public static System.Net.Http.HttpMethod Trace { get { throw null; } } - public bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] System.Net.Http.HttpMethod? other) { throw null; } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] System.Net.Http.HttpMethod? other) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static bool operator ==(System.Net.Http.HttpMethod? left, System.Net.Http.HttpMethod? right) { throw null; } public static bool operator !=(System.Net.Http.HttpMethod? left, System.Net.Http.HttpMethod? right) { throw null; } @@ -244,9 +254,9 @@ public HttpRequestMessage(System.Net.Http.HttpMethod method, System.Uri? request public System.Net.Http.HttpContent? Content { get { throw null; } set { } } public System.Net.Http.Headers.HttpRequestHeaders Headers { get { throw null; } } public System.Net.Http.HttpMethod Method { get { throw null; } set { } } - public System.Net.Http.HttpRequestOptions Options { get { throw null; } } [System.ObsoleteAttribute("HttpRequestMessage.Properties has been deprecated. Use Options instead.")] public System.Collections.Generic.IDictionary Properties { get { throw null; } } + public HttpRequestOptions Options { get { throw null; } } public System.Uri? RequestUri { get { throw null; } set { } } public System.Version Version { get { throw null; } set { } } public System.Net.Http.HttpVersionPolicy VersionPolicy { get { throw null; } set { } } @@ -254,35 +264,35 @@ public void Dispose() { } protected virtual void Dispose(bool disposing) { } public override string ToString() { throw null; } } - public sealed partial class HttpRequestOptions : System.Collections.Generic.ICollection>, System.Collections.Generic.IDictionary, System.Collections.Generic.IEnumerable>, System.Collections.IEnumerable + + public readonly struct HttpRequestOptionsKey { - public HttpRequestOptions() { } - int System.Collections.Generic.ICollection>.Count { get { throw null; } } - bool System.Collections.Generic.ICollection>.IsReadOnly { get { throw null; } } - object? System.Collections.Generic.IDictionary.this[string key] { get { throw null; } set { } } + public HttpRequestOptionsKey(string key) {} + public string Key { get { throw null; } } + } + + public sealed class HttpRequestOptions : System.Collections.Generic.IDictionary + { + void System.Collections.Generic.IDictionary.Add(string key, object? value) { throw null; } System.Collections.Generic.ICollection System.Collections.Generic.IDictionary.Keys { get { throw null; } } System.Collections.Generic.ICollection System.Collections.Generic.IDictionary.Values { get { throw null; } } - public void Set(System.Net.Http.HttpRequestOptionsKey key, TValue value) { } - void System.Collections.Generic.ICollection>.Add(System.Collections.Generic.KeyValuePair item) { } - void System.Collections.Generic.ICollection>.Clear() { } - bool System.Collections.Generic.ICollection>.Contains(System.Collections.Generic.KeyValuePair item) { throw null; } - void System.Collections.Generic.ICollection>.CopyTo(System.Collections.Generic.KeyValuePair[] array, int arrayIndex) { } - bool System.Collections.Generic.ICollection>.Remove(System.Collections.Generic.KeyValuePair item) { throw null; } - void System.Collections.Generic.IDictionary.Add(string key, object value) { } - bool System.Collections.Generic.IDictionary.ContainsKey(string key) { throw null; } bool System.Collections.Generic.IDictionary.Remove(string key) { throw null; } - bool System.Collections.Generic.IDictionary.TryGetValue(string key, out object value) { throw null; } - System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; } + bool System.Collections.Generic.ICollection>.Remove(System.Collections.Generic.KeyValuePair item) { throw null; } + bool System.Collections.Generic.IDictionary.TryGetValue(string key, out object? value) { throw null; } + object? System.Collections.Generic.IDictionary.this[string key] { get { throw null; } set { } } + void System.Collections.Generic.ICollection>.Add(System.Collections.Generic.KeyValuePair item) { throw null; } + void System.Collections.Generic.ICollection>.Clear() { throw null; } + bool System.Collections.Generic.ICollection>.Contains(System.Collections.Generic.KeyValuePair item) { throw null; } + bool System.Collections.Generic.IDictionary.ContainsKey(string key) { throw null; } + void System.Collections.Generic.ICollection>.CopyTo(System.Collections.Generic.KeyValuePair[] array, int arrayIndex) { throw null; } + int System.Collections.Generic.ICollection>.Count { get { throw null; } } + bool System.Collections.Generic.ICollection>.IsReadOnly { get { throw null; } } + System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } - public bool TryGetValue(System.Net.Http.HttpRequestOptionsKey key, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out TValue value) { throw null; } - } - public readonly partial struct HttpRequestOptionsKey - { - private readonly object _dummy; - private readonly int _dummyPrimitive; - public HttpRequestOptionsKey(string key) { throw null; } - public string Key { get { throw null; } } + public bool TryGetValue(HttpRequestOptionsKey key, [MaybeNullWhen(false)] out TValue value) { throw null; } + public void Set(HttpRequestOptionsKey key, TValue value) { throw null; } } + public partial class HttpResponseMessage : System.IDisposable { public HttpResponseMessage() { } @@ -353,39 +363,28 @@ protected override void SerializeToStream(System.IO.Stream stream, System.Net.Tr protected override System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext? context, System.Threading.CancellationToken cancellationToken) { throw null; } protected internal override bool TryComputeLength(out long length) { throw null; } } - public sealed partial class SocketsHttpConnectionContext - { - internal SocketsHttpConnectionContext() { } - public System.Net.DnsEndPoint DnsEndPoint { get { throw null; } } - public System.Net.Http.HttpRequestMessage InitialRequestMessage { get { throw null; } } - } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public sealed partial class SocketsHttpHandler : System.Net.Http.HttpMessageHandler { public SocketsHttpHandler() { } - [System.CLSCompliantAttribute(false)] - public System.Diagnostics.DistributedContextPropagator? ActivityHeadersPropagator { get { throw null; } set { } } + public int InitialHttp2StreamWindowSize { get { throw null; } set { } } + [System.Runtime.Versioning.UnsupportedOSPlatformGuardAttribute("browser")] + public static bool IsSupported { get { throw null; } } public bool AllowAutoRedirect { get { throw null; } set { } } public System.Net.DecompressionMethods AutomaticDecompression { get { throw null; } set { } } - public System.Func>? ConnectCallback { get { throw null; } set { } } public System.TimeSpan ConnectTimeout { get { throw null; } set { } } [System.Diagnostics.CodeAnalysis.AllowNullAttribute] public System.Net.CookieContainer CookieContainer { get { throw null; } set { } } public System.Net.ICredentials? Credentials { get { throw null; } set { } } public System.Net.ICredentials? DefaultProxyCredentials { get { throw null; } set { } } - public bool EnableMultipleHttp2Connections { get { throw null; } set { } } public System.TimeSpan Expect100ContinueTimeout { get { throw null; } set { } } - public int InitialHttp2StreamWindowSize { get { throw null; } set { } } - [System.Runtime.Versioning.UnsupportedOSPlatformGuardAttribute("browser")] - public static bool IsSupported { get { throw null; } } public System.TimeSpan KeepAlivePingDelay { get { throw null; } set { } } - public System.Net.Http.HttpKeepAlivePingPolicy KeepAlivePingPolicy { get { throw null; } set { } } public System.TimeSpan KeepAlivePingTimeout { get { throw null; } set { } } + public HttpKeepAlivePingPolicy KeepAlivePingPolicy { get { throw null; } set { } } public int MaxAutomaticRedirections { get { throw null; } set { } } public int MaxConnectionsPerServer { get { throw null; } set { } } public int MaxResponseDrainSize { get { throw null; } set { } } public int MaxResponseHeadersLength { get { throw null; } set { } } - public System.Func>? PlaintextStreamFilter { get { throw null; } set { } } public System.TimeSpan PooledConnectionIdleTimeout { get { throw null; } set { } } public System.TimeSpan PooledConnectionLifetime { get { throw null; } set { } } public bool PreAuthenticate { get { throw null; } set { } } @@ -401,13 +400,29 @@ public SocketsHttpHandler() { } protected override void Dispose(bool disposing) { } protected internal override System.Net.Http.HttpResponseMessage Send(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { throw null; } protected internal override System.Threading.Tasks.Task SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { throw null; } + public bool EnableMultipleHttp2Connections { get { throw null; } set { } } + public Func>? ConnectCallback { get { throw null; } set { } } + public Func>? PlaintextStreamFilter { get { throw null; } set { } } + [System.CLSCompliantAttribute(false)] + public System.Diagnostics.DistributedContextPropagator? ActivityHeadersPropagator { get { throw null; } set { } } } - public sealed partial class SocketsHttpPlaintextStreamFilterContext + public sealed class SocketsHttpConnectionContext + { + internal SocketsHttpConnectionContext() { } + public DnsEndPoint DnsEndPoint { get { throw null; } } + public HttpRequestMessage InitialRequestMessage { get { throw null; } } + } + public sealed class SocketsHttpPlaintextStreamFilterContext { internal SocketsHttpPlaintextStreamFilterContext() { } - public System.Net.Http.HttpRequestMessage InitialRequestMessage { get { throw null; } } - public System.Version NegotiatedHttpVersion { get { throw null; } } public System.IO.Stream PlaintextStream { get { throw null; } } + public Version NegotiatedHttpVersion { get { throw null; } } + public HttpRequestMessage InitialRequestMessage { get { throw null; } } + } + public enum HttpKeepAlivePingPolicy + { + WithActiveRequests, + Always } public partial class StreamContent : System.Net.Http.HttpContent { @@ -439,7 +454,7 @@ public AuthenticationHeaderValue(string scheme) { } public AuthenticationHeaderValue(string scheme, string? parameter) { } public string? Parameter { get { throw null; } } public string Scheme { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.AuthenticationHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -465,7 +480,7 @@ public CacheControlHeaderValue() { } public bool ProxyRevalidate { get { throw null; } set { } } public bool Public { get { throw null; } set { } } public System.TimeSpan? SharedMaxAge { get { throw null; } set { } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.CacheControlHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -485,10 +500,10 @@ public ContentDispositionHeaderValue(string dispositionType) { } public System.Collections.Generic.ICollection Parameters { get { throw null; } } public System.DateTimeOffset? ReadDate { get { throw null; } set { } } public long? Size { get { throw null; } set { } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.ContentDispositionHeaderValue Parse(string? input) { throw null; } - object? System.ICloneable.Clone() { throw null; } + object System.ICloneable.Clone() { throw null; } public override string ToString() { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? input, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Net.Http.Headers.ContentDispositionHeaderValue? parsedValue) { throw null; } } @@ -503,7 +518,7 @@ public ContentRangeHeaderValue(long from, long to, long length) { } public long? Length { get { throw null; } } public long? To { get { throw null; } } public string Unit { get { throw null; } set { } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.ContentRangeHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -517,7 +532,7 @@ public EntityTagHeaderValue(string tag, bool isWeak) { } public static System.Net.Http.Headers.EntityTagHeaderValue Any { get { throw null; } } public bool IsWeak { get { throw null; } } public string Tag { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.EntityTagHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -585,12 +600,12 @@ public void Clear() { } System.Collections.Generic.IEnumerable System.Collections.Generic.IReadOnlyDictionary.Keys { get { throw null; } } System.Collections.Generic.IEnumerable System.Collections.Generic.IReadOnlyDictionary.Values { get { throw null; } } public bool Contains(string headerName) { throw null; } + bool System.Collections.Generic.IReadOnlyDictionary.ContainsKey(string key) { throw null; } public System.Net.Http.Headers.HttpHeadersNonValidated.Enumerator GetEnumerator() { throw null; } System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { throw null; } - bool System.Collections.Generic.IReadOnlyDictionary.ContainsKey(string key) { throw null; } - bool System.Collections.Generic.IReadOnlyDictionary.TryGetValue(string key, out System.Net.Http.Headers.HeaderStringValues value) { throw null; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } public bool TryGetValues(string headerName, out System.Net.Http.Headers.HeaderStringValues values) { throw null; } + bool System.Collections.Generic.IReadOnlyDictionary.TryGetValue(string key, out System.Net.Http.Headers.HeaderStringValues value) { throw null; } public partial struct Enumerator : System.Collections.Generic.IEnumerator>, System.Collections.IEnumerator, System.IDisposable { private object _dummy; @@ -686,10 +701,10 @@ public MediaTypeHeaderValue(string mediaType, string? charSet) { } [System.Diagnostics.CodeAnalysis.DisallowNullAttribute] public string? MediaType { get { throw null; } set { } } public System.Collections.Generic.ICollection Parameters { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.MediaTypeHeaderValue Parse(string? input) { throw null; } - object? System.ICloneable.Clone() { throw null; } + object System.ICloneable.Clone() { throw null; } public override string ToString() { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? input, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Net.Http.Headers.MediaTypeHeaderValue? parsedValue) { throw null; } } @@ -704,12 +719,12 @@ public MediaTypeWithQualityHeaderValue(string mediaType, double quality) : base } public partial class NameValueHeaderValue : System.ICloneable { - protected internal NameValueHeaderValue(System.Net.Http.Headers.NameValueHeaderValue source) { } + protected NameValueHeaderValue(System.Net.Http.Headers.NameValueHeaderValue source) { } public NameValueHeaderValue(string name) { } public NameValueHeaderValue(string name, string? value) { } public string Name { get { throw null; } } public string? Value { get { throw null; } set { } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.NameValueHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -722,7 +737,7 @@ protected NameValueWithParametersHeaderValue(System.Net.Http.Headers.NameValueWi public NameValueWithParametersHeaderValue(string name) : base (default(string)) { } public NameValueWithParametersHeaderValue(string name, string? value) : base (default(string)) { } public System.Collections.Generic.ICollection Parameters { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static new System.Net.Http.Headers.NameValueWithParametersHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -735,7 +750,7 @@ public ProductHeaderValue(string name) { } public ProductHeaderValue(string name, string? version) { } public string Name { get { throw null; } } public string? Version { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.ProductHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -749,7 +764,7 @@ public ProductInfoHeaderValue(string comment) { } public ProductInfoHeaderValue(string productName, string? productVersion) { } public string? Comment { get { throw null; } } public System.Net.Http.Headers.ProductHeaderValue? Product { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.ProductInfoHeaderValue Parse(string input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -763,7 +778,7 @@ public RangeConditionHeaderValue(System.Net.Http.Headers.EntityTagHeaderValue en public RangeConditionHeaderValue(string entityTag) { } public System.DateTimeOffset? Date { get { throw null; } } public System.Net.Http.Headers.EntityTagHeaderValue? EntityTag { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.RangeConditionHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -776,7 +791,7 @@ public RangeHeaderValue() { } public RangeHeaderValue(long? from, long? to) { } public System.Collections.Generic.ICollection Ranges { get { throw null; } } public string Unit { get { throw null; } set { } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.RangeHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -788,7 +803,7 @@ public partial class RangeItemHeaderValue : System.ICloneable public RangeItemHeaderValue(long? from, long? to) { } public long? From { get { throw null; } } public long? To { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } object System.ICloneable.Clone() { throw null; } public override string ToString() { throw null; } @@ -799,7 +814,7 @@ public RetryConditionHeaderValue(System.DateTimeOffset date) { } public RetryConditionHeaderValue(System.TimeSpan delta) { } public System.DateTimeOffset? Date { get { throw null; } } public System.TimeSpan? Delta { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.RetryConditionHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -812,7 +827,7 @@ public StringWithQualityHeaderValue(string value) { } public StringWithQualityHeaderValue(string value, double quality) { } public double? Quality { get { throw null; } } public string Value { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.StringWithQualityHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -825,7 +840,7 @@ protected TransferCodingHeaderValue(System.Net.Http.Headers.TransferCodingHeader public TransferCodingHeaderValue(string value) { } public System.Collections.Generic.ICollection Parameters { get { throw null; } } public string Value { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.TransferCodingHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -850,7 +865,7 @@ public ViaHeaderValue(string protocolVersion, string receivedBy, string? protoco public string? ProtocolName { get { throw null; } } public string ProtocolVersion { get { throw null; } } public string ReceivedBy { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.ViaHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } @@ -865,7 +880,7 @@ public WarningHeaderValue(int code, string agent, string text, System.DateTimeOf public int Code { get { throw null; } } public System.DateTimeOffset? Date { get { throw null; } } public string Text { get { throw null; } } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } public override int GetHashCode() { throw null; } public static System.Net.Http.Headers.WarningHeaderValue Parse(string? input) { throw null; } object System.ICloneable.Clone() { throw null; } From 9d4d557a83f6f9cf60f3f1146e9b574ddbe78d72 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sat, 4 Jun 2022 12:56:43 +0200 Subject: [PATCH 07/28] Build fixes. --- .../Common/src/System/Net/NTAuthentication.Common.cs | 12 ++++++++++-- .../System/Net/Security/NegotiateStreamPal.Unix.cs | 5 +++++ .../Net/Security/NegotiateStreamPal.Windows.cs | 5 +++++ .../src/Resources/Strings.resx | 3 +++ .../System.Net.Mail/src/Resources/Strings.resx | 3 +++ .../System/Net/Security/NegotiateStreamPal.Unix.cs | 5 ----- .../Net/Security/NegotiateStreamPal.Windows.cs | 5 ----- 7 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs index 3ee417f509c92..c54e12353a2ad 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs @@ -352,7 +352,7 @@ internal int Encrypt(ReadOnlySpan buffer, [NotNull] ref byte[]? output, ui return NegotiateStreamPal.Encrypt( _securityContext!, buffer, - IsConfidentialityFlag, + (_contextFlags & ContextFlagsPal.Confidentiality) != 0, IsNTLM, ref output, sequenceNumber); @@ -360,7 +360,15 @@ internal int Encrypt(ReadOnlySpan buffer, [NotNull] ref byte[]? output, ui internal int Decrypt(byte[] payload, int offset, int count, out int newOffset, uint expectedSeqNumber) { - return NegotiateStreamPal.Decrypt(_securityContext!, payload, offset, count, IsConfidentialityFlag, IsNTLM, out newOffset, expectedSeqNumber); + return NegotiateStreamPal.Decrypt( + _securityContext!, + payload, + offset, + count, + (_contextFlags & ContextFlagsPal.Confidentiality) != 0, + IsNTLM, + out newOffset, + expectedSeqNumber); } } } diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs index 89d465086114d..cb038ae9f1dfa 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs @@ -28,6 +28,11 @@ internal static partial class NegotiateStreamPal // defined in winerror.h private const int NTE_FAIL = unchecked((int)0x80090020); + internal static string QueryContextAssociatedName(SafeDeleteContext? securityContext) + { + throw new PlatformNotSupportedException(SR.net_nego_server_not_supported); + } + internal static string QueryContextClientSpecifiedSpn(SafeDeleteContext securityContext) { throw new PlatformNotSupportedException(SR.net_nego_server_not_supported); diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs index 26f2b0367149f..37967d9bfd521 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs @@ -55,6 +55,11 @@ internal static SafeFreeCredentials AcquireCredentialsHandle(string package, boo } } + internal static string? QueryContextAssociatedName(SafeDeleteContext securityContext) + { + return SSPIWrapper.QueryStringContextAttributes(GlobalSSPI.SSPIAuth, securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_NAMES); + } + internal static string? QueryContextClientSpecifiedSpn(SafeDeleteContext securityContext) { return SSPIWrapper.QueryStringContextAttributes(GlobalSSPI.SSPIAuth, securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_CLIENT_SPECIFIED_TARGET); diff --git a/src/libraries/System.Net.HttpListener/src/Resources/Strings.resx b/src/libraries/System.Net.HttpListener/src/Resources/Strings.resx index c9c146f360c39..11a2453619c17 100644 --- a/src/libraries/System.Net.HttpListener/src/Resources/Strings.resx +++ b/src/libraries/System.Net.HttpListener/src/Resources/Strings.resx @@ -131,6 +131,9 @@ {0} can only be called once for each asynchronous operation. + + The byte count must not exceed {0} bytes for this stream type. + Custom channel bindings are not supported. diff --git a/src/libraries/System.Net.Mail/src/Resources/Strings.resx b/src/libraries/System.Net.Mail/src/Resources/Strings.resx index 8f077a3789f67..082461c79ccae 100644 --- a/src/libraries/System.Net.Mail/src/Resources/Strings.resx +++ b/src/libraries/System.Net.Mail/src/Resources/Strings.resx @@ -64,6 +64,9 @@ {0} can only be called once for each asynchronous operation. + + The byte count must not exceed {0} bytes for this stream type. + This method is not implemented by this class. diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Unix.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Unix.cs index 04054004c9d79..b1288fce48655 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Unix.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Unix.cs @@ -31,11 +31,6 @@ internal static IIdentity GetIdentity(NTAuthentication context) return new GenericIdentity(name, protocol); } - internal static string QueryContextAssociatedName(SafeDeleteContext? securityContext) - { - throw new PlatformNotSupportedException(SR.net_nego_server_not_supported); - } - internal static void ValidateImpersonationLevel(TokenImpersonationLevel impersonationLevel) { if (impersonationLevel != TokenImpersonationLevel.Identification) diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Windows.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Windows.cs index 844d87034fb34..39248f0fc7bf6 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Windows.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Windows.cs @@ -68,11 +68,6 @@ internal static IIdentity GetIdentity(NTAuthentication context) return result; } - internal static string? QueryContextAssociatedName(SafeDeleteContext securityContext) - { - return SSPIWrapper.QueryStringContextAttributes(GlobalSSPI.SSPIAuth, securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_NAMES); - } - internal static void ValidateImpersonationLevel(TokenImpersonationLevel impersonationLevel) { if (impersonationLevel != TokenImpersonationLevel.Identification && From c3715151bcd1ccb3ea5cf3cf49efbd73d763f586 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sat, 4 Jun 2022 13:05:55 +0200 Subject: [PATCH 08/28] Fix error handling condition --- .../AuthenticationHelper.NtAuth.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs index 3f8248f1c2289..22c6182de58ed 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs @@ -182,9 +182,9 @@ private static async Task SendWithNtAuthAsync(HttpRequestMe NegotiateAuthenticationStatusCode statusCode; while (true) { - SecurityStatusPal statusCode; - string? challengeResponse = authContext.GetOutgoingBlob(challengeData, throwOnError: false, out statusCode); - if (statusCode.ErrorCode > SecurityStatusPalErrorCode.TryAgain || challengeResponse == null) + NegotiateAuthenticationStatusCode statusCode; + string? challengeResponse = authContext.GetOutgoingBlob(challengeData, out statusCode); + if (statusCode > NegotiateAuthenticationStatusCode.ContinueNeeded || challengeResponse == null) { // Response indicated denial even after login, so stop processing and return current response. break; @@ -206,12 +206,12 @@ private static async Task SendWithNtAuthAsync(HttpRequestMe if (!IsAuthenticationChallenge(response, isProxyAuth)) { // Tail response for Negoatiate on successful authentication. Validate it before we proceed. - authContext.GetOutgoingBlob(challengeData, throwOnError: false, out statusCode); - if (statusCode.ErrorCode != SecurityStatusPalErrorCode.OK) + authContext.GetOutgoingBlob(challengeData, out statusCode); + if (statusCode > NegotiateAuthenticationStatusCode.ContinueNeeded) { isNewConnection = false; connection.Dispose(); - throw new HttpRequestException(SR.Format(SR.net_http_authvalidationfailure, statusCode.ErrorCode), null, HttpStatusCode.Unauthorized); + throw new HttpRequestException(SR.Format(SR.net_http_authvalidationfailure, statusCode), null, HttpStatusCode.Unauthorized); } break; } From 3306c67372c223005e4b1b47f05f886e7b6792be Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sat, 4 Jun 2022 13:33:06 +0200 Subject: [PATCH 09/28] Update error mapping based on HttpListener usage. --- .../src/System/Net/Security/NegotiateAuthentication.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs index be67dc0476343..490f050a64747 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs @@ -132,8 +132,15 @@ public IIdentity RemoteIdentity SecurityStatusPalErrorCode.IllegalMessage => NegotiateAuthenticationStatusCode.InvalidToken, SecurityStatusPalErrorCode.CertExpired => NegotiateAuthenticationStatusCode.CredentialsExpired, SecurityStatusPalErrorCode.SecurityQosFailed => NegotiateAuthenticationStatusCode.QopNotSupported, - SecurityStatusPalErrorCode.UnsupportedPreauth => NegotiateAuthenticationStatusCode.Unsupported, + SecurityStatusPalErrorCode.UnsupportedPreauth => NegotiateAuthenticationStatusCode.InvalidToken, SecurityStatusPalErrorCode.BadBinding => NegotiateAuthenticationStatusCode.BadBinding, + SecurityStatusPalErrorCode.UntrustedRoot => NegotiateAuthenticationStatusCode.UnknownCredentials, + SecurityStatusPalErrorCode.SmartcardLogonRequired => NegotiateAuthenticationStatusCode.UnknownCredentials, + SecurityStatusPalErrorCode.WrongPrincipal => NegotiateAuthenticationStatusCode.UnknownCredentials, + SecurityStatusPalErrorCode.CannotPack => NegotiateAuthenticationStatusCode.InvalidToken, + SecurityStatusPalErrorCode.TimeSkew => NegotiateAuthenticationStatusCode.InvalidToken, + SecurityStatusPalErrorCode.AlgorithmMismatch => NegotiateAuthenticationStatusCode.InvalidToken, + SecurityStatusPalErrorCode.CertUnknown => NegotiateAuthenticationStatusCode.UnknownCredentials, // Processing partial inputs is not supported, so this is result of incorrect input SecurityStatusPalErrorCode.IncompleteMessage => NegotiateAuthenticationStatusCode.InvalidToken, From 065d87c0306c99b2f4a7c47a9a457c3b57c0e3a9 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 7 Jun 2022 15:31:17 +0200 Subject: [PATCH 10/28] WIP: HttpListener test --- .../Net/Windows/HttpListener.Windows.cs | 200 +++++++++--------- 1 file changed, 97 insertions(+), 103 deletions(-) diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpListener.Windows.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpListener.Windows.cs index c8e813205f38e..3d687a826234a 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpListener.Windows.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpListener.Windows.cs @@ -736,9 +736,9 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) // Figure out what schemes we're allowing, what context we have. stoleBlob = true; HttpListenerContext? httpContext = null; - NTAuthentication? oldContext = null; - NTAuthentication? newContext = null; - NTAuthentication? context = null; + NegotiateAuthentication? oldContext = null; + NegotiateAuthentication? newContext = null; + NegotiateAuthentication? context = null; AuthenticationSchemes headerScheme = AuthenticationSchemes.None; AuthenticationSchemes authenticationScheme = AuthenticationSchemes; ExtendedProtectionPolicy extendedProtectionPolicy = _extendedProtectionPolicy; @@ -884,7 +884,7 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) string inBlob = index < authorizationHeader.Length ? authorizationHeader.Substring(index) : ""; IPrincipal? principal = null; - SecurityStatusPal statusCodeNew; + NegotiateAuthenticationStatusCode statusCodeNew; ChannelBinding? binding; if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Performing Authentication headerScheme: {headerScheme}"); switch (headerScheme) @@ -901,8 +901,14 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) else { binding = GetChannelBinding(session, connectionId, isSecureConnection, extendedProtectionPolicy); - ContextFlagsPal contextFlags = GetContextFlags(extendedProtectionPolicy, isSecureConnection); - context = new NTAuthentication(true, package, CredentialCache.DefaultNetworkCredentials, null, contextFlags, binding); + // FIXME + //ContextFlagsPal contextFlags = GetContextFlags(extendedProtectionPolicy, isSecureConnection); + NegotiateAuthenticationServerOptions serverOptions = new NegotiateAuthenticationServerOptions + { + Package = package, + Binding = binding, + }; + context = new NegotiateAuthentication(serverOptions); } try @@ -917,20 +923,20 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) } if (!error) { - decodedOutgoingBlob = context.GetOutgoingBlob(bytes, false, out statusCodeNew); - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"GetOutgoingBlob returned IsCompleted: {context.IsCompleted} and statusCodeNew: {statusCodeNew}"); - error = !context.IsValidContext; + decodedOutgoingBlob = context.GetOutgoingBlob(bytes, out statusCodeNew); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"GetOutgoingBlob returned IsAuthenticated: {context.IsAuthenticated} and statusCodeNew: {statusCodeNew}"); + error = statusCodeNew >= NegotiateAuthenticationStatusCode.GenericFailure; if (error) { // SSPI Workaround // If a client sends up a blob on the initial request, Negotiate returns SEC_E_INVALID_HANDLE // when it should return SEC_E_INVALID_TOKEN. - if (statusCodeNew.ErrorCode == SecurityStatusPalErrorCode.InvalidHandle && oldContext == null && bytes != null && bytes.Length > 0) - { - statusCodeNew = new SecurityStatusPal(SecurityStatusPalErrorCode.InvalidToken); - } + //if (statusCodeNew.ErrorCode == SecurityStatusPalErrorCode.InvalidHandle && oldContext == null && bytes != null && bytes.Length > 0) + //{ + // statusCodeNew = new SecurityStatusPal(SecurityStatusPalErrorCode.InvalidToken); + //} - httpError = HttpStatusFromSecurityStatus(statusCodeNew.ErrorCode); + httpError = HttpStatusFromSecurityStatus(statusCodeNew); } } @@ -942,7 +948,7 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) if (!error) { - if (context.IsCompleted) + if (context.IsAuthenticated) { SecurityContextTokenHandle? userContext = null; try @@ -953,67 +959,72 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) } else { - httpContext.Request.ServiceName = context.ClientSpecifiedSpn; + httpContext.Request.ServiceName = context.TargetName; - SafeDeleteContext securityContext = context.GetContext(out statusCodeNew)!; - if (statusCodeNew.ErrorCode != SecurityStatusPalErrorCode.OK) + try { - if (NetEventSource.Log.IsEnabled()) - { - NetEventSource.Info(this, - $"HandleAuthentication GetContextToken failed with statusCodeNew: {statusCodeNew}"); - } + IIdentity identity = context.RemoteIdentity; - httpError = HttpStatusFromSecurityStatus(statusCodeNew.ErrorCode); - } - else - { - SSPIWrapper.QuerySecurityContextToken(GlobalSSPI.SSPIAuth, securityContext, out userContext); - - if (NetEventSource.Log.IsEnabled()) - { - NetEventSource.Info(this, - $"HandleAuthentication creating new WindowsIdentity from user context: {userContext.DangerousGetHandle():x8}"); - } - - WindowsPrincipal windowsPrincipal = new WindowsPrincipal( - new WindowsIdentity(userContext.DangerousGetHandle(), context.ProtocolName)); - - principal = windowsPrincipal; - // if appropriate, cache this credential on this connection - if (UnsafeConnectionNtlmAuthentication && context.ProtocolName == NegotiationInfoClass.NTLM) + if (identity is not WindowsIdentity windowsIdentity) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, - $"HandleAuthentication inserting principal: {principal} for connectionId: {connectionId}"); + $"HandleAuthentication RemoteIdentity return non-Windows identity: {identity.GetType().Name}"); } - // We may need to call WaitForDisconnect. - if (disconnectResult == null) - { - RegisterForDisconnectNotification(session, connectionId, ref disconnectResult); - } - if (disconnectResult != null) + httpError = HttpStatusCode.InternalServerError; + } + else + { + WindowsPrincipal windowsPrincipal = new WindowsPrincipal(windowsIdentity); + + principal = windowsPrincipal; + // if appropriate, cache this credential on this connection + if (UnsafeConnectionNtlmAuthentication && context.Package == NegotiationInfoClass.NTLM) { - lock ((DisconnectResults as ICollection).SyncRoot) + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.Info(this, + $"HandleAuthentication inserting principal: {principal} for connectionId: {connectionId}"); + } + + // We may need to call WaitForDisconnect. + if (disconnectResult == null) + { + RegisterForDisconnectNotification(session, connectionId, ref disconnectResult); + } + if (disconnectResult != null) { - if (UnsafeConnectionNtlmAuthentication) + lock ((DisconnectResults as ICollection).SyncRoot) { - disconnectResult.AuthenticatedConnection = windowsPrincipal; + if (UnsafeConnectionNtlmAuthentication) + { + disconnectResult.AuthenticatedConnection = windowsPrincipal; + } } } - } - else - { - // Registration failed - UnsafeConnectionNtlmAuthentication ignored. - if (NetEventSource.Log.IsEnabled()) + else { - NetEventSource.Info(this, $"HandleAuthentication RegisterForDisconnectNotification failed."); + // Registration failed - UnsafeConnectionNtlmAuthentication ignored. + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.Info(this, $"HandleAuthentication RegisterForDisconnectNotification failed."); + } } } } } + catch (Exception e) + { + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.Info(this, + $"HandleAuthentication RemoteIdentity failed with exception: {e.Message}"); + } + + httpError = HttpStatusCode.InternalServerError; + } } } finally @@ -1110,9 +1121,9 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) if (newContext != oldContext) { - NTAuthentication toClose = newContext; + NegotiateAuthentication toClose = newContext; newContext = null; - toClose.CloseContext(); + toClose.Dispose(); } else { @@ -1150,9 +1161,9 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) if (newContext != oldContext) { - NTAuthentication toClose = newContext; + NegotiateAuthentication toClose = newContext; newContext = null; - toClose.CloseContext(); + toClose.Dispose(); } else { @@ -1176,16 +1187,13 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) context = null; } - NTAuthentication? toClose = oldContext; + NegotiateAuthentication? toClose = oldContext; oldContext = newContext; // TODO: Can disconnectResult be null here? Debug.Assert(disconnectResult != null); disconnectResult!.Session = newContext; - if (toClose != null) - { - toClose.CloseContext(); - } + toClose?.Dispose(); } // Send the 401 here. @@ -1216,9 +1224,9 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) if (newContext != oldContext) { - NTAuthentication toClose = newContext; + NegotiateAuthentication toClose = newContext; newContext = null; - toClose.CloseContext(); + toClose.Dispose(); } else { @@ -1240,13 +1248,13 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) disconnectResult.Session = null; } - oldContext.CloseContext(); + oldContext.Dispose(); } // Delete any context created but not stored. if (context != null && oldContext != context && newContext != context) { - context.CloseContext(); + context.Dispose(); } } finally @@ -1325,10 +1333,10 @@ internal void SetAuthenticationHeaders(HttpListenerContext context) return result; } - private bool CheckSpn(NTAuthentication context, bool isSecureConnection, ExtendedProtectionPolicy policy) + private bool CheckSpn(NegotiateAuthentication context, bool isSecureConnection, ExtendedProtectionPolicy policy) { // Kerberos does SPN check already in ASC - if (context.IsKerberos) + if (context.Package == NegotiationInfoClass.Kerberos) { if (NetEventSource.Log.IsEnabled()) { @@ -1356,7 +1364,7 @@ private bool CheckSpn(NTAuthentication context, bool isSecureConnection, Extende return true; } - string? clientSpn = context.ClientSpecifiedSpn; + string? clientSpn = context.TargetName; // An empty SPN is only allowed in the WhenSupported case if (string.IsNullOrEmpty(clientSpn)) @@ -1477,7 +1485,7 @@ private static ContextFlagsPal GetContextFlags(ExtendedProtectionPolicy policy, } // This only works for context-destroying errors. - private static HttpStatusCode HttpStatusFromSecurityStatus(SecurityStatusPalErrorCode statusErrorCode) + private static HttpStatusCode HttpStatusFromSecurityStatus(NegotiateAuthenticationStatusCode statusErrorCode) { if (IsCredentialFailure(statusErrorCode)) { @@ -1491,36 +1499,22 @@ private static HttpStatusCode HttpStatusFromSecurityStatus(SecurityStatusPalErro } // This only works for context-destroying errors. - internal static bool IsCredentialFailure(SecurityStatusPalErrorCode error) + internal static bool IsCredentialFailure(NegotiateAuthenticationStatusCode error) { - return error == SecurityStatusPalErrorCode.LogonDenied || - error == SecurityStatusPalErrorCode.UnknownCredentials || - error == SecurityStatusPalErrorCode.NoImpersonation || - error == SecurityStatusPalErrorCode.NoAuthenticatingAuthority || - error == SecurityStatusPalErrorCode.UntrustedRoot || - error == SecurityStatusPalErrorCode.CertExpired || - error == SecurityStatusPalErrorCode.SmartcardLogonRequired || - error == SecurityStatusPalErrorCode.BadBinding; + return error == NegotiateAuthenticationStatusCode.UnknownCredentials || + error == NegotiateAuthenticationStatusCode.CredentialsExpired || + error == NegotiateAuthenticationStatusCode.BadBinding; } // This only works for context-destroying errors. - internal static bool IsClientFault(SecurityStatusPalErrorCode error) + internal static bool IsClientFault(NegotiateAuthenticationStatusCode error) { - return error == SecurityStatusPalErrorCode.InvalidToken || - error == SecurityStatusPalErrorCode.CannotPack || - error == SecurityStatusPalErrorCode.QopNotSupported || - error == SecurityStatusPalErrorCode.NoCredentials || - error == SecurityStatusPalErrorCode.MessageAltered || - error == SecurityStatusPalErrorCode.OutOfSequence || - error == SecurityStatusPalErrorCode.IncompleteMessage || - error == SecurityStatusPalErrorCode.IncompleteCredentials || - error == SecurityStatusPalErrorCode.WrongPrincipal || - error == SecurityStatusPalErrorCode.TimeSkew || - error == SecurityStatusPalErrorCode.IllegalMessage || - error == SecurityStatusPalErrorCode.CertUnknown || - error == SecurityStatusPalErrorCode.AlgorithmMismatch || - error == SecurityStatusPalErrorCode.SecurityQosFailed || - error == SecurityStatusPalErrorCode.UnsupportedPreauth; + return error == NegotiateAuthenticationStatusCode.InvalidToken || + error == NegotiateAuthenticationStatusCode.QopNotSupported || + error == NegotiateAuthenticationStatusCode.UnknownCredentials || + error == NegotiateAuthenticationStatusCode.MessageAltered || + error == NegotiateAuthenticationStatusCode.OutOfSequence || + error == NegotiateAuthenticationStatusCode.InvalidCredentials; } private static void AddChallenge(ref ArrayList? challenges, string challenge) @@ -1541,7 +1535,7 @@ private static void AddChallenge(ref ArrayList? challenges, string challenge) } private ArrayList? BuildChallenge(AuthenticationSchemes authenticationScheme, ulong connectionId, - out NTAuthentication? newContext, ExtendedProtectionPolicy policy, bool isSecureConnection) + out NegotiateAuthentication? newContext, ExtendedProtectionPolicy policy, bool isSecureConnection) { if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "AuthenticationScheme:" + authenticationScheme.ToString()); ArrayList? challenges = null; @@ -1798,7 +1792,7 @@ private sealed class DisconnectAsyncResult : IAsyncResult private int _ownershipState; // 0 = normal, 1 = in HandleAuthentication(), 2 = disconnected, 3 = cleaned up private WindowsPrincipal? _authenticatedConnection; - private NTAuthentication? _session; + private NegotiateAuthentication? _session; internal NativeOverlapped* NativeOverlapped { @@ -1905,7 +1899,7 @@ private void HandleDisconnect() listener.DisconnectResults.Remove(_connectionId); if (_session != null) { - _session.CloseContext(); + _session.Dispose(); } // Clean up the identity. This is for scenarios where identity was not cleaned up before due to @@ -1937,7 +1931,7 @@ internal WindowsPrincipal? AuthenticatedConnection } } - internal NTAuthentication? Session + internal NegotiateAuthentication? Session { get { From 447020a5441fc330ee38cc0b5c3922530a4f171f Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 7 Jun 2022 19:11:25 +0200 Subject: [PATCH 11/28] Move workaround from HttpListener to low-level SSPI code --- .../src/System/Net/Security/NegotiateStreamPal.Windows.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs index 37967d9bfd521..065c3600b324b 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs @@ -168,6 +168,14 @@ internal static SecurityStatusPal AcceptSecurityContext( ref outSecurityBuffer, ref outContextFlags); + // SSPI Workaround + // If a client sends up a blob on the initial request, Negotiate returns SEC_E_INVALID_HANDLE + // when it should return SEC_E_INVALID_TOKEN. + if (winStatus == Interop.SECURITY_STATUS.InvalidHandle && securityContext == null && !incomingBlob.IsEmpty) + { + winStatus = Interop.SECURITY_STATUS.InvalidToken; + } + resultBlob = outSecurityBuffer.token; securityContext = sslContext; contextFlags = ContextFlagsAdapterPal.GetContextFlagsPalFromInterop(outContextFlags); From bed08fa657cda327ea4a3cb6d3cb303df200941a Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 14 Jun 2022 10:44:03 +0000 Subject: [PATCH 12/28] Fix build --- .../Common/src/System/Net/NTAuthentication.Managed.cs | 2 +- .../Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs index 3b355818ddf7b..6d117cdb24cfd 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs @@ -313,7 +313,7 @@ internal void CloseContext() { decodedIncomingBlob = Convert.FromBase64String(incomingBlob); } - byte[]? decodedOutgoingBlob = GetOutgoingBlob(decodedIncomingBlob, true); + byte[]? decodedOutgoingBlob = GetOutgoingBlob(decodedIncomingBlob, throwOnError, out statusCode); string? outgoingBlob = null; if (decodedOutgoingBlob != null && decodedOutgoingBlob.Length > 0) { diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs index 22c6182de58ed..d6f36c4ed8f99 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs @@ -17,7 +17,6 @@ internal static partial class AuthenticationHelper { private const string UsePortInSpnCtxSwitch = "System.Net.Http.UsePortInSpn"; private const string UsePortInSpnEnvironmentVariable = "DOTNET_SYSTEM_NET_HTTP_USEPORTINSPN"; - private const int NTE_FAIL = unchecked((int)0x80090020); private static volatile int s_usePortInSpn = -1; @@ -182,7 +181,6 @@ private static async Task SendWithNtAuthAsync(HttpRequestMe NegotiateAuthenticationStatusCode statusCode; while (true) { - NegotiateAuthenticationStatusCode statusCode; string? challengeResponse = authContext.GetOutgoingBlob(challengeData, out statusCode); if (statusCode > NegotiateAuthenticationStatusCode.ContinueNeeded || challengeResponse == null) { @@ -198,7 +196,7 @@ private static async Task SendWithNtAuthAsync(HttpRequestMe SetRequestAuthenticationHeaderValue(request, new AuthenticationHeaderValue(challenge.SchemeName, challengeResponse), isProxyAuth); response = await InnerSendAsync(request, async, isProxyAuth, connectionPool, connection, cancellationToken).ConfigureAwait(false); - if (authContext.IsCompleted || !TryGetChallengeDataForScheme(challenge.SchemeName, GetResponseAuthenticationHeaderValues(response, isProxyAuth), out challengeData)) + if (authContext.IsAuthenticated || !TryGetChallengeDataForScheme(challenge.SchemeName, GetResponseAuthenticationHeaderValues(response, isProxyAuth), out challengeData)) { break; } @@ -206,7 +204,7 @@ private static async Task SendWithNtAuthAsync(HttpRequestMe if (!IsAuthenticationChallenge(response, isProxyAuth)) { // Tail response for Negoatiate on successful authentication. Validate it before we proceed. - authContext.GetOutgoingBlob(challengeData, out statusCode); + authContext.GetOutgoingBlob(challengeData, out statusCode); if (statusCode > NegotiateAuthenticationStatusCode.ContinueNeeded) { isNewConnection = false; From 318ca069e7baab2acb220ef51cfb1e5d674bfeb7 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 14 Jun 2022 11:32:58 +0000 Subject: [PATCH 13/28] Clean up --- .../Net/Security/NegotiateAuthentication.cs | 112 +++++++++++++++++- .../NegotiateAuthenticationClientOptions.cs | 3 + .../NegotiateAuthenticationServerOptions.cs | 3 + .../NegotiateAuthenticationStatusCode.cs | 68 ++++++++--- 4 files changed, 169 insertions(+), 17 deletions(-) diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs index 490f050a64747..df3ea3f0d035a 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.Security.Principal; -using System.Runtime.Versioning; namespace System.Net.Security { @@ -13,10 +12,15 @@ namespace System.Net.Security /// public sealed class NegotiateAuthentication : IDisposable { - private NTAuthentication _ntAuthentication; - private bool _isServer; + private readonly NTAuthentication _ntAuthentication; + private readonly bool _isServer; private IIdentity? _remoteIdentity; + /// + /// Initializes a new instance of the + /// for client-side authentication session. + /// + /// The property bag for the authentication options. public NegotiateAuthentication(NegotiateAuthenticationClientOptions clientOptions) { ContextFlagsPal contextFlags = ContextFlagsPal.Connection | clientOptions.RequiredProtectionLevel switch @@ -36,6 +40,11 @@ public NegotiateAuthentication(NegotiateAuthenticationClientOptions clientOption clientOptions.Binding); } + /// + /// Initializes a new instance of the + /// for server-side authentication session. + /// + /// The property bag for the authentication options. public NegotiateAuthentication(NegotiateAuthenticationServerOptions serverOptions) { ContextFlagsPal contextFlags = ContextFlagsPal.Connection | serverOptions.RequiredProtectionLevel switch @@ -55,30 +64,93 @@ public NegotiateAuthentication(NegotiateAuthenticationServerOptions serverOption serverOptions.Binding); } + /// + /// Releases the unmanaged resources used by the + /// and optionally releases the managed resources. + /// public void Dispose() { _ntAuthentication.CloseContext(); } + /// + /// Indicates whether authentication was successfully completed and the session + /// was established. + /// public bool IsAuthenticated => _ntAuthentication.IsCompleted; + /// + /// Indicates the negotiated level of protection. + /// + /// + /// The negotiated level of protection is only available when the session + /// authentication was finished (see ). The + /// protection level can be higher than the initially requested protection + /// level specified by or + /// . + /// public ProtectionLevel ProtectionLevel { get => IsSigned ? (IsEncrypted ? ProtectionLevel.EncryptAndSign : ProtectionLevel.Sign) : ProtectionLevel.None; } + /// + /// Indicates whether data signing was negotiated. + /// public bool IsSigned => _ntAuthentication.IsIntegrityFlag; + /// + /// Indicates whether data encryption was negotiated. + /// public bool IsEncrypted => _ntAuthentication.IsConfidentialityFlag; + /// + /// Indicates whether both server and client have been authenticated. + /// public bool IsMutuallyAuthenticated => _ntAuthentication.IsMutualAuthFlag; + /// + /// Indicates whether the local side of the authentication is representing + /// the server. + /// public bool IsServer => _isServer; + /// + /// Name of the negotiated authentication package. + /// + /// + /// The negotiated authentication package is only available when the session + /// authentication was finished (see ). For + /// unfinished authentication sessions the value is undefined and usually + /// returns the initial authentication package name specified in + /// or + /// . + /// + /// If the Negotiate package was used for authentication the value of this + /// property will be Kerberos, NTLM, or any other specific protocol that was + /// negotiated between both sides of the authentication. + /// public string Package => _ntAuthentication.ProtocolName; + /// + /// Gets target name (service principal name) of the server. + /// + /// + /// For server-side of the authentication the property returns the target name + /// specified by the client after successful authentication (see ). + /// + /// For client-side of the authentication the property returns the target name + /// specified in . + /// public string? TargetName => IsServer ? _ntAuthentication.ClientSpecifiedSpn : _ntAuthentication.Spn; + /// + /// Gets information about the identity of the remote party. + /// + /// + /// An object that describes the identity of the remote endpoint. + /// + /// Authentication failed or has not occurred. public IIdentity RemoteIdentity { get @@ -101,6 +173,23 @@ public IIdentity RemoteIdentity } } + /// + /// Evaluates an authentication token sent by the other party and returns a token in response. + /// + /// Incoming authentication token, or empty value when initiating the authentication exchange. + /// Status code returned by the authentication provider. + /// Outgoing authentication token to be sent to the other party. + /// + /// When initiating the authentication exchange, one of the parties starts + /// with an empty incomingBlob parameter. + /// + /// Successful step of the authentication returns either + /// or status codes. + /// Any other status code indicates an unrecoverable error. + /// + /// When is returned the + /// return value is an authentication token to be transported to the other party. + /// public byte[]? GetOutgoingBlob(ReadOnlySpan incomingBlob, out NegotiateAuthenticationStatusCode statusCode) { byte[]? blob = _ntAuthentication.GetOutgoingBlob(incomingBlob, false, out SecurityStatusPal securityStatus); @@ -151,6 +240,23 @@ public IIdentity RemoteIdentity return blob; } + /// + /// Evaluates an authentication token sent by the other party and returns a token in response. + /// + /// Incoming authentication token, or empty value when initiating the authentication exchange. Encoded as base64. + /// Status code returned by the authentication provider. + /// Outgoing authentication token to be sent to the other party, encoded as base64. + /// + /// When initiating the authentication exchange, one of the parties starts + /// with an empty incomingBlob parameter. + /// + /// Successful step of the authentication returns either + /// or status codes. + /// Any other status code indicates an unrecoverable error. + /// + /// When is returned the + /// return value is an authentication token to be transported to the other party. + /// public string? GetOutgoingBlob(string? incomingBlob, out NegotiateAuthenticationStatusCode statusCode) { byte[]? decodedIncomingBlob = null; diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationClientOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationClientOptions.cs index 8dc0007d54a5f..a18f15288fb1e 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationClientOptions.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationClientOptions.cs @@ -5,6 +5,9 @@ namespace System.Net.Security { + /// + /// Represents a propery bag for client-side of an authentication exchange. + /// public class NegotiateAuthenticationClientOptions { /// diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationServerOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationServerOptions.cs index 42182773100cd..c96df1c9e60ab 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationServerOptions.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationServerOptions.cs @@ -5,6 +5,9 @@ namespace System.Net.Security { + /// + /// Represents a propery bag for server-side of an authentication exchange. + /// public class NegotiateAuthenticationServerOptions { /// diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs index 24b02014d26d2..62f8227932b81 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs @@ -3,21 +3,61 @@ namespace System.Net.Security { + /// + /// Represents a status code for single step of an authentication exchange. + /// public enum NegotiateAuthenticationStatusCode { - Completed = 0, // GSS_S_COMPLETE - ContinueNeeded, // GSS_S_CONTINUE_NEEDED - - GenericFailure, // GSS_S_FAILURE/GSS_S_NO_CONTEXT - BadBinding, // GSS_S_BAD_BINDINGS - Unsupported, // GSS_S_BAD_MECH (Unsupported mechanism) - MessageAltered, // GSS_S_BAD_SIG/GSS_S_BAD_MIC - ContextExpired, // GSS_S_CONTEXT_EXPIRED - CredentialsExpired, // GSS_S_CREDENTIALS_EXPIRED - InvalidCredentials, // GSS_S_DEFECTIVE_CREDENTIAL - InvalidToken, // GSS_S_DEFECTIVE_TOKEN - UnknownCredentials, // GSS_S_NO_CRED - QopNotSupported, // GSS_S_BAD_QOP - OutOfSequence, // GSS_S_DUPLICATE_TOKEN/GSS_S_OLD_TOKEN/GSS_S_UNSEQ_TOKEN/GSS_S_GAP_TOKEN + GSS_E_FAILURE + /// Operation completed successfully. + /// Maps to GSS_S_COMPLETE status in GSSAPI. + Completed = 0, + + /// Operation completed successfully but more tokens are to be exchanged with the other party. + /// Maps to GSS_S_CONTINUE_NEEDED status in GSSAPI. + ContinueNeeded, + + /// Operation resulted in failure but not specific error code was given. + /// Maps to GSS_S_FAILURE status in GSSAPI. + GenericFailure, + + /// Channel binding mismatch between client and server. + /// Maps to GSS_S_BAD_BINDINGS status in GSSAPI. + BadBinding, + + /// Unsupported authentication package was requested. + /// Maps to GSS_S_BAD_MECH status in GSSAPI. + Unsupported, + + /// Message was altered and failed an integrity check validation. + /// Maps to GSS_S_BAD_SIG or GSS_S_BAD_MIC status in GSSAPI. + MessageAltered, + + /// Referenced authentication context has expired. + /// Maps to GSS_S_CONTEXT_EXPIRED status in GSSAPI. + ContextExpired, + + /// Authentication credentials have expired. + /// Maps to GSS_S_CREDENTIALS_EXPIRED status in GSSAPI. + CredentialsExpired, + + /// Consistency checks performed on the credential failed. + /// Maps to GSS_S_DEFECTIVE_CREDENTIAL status in GSSAPI. + InvalidCredentials, + + /// Checks performed on the authentication token failed. + /// Maps to GSS_S_DEFECTIVE_TOKEN status in GSSAPI. + InvalidToken, + + /// The supplied credentials were not valid for context acceptance, or the credential handle did not reference any credentials. + /// Maps to GSS_S_NO_CRED status in GSSAPI. + UnknownCredentials, + + /// Requested protection level is not supported. + /// Maps to GSS_S_BAD_QOP status in GSSAPI. + QopNotSupported, + + /// Authentication token was identfied as duplicate, old, or out of expected sequence. + /// Maps to GSS_S_DUPLICATE_TOKEN, GSS_S_OLD_TOKEN, GSS_S_UNSEQ_TOKEN, and GSS_S_GAP_TOKEN status bits in GSSAPI when failure was indicated. + OutOfSequence } } From 5474811b023ccb7433a4e03adc42db5f3215bcc5 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 14 Jun 2022 11:33:13 +0000 Subject: [PATCH 14/28] Revert "WIP: HttpListener test" This reverts commit 18d7d93f04c93e048efcaca0f3c55c3f1f73516a. --- .../Net/Windows/HttpListener.Windows.cs | 200 +++++++++--------- 1 file changed, 103 insertions(+), 97 deletions(-) diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpListener.Windows.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpListener.Windows.cs index 3d687a826234a..c8e813205f38e 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpListener.Windows.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpListener.Windows.cs @@ -736,9 +736,9 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) // Figure out what schemes we're allowing, what context we have. stoleBlob = true; HttpListenerContext? httpContext = null; - NegotiateAuthentication? oldContext = null; - NegotiateAuthentication? newContext = null; - NegotiateAuthentication? context = null; + NTAuthentication? oldContext = null; + NTAuthentication? newContext = null; + NTAuthentication? context = null; AuthenticationSchemes headerScheme = AuthenticationSchemes.None; AuthenticationSchemes authenticationScheme = AuthenticationSchemes; ExtendedProtectionPolicy extendedProtectionPolicy = _extendedProtectionPolicy; @@ -884,7 +884,7 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) string inBlob = index < authorizationHeader.Length ? authorizationHeader.Substring(index) : ""; IPrincipal? principal = null; - NegotiateAuthenticationStatusCode statusCodeNew; + SecurityStatusPal statusCodeNew; ChannelBinding? binding; if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Performing Authentication headerScheme: {headerScheme}"); switch (headerScheme) @@ -901,14 +901,8 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) else { binding = GetChannelBinding(session, connectionId, isSecureConnection, extendedProtectionPolicy); - // FIXME - //ContextFlagsPal contextFlags = GetContextFlags(extendedProtectionPolicy, isSecureConnection); - NegotiateAuthenticationServerOptions serverOptions = new NegotiateAuthenticationServerOptions - { - Package = package, - Binding = binding, - }; - context = new NegotiateAuthentication(serverOptions); + ContextFlagsPal contextFlags = GetContextFlags(extendedProtectionPolicy, isSecureConnection); + context = new NTAuthentication(true, package, CredentialCache.DefaultNetworkCredentials, null, contextFlags, binding); } try @@ -923,20 +917,20 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) } if (!error) { - decodedOutgoingBlob = context.GetOutgoingBlob(bytes, out statusCodeNew); - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"GetOutgoingBlob returned IsAuthenticated: {context.IsAuthenticated} and statusCodeNew: {statusCodeNew}"); - error = statusCodeNew >= NegotiateAuthenticationStatusCode.GenericFailure; + decodedOutgoingBlob = context.GetOutgoingBlob(bytes, false, out statusCodeNew); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"GetOutgoingBlob returned IsCompleted: {context.IsCompleted} and statusCodeNew: {statusCodeNew}"); + error = !context.IsValidContext; if (error) { // SSPI Workaround // If a client sends up a blob on the initial request, Negotiate returns SEC_E_INVALID_HANDLE // when it should return SEC_E_INVALID_TOKEN. - //if (statusCodeNew.ErrorCode == SecurityStatusPalErrorCode.InvalidHandle && oldContext == null && bytes != null && bytes.Length > 0) - //{ - // statusCodeNew = new SecurityStatusPal(SecurityStatusPalErrorCode.InvalidToken); - //} + if (statusCodeNew.ErrorCode == SecurityStatusPalErrorCode.InvalidHandle && oldContext == null && bytes != null && bytes.Length > 0) + { + statusCodeNew = new SecurityStatusPal(SecurityStatusPalErrorCode.InvalidToken); + } - httpError = HttpStatusFromSecurityStatus(statusCodeNew); + httpError = HttpStatusFromSecurityStatus(statusCodeNew.ErrorCode); } } @@ -948,7 +942,7 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) if (!error) { - if (context.IsAuthenticated) + if (context.IsCompleted) { SecurityContextTokenHandle? userContext = null; try @@ -959,72 +953,67 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) } else { - httpContext.Request.ServiceName = context.TargetName; + httpContext.Request.ServiceName = context.ClientSpecifiedSpn; - try + SafeDeleteContext securityContext = context.GetContext(out statusCodeNew)!; + if (statusCodeNew.ErrorCode != SecurityStatusPalErrorCode.OK) { - IIdentity identity = context.RemoteIdentity; + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.Info(this, + $"HandleAuthentication GetContextToken failed with statusCodeNew: {statusCodeNew}"); + } - if (identity is not WindowsIdentity windowsIdentity) + httpError = HttpStatusFromSecurityStatus(statusCodeNew.ErrorCode); + } + else + { + SSPIWrapper.QuerySecurityContextToken(GlobalSSPI.SSPIAuth, securityContext, out userContext); + + if (NetEventSource.Log.IsEnabled()) + { + NetEventSource.Info(this, + $"HandleAuthentication creating new WindowsIdentity from user context: {userContext.DangerousGetHandle():x8}"); + } + + WindowsPrincipal windowsPrincipal = new WindowsPrincipal( + new WindowsIdentity(userContext.DangerousGetHandle(), context.ProtocolName)); + + principal = windowsPrincipal; + // if appropriate, cache this credential on this connection + if (UnsafeConnectionNtlmAuthentication && context.ProtocolName == NegotiationInfoClass.NTLM) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, - $"HandleAuthentication RemoteIdentity return non-Windows identity: {identity.GetType().Name}"); + $"HandleAuthentication inserting principal: {principal} for connectionId: {connectionId}"); } - httpError = HttpStatusCode.InternalServerError; - } - else - { - WindowsPrincipal windowsPrincipal = new WindowsPrincipal(windowsIdentity); - - principal = windowsPrincipal; - // if appropriate, cache this credential on this connection - if (UnsafeConnectionNtlmAuthentication && context.Package == NegotiationInfoClass.NTLM) + // We may need to call WaitForDisconnect. + if (disconnectResult == null) { - if (NetEventSource.Log.IsEnabled()) - { - NetEventSource.Info(this, - $"HandleAuthentication inserting principal: {principal} for connectionId: {connectionId}"); - } - - // We may need to call WaitForDisconnect. - if (disconnectResult == null) - { - RegisterForDisconnectNotification(session, connectionId, ref disconnectResult); - } - if (disconnectResult != null) + RegisterForDisconnectNotification(session, connectionId, ref disconnectResult); + } + if (disconnectResult != null) + { + lock ((DisconnectResults as ICollection).SyncRoot) { - lock ((DisconnectResults as ICollection).SyncRoot) + if (UnsafeConnectionNtlmAuthentication) { - if (UnsafeConnectionNtlmAuthentication) - { - disconnectResult.AuthenticatedConnection = windowsPrincipal; - } + disconnectResult.AuthenticatedConnection = windowsPrincipal; } } - else + } + else + { + // Registration failed - UnsafeConnectionNtlmAuthentication ignored. + if (NetEventSource.Log.IsEnabled()) { - // Registration failed - UnsafeConnectionNtlmAuthentication ignored. - if (NetEventSource.Log.IsEnabled()) - { - NetEventSource.Info(this, $"HandleAuthentication RegisterForDisconnectNotification failed."); - } + NetEventSource.Info(this, $"HandleAuthentication RegisterForDisconnectNotification failed."); } } } } - catch (Exception e) - { - if (NetEventSource.Log.IsEnabled()) - { - NetEventSource.Info(this, - $"HandleAuthentication RemoteIdentity failed with exception: {e.Message}"); - } - - httpError = HttpStatusCode.InternalServerError; - } } } finally @@ -1121,9 +1110,9 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) if (newContext != oldContext) { - NegotiateAuthentication toClose = newContext; + NTAuthentication toClose = newContext; newContext = null; - toClose.Dispose(); + toClose.CloseContext(); } else { @@ -1161,9 +1150,9 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) if (newContext != oldContext) { - NegotiateAuthentication toClose = newContext; + NTAuthentication toClose = newContext; newContext = null; - toClose.Dispose(); + toClose.CloseContext(); } else { @@ -1187,13 +1176,16 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) context = null; } - NegotiateAuthentication? toClose = oldContext; + NTAuthentication? toClose = oldContext; oldContext = newContext; // TODO: Can disconnectResult be null here? Debug.Assert(disconnectResult != null); disconnectResult!.Session = newContext; - toClose?.Dispose(); + if (toClose != null) + { + toClose.CloseContext(); + } } // Send the 401 here. @@ -1224,9 +1216,9 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) if (newContext != oldContext) { - NegotiateAuthentication toClose = newContext; + NTAuthentication toClose = newContext; newContext = null; - toClose.Dispose(); + toClose.CloseContext(); } else { @@ -1248,13 +1240,13 @@ public HttpListenerContext EndGetContext(IAsyncResult asyncResult) disconnectResult.Session = null; } - oldContext.Dispose(); + oldContext.CloseContext(); } // Delete any context created but not stored. if (context != null && oldContext != context && newContext != context) { - context.Dispose(); + context.CloseContext(); } } finally @@ -1333,10 +1325,10 @@ internal void SetAuthenticationHeaders(HttpListenerContext context) return result; } - private bool CheckSpn(NegotiateAuthentication context, bool isSecureConnection, ExtendedProtectionPolicy policy) + private bool CheckSpn(NTAuthentication context, bool isSecureConnection, ExtendedProtectionPolicy policy) { // Kerberos does SPN check already in ASC - if (context.Package == NegotiationInfoClass.Kerberos) + if (context.IsKerberos) { if (NetEventSource.Log.IsEnabled()) { @@ -1364,7 +1356,7 @@ private bool CheckSpn(NegotiateAuthentication context, bool isSecureConnection, return true; } - string? clientSpn = context.TargetName; + string? clientSpn = context.ClientSpecifiedSpn; // An empty SPN is only allowed in the WhenSupported case if (string.IsNullOrEmpty(clientSpn)) @@ -1485,7 +1477,7 @@ private static ContextFlagsPal GetContextFlags(ExtendedProtectionPolicy policy, } // This only works for context-destroying errors. - private static HttpStatusCode HttpStatusFromSecurityStatus(NegotiateAuthenticationStatusCode statusErrorCode) + private static HttpStatusCode HttpStatusFromSecurityStatus(SecurityStatusPalErrorCode statusErrorCode) { if (IsCredentialFailure(statusErrorCode)) { @@ -1499,22 +1491,36 @@ private static HttpStatusCode HttpStatusFromSecurityStatus(NegotiateAuthenticati } // This only works for context-destroying errors. - internal static bool IsCredentialFailure(NegotiateAuthenticationStatusCode error) + internal static bool IsCredentialFailure(SecurityStatusPalErrorCode error) { - return error == NegotiateAuthenticationStatusCode.UnknownCredentials || - error == NegotiateAuthenticationStatusCode.CredentialsExpired || - error == NegotiateAuthenticationStatusCode.BadBinding; + return error == SecurityStatusPalErrorCode.LogonDenied || + error == SecurityStatusPalErrorCode.UnknownCredentials || + error == SecurityStatusPalErrorCode.NoImpersonation || + error == SecurityStatusPalErrorCode.NoAuthenticatingAuthority || + error == SecurityStatusPalErrorCode.UntrustedRoot || + error == SecurityStatusPalErrorCode.CertExpired || + error == SecurityStatusPalErrorCode.SmartcardLogonRequired || + error == SecurityStatusPalErrorCode.BadBinding; } // This only works for context-destroying errors. - internal static bool IsClientFault(NegotiateAuthenticationStatusCode error) + internal static bool IsClientFault(SecurityStatusPalErrorCode error) { - return error == NegotiateAuthenticationStatusCode.InvalidToken || - error == NegotiateAuthenticationStatusCode.QopNotSupported || - error == NegotiateAuthenticationStatusCode.UnknownCredentials || - error == NegotiateAuthenticationStatusCode.MessageAltered || - error == NegotiateAuthenticationStatusCode.OutOfSequence || - error == NegotiateAuthenticationStatusCode.InvalidCredentials; + return error == SecurityStatusPalErrorCode.InvalidToken || + error == SecurityStatusPalErrorCode.CannotPack || + error == SecurityStatusPalErrorCode.QopNotSupported || + error == SecurityStatusPalErrorCode.NoCredentials || + error == SecurityStatusPalErrorCode.MessageAltered || + error == SecurityStatusPalErrorCode.OutOfSequence || + error == SecurityStatusPalErrorCode.IncompleteMessage || + error == SecurityStatusPalErrorCode.IncompleteCredentials || + error == SecurityStatusPalErrorCode.WrongPrincipal || + error == SecurityStatusPalErrorCode.TimeSkew || + error == SecurityStatusPalErrorCode.IllegalMessage || + error == SecurityStatusPalErrorCode.CertUnknown || + error == SecurityStatusPalErrorCode.AlgorithmMismatch || + error == SecurityStatusPalErrorCode.SecurityQosFailed || + error == SecurityStatusPalErrorCode.UnsupportedPreauth; } private static void AddChallenge(ref ArrayList? challenges, string challenge) @@ -1535,7 +1541,7 @@ private static void AddChallenge(ref ArrayList? challenges, string challenge) } private ArrayList? BuildChallenge(AuthenticationSchemes authenticationScheme, ulong connectionId, - out NegotiateAuthentication? newContext, ExtendedProtectionPolicy policy, bool isSecureConnection) + out NTAuthentication? newContext, ExtendedProtectionPolicy policy, bool isSecureConnection) { if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "AuthenticationScheme:" + authenticationScheme.ToString()); ArrayList? challenges = null; @@ -1792,7 +1798,7 @@ private sealed class DisconnectAsyncResult : IAsyncResult private int _ownershipState; // 0 = normal, 1 = in HandleAuthentication(), 2 = disconnected, 3 = cleaned up private WindowsPrincipal? _authenticatedConnection; - private NegotiateAuthentication? _session; + private NTAuthentication? _session; internal NativeOverlapped* NativeOverlapped { @@ -1899,7 +1905,7 @@ private void HandleDisconnect() listener.DisconnectResults.Remove(_connectionId); if (_session != null) { - _session.Dispose(); + _session.CloseContext(); } // Clean up the identity. This is for scenarios where identity was not cleaned up before due to @@ -1931,7 +1937,7 @@ internal WindowsPrincipal? AuthenticatedConnection } } - internal NegotiateAuthentication? Session + internal NTAuthentication? Session { get { From 4cdb8af803a0b2eeef0faf0f79f4615834ed57f8 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 14 Jun 2022 12:37:52 +0000 Subject: [PATCH 15/28] Convert System.Net.Http.FunctionalTests to use NegotiateAuthentication instead of NTAuthentication --- .../FunctionalTests/NtAuthTests.Windows.cs | 21 ++-- .../System.Net.Http.Functional.Tests.csproj | 102 ------------------ 2 files changed, 9 insertions(+), 114 deletions(-) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/NtAuthTests.Windows.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/NtAuthTests.Windows.cs index 8bb4df5747efc..6a21f050015a3 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/NtAuthTests.Windows.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/NtAuthTests.Windows.cs @@ -31,7 +31,7 @@ internal static Task HandleNegotiateAuthenticationRequest(LoopbackServer.Connect internal static async Task HandleAuthenticationRequest(LoopbackServer.Connection connection, bool useNtlm, bool useNegotiate, bool closeConnection) { HttpRequestData request = await connection.ReadRequestDataAsync(); - NTAuthentication authContext = null; + NegotiateAuthentication authContext = null; string authHeader = null; foreach (HttpHeaderData header in request.Headers) @@ -64,7 +64,7 @@ internal static async Task HandleAuthenticationRequest(LoopbackServer.Connection request = await connection.ReadRequestDataAsync(); } - SecurityStatusPal statusCode; + NegotiateAuthenticationStatusCode statusCode; do { foreach (HttpHeaderData header in request.Headers) @@ -81,11 +81,11 @@ internal static async Task HandleAuthenticationRequest(LoopbackServer.Connection // Should be type and base64 encoded blob Assert.Equal(2, tokens.Length); - authContext ??= new NTAuthentication(isServer: true, tokens[0], CredentialCache.DefaultNetworkCredentials, null, ContextFlagsPal.Connection, null); + authContext ??= new NegotiateAuthentication(new NegotiateAuthenticationServerOptions { Package = tokens[0] }); - byte[]? outBlob = authContext.GetOutgoingBlob(Convert.FromBase64String(tokens[1]), throwOnError: false, out statusCode); + byte[]? outBlob = authContext.GetOutgoingBlob(Convert.FromBase64String(tokens[1]), out statusCode); - if (outBlob != null && statusCode.ErrorCode == SecurityStatusPalErrorCode.ContinueNeeded) + if (outBlob != null && statusCode == NegotiateAuthenticationStatusCode.ContinueNeeded) { authHeader = $"WWW-Authenticate: {tokens[0]} {Convert.ToBase64String(outBlob)}\r\n"; await connection.SendResponseAsync(HttpStatusCode.Unauthorized, authHeader); @@ -94,15 +94,12 @@ internal static async Task HandleAuthenticationRequest(LoopbackServer.Connection request = await connection.ReadRequestDataAsync(); } } - while (statusCode.ErrorCode == SecurityStatusPalErrorCode.ContinueNeeded); + while (statusCode == NegotiateAuthenticationStatusCode.ContinueNeeded); - if (statusCode.ErrorCode == SecurityStatusPalErrorCode.OK) + if (statusCode == NegotiateAuthenticationStatusCode.Completed) { // If authentication succeeded ask Windows about the identity and send it back as custom header. - SecurityContextTokenHandle? userContext = null; - using SafeDeleteContext securityContext = authContext.GetContext(out SecurityStatusPal statusCodeNew)!; - SSPIWrapper.QuerySecurityContextToken(GlobalSSPI.SSPIAuth, securityContext, out userContext); - using WindowsIdentity identity = new WindowsIdentity(userContext.DangerousGetHandle(), authContext.ProtocolName); + IIdentity identity = authContext.RemoteIdentity; authHeader = $"{UserHeaderName}: {identity.Name}\r\n"; if (closeConnection) @@ -111,7 +108,7 @@ internal static async Task HandleAuthenticationRequest(LoopbackServer.Connection } await connection.SendResponseAsync(HttpStatusCode.OK, authHeader, "foo"); - userContext.Dispose(); + authContext.Dispose(); } else { diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj index e5da76698df37..8d686b8343d4e 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj @@ -225,108 +225,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 34e47c44d103ca9d292379b1c74ad0aa12a64c94 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 14 Jun 2022 12:40:07 +0000 Subject: [PATCH 16/28] Dispose the identity along NegotiateAuthentication --- .../src/System/Net/Security/NegotiateAuthentication.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs index df3ea3f0d035a..1c14456ce85c3 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs @@ -71,6 +71,10 @@ public NegotiateAuthentication(NegotiateAuthenticationServerOptions serverOption public void Dispose() { _ntAuthentication.CloseContext(); + if (_remoteIdentity is IDisposable disposableRemoteIdentity) + { + disposableRemoteIdentity.Dispose(); + } } /// From 73ac446d48527f973f3c1872ff11ca040fc941b7 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 14 Jun 2022 13:49:11 +0000 Subject: [PATCH 17/28] Modify unit tests to use the new API --- .../tests/UnitTests/NTAuthenticationTests.cs | 133 ------------- .../UnitTests/NegotiateAuthenticationTests.cs | 185 ++++++++++++++++++ .../System.Net.Security.Unit.Tests.csproj | 1 + 3 files changed, 186 insertions(+), 133 deletions(-) create mode 100644 src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs diff --git a/src/libraries/System.Net.Security/tests/UnitTests/NTAuthenticationTests.cs b/src/libraries/System.Net.Security/tests/UnitTests/NTAuthenticationTests.cs index 43cced820c7cf..0de9ec03fc18c 100644 --- a/src/libraries/System.Net.Security/tests/UnitTests/NTAuthenticationTests.cs +++ b/src/libraries/System.Net.Security/tests/UnitTests/NTAuthenticationTests.cs @@ -17,96 +17,8 @@ public class NTAuthenticationTests private static bool IsNtlmInstalled => Capability.IsNtlmInstalled(); private static NetworkCredential s_testCredentialRight = new NetworkCredential("rightusername", "rightpassword"); - private static NetworkCredential s_testCredentialWrong = new NetworkCredential("rightusername", "wrongpassword"); private static readonly byte[] s_Hello = "Hello"u8.ToArray(); - [Fact] - public void NtlmProtocolExampleTest() - { - // Mirrors the NTLMv2 example in the NTLM specification: - NetworkCredential credential = new NetworkCredential("User", "Password", "Domain"); - FakeNtlmServer fakeNtlmServer = new FakeNtlmServer(credential); - fakeNtlmServer.SendTimestamp = false; - fakeNtlmServer.TargetIsServer = true; - fakeNtlmServer.PreferUnicode = false; - - // NEGOTIATE_MESSAGE - // Flags: - // NTLMSSP_NEGOTIATE_KEY_EXCH - // NTLMSSP_NEGOTIATE_56 - // NTLMSSP_NEGOTIATE_128 - // NTLMSSP_NEGOTIATE_VERSION - // NTLMSSP_NEGOTIATE_TARGET_INFO - // NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY - // NTLMSSP_TARGET_TYPE_SERVER - // NTLMSSP_NEGOTIATE_ALWAYS_SIGN - // NTLMSSP_NEGOTIATE_NTLM - // NTLMSSP_NEGOTIATE_SEAL - // NTLMSSP_NEGOTIATE_SIGN - // NTLMSSP_NEGOTIATE_OEM - // NTLMSSP_NEGOTIATE_UNICODE - // Domain: (empty) (should be "Domain" but the fake server doesn't check) - // Workstation: (empty) (should be "COMPUTER" but the fake server doesn't check) - // Version: 6.1.7600 / 15 - byte[] negotiateBlob = Convert.FromHexString("4e544c4d535350000100000033828ae2000000000000000000000000000000000601b01d0000000f"); - byte[]? challengeBlob = fakeNtlmServer.GetOutgoingBlob(negotiateBlob); - - // CHALLENGE_MESSAGE from 4.2.4.3 Messages - byte[] expectedChallengeBlob = Convert.FromHexString( - "4e544c4d53535000020000000c000c003800000033828ae20123456789abcdef" + - "00000000000000002400240044000000060070170000000f5300650072007600" + - "6500720002000c0044006f006d00610069006e0001000c005300650072007600" + - "6500720000000000"); - Assert.Equal(expectedChallengeBlob, challengeBlob); - - // AUTHENTICATE_MESSAGE from 4.2.4.3 Messages - byte[] authenticateBlob = Convert.FromHexString( - "4e544c4d5353500003000000180018006c00000054005400840000000c000c00" + - "480000000800080054000000100010005c00000010001000d8000000358288e2" + - "0501280a0000000f44006f006d00610069006e00550073006500720043004f00" + - "4d005000550054004500520086c35097ac9cec102554764a57cccc19aaaaaaaa" + - "aaaaaaaa68cd0ab851e51c96aabc927bebef6a1c010100000000000000000000" + - "00000000aaaaaaaaaaaaaaaa0000000002000c0044006f006d00610069006e00" + - "01000c005300650072007600650072000000000000000000c5dad2544fc97990" + - "94ce1ce90bc9d03e"); - byte[]? empty = fakeNtlmServer.GetOutgoingBlob(authenticateBlob); - Assert.Null(empty); - Assert.True(fakeNtlmServer.IsAuthenticated); - Assert.False(fakeNtlmServer.IsMICPresent); - } - - [ConditionalFact(nameof(IsNtlmInstalled))] - public void NtlmCorrectExchangeTest() - { - FakeNtlmServer fakeNtlmServer = new FakeNtlmServer(s_testCredentialRight); - NTAuthentication ntAuth = new NTAuthentication( - isServer: false, "NTLM", s_testCredentialRight, "HTTP/foo", - ContextFlagsPal.Connection | ContextFlagsPal.InitIntegrity, null); - - DoNtlmExchange(fakeNtlmServer, ntAuth); - - Assert.True(fakeNtlmServer.IsAuthenticated); - // NTLMSSP on Linux doesn't send the MIC and sends incorrect SPN (drops the service prefix) - if (!OperatingSystem.IsLinux()) - { - Assert.True(fakeNtlmServer.IsMICPresent); - Assert.Equal("HTTP/foo", fakeNtlmServer.ClientSpecifiedSpn); - } - } - - [ConditionalFact(nameof(IsNtlmInstalled))] - public void NtlmIncorrectExchangeTest() - { - FakeNtlmServer fakeNtlmServer = new FakeNtlmServer(s_testCredentialRight); - NTAuthentication ntAuth = new NTAuthentication( - isServer: false, "NTLM", s_testCredentialWrong, "HTTP/foo", - ContextFlagsPal.Connection | ContextFlagsPal.InitIntegrity, null); - - DoNtlmExchange(fakeNtlmServer, ntAuth); - - Assert.False(fakeNtlmServer.IsAuthenticated); - } - [ConditionalFact(nameof(IsNtlmInstalled))] [ActiveIssue("https://github.com/dotnet/runtime/issues/65678", TestPlatforms.OSX | TestPlatforms.iOS | TestPlatforms.MacCatalyst)] public void NtlmSignatureTest() @@ -153,50 +65,5 @@ private void DoNtlmExchange(FakeNtlmServer fakeNtlmServer, NTAuthentication ntAu byte[]? empty = fakeNtlmServer.GetOutgoingBlob(authenticateBlob); Assert.Null(empty); } - - [ConditionalTheory(nameof(IsNtlmInstalled))] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, false)] - public void NegotiateCorrectExchangeTest(bool requestMIC, bool requestConfidentiality) - { - // Older versions of gss-ntlmssp on Linux generate MIC at incorrect offset unless ForceNegotiateVersion is specified - FakeNtlmServer fakeNtlmServer = new FakeNtlmServer(s_testCredentialRight) { ForceNegotiateVersion = true }; - FakeNegotiateServer fakeNegotiateServer = new FakeNegotiateServer(fakeNtlmServer) { RequestMIC = requestMIC }; - NTAuthentication ntAuth = new NTAuthentication( - isServer: false, "Negotiate", s_testCredentialRight, "HTTP/foo", - ContextFlagsPal.Connection | ContextFlagsPal.InitIntegrity | - (requestConfidentiality ? ContextFlagsPal.Confidentiality : 0), null); - - byte[]? clientBlob = null; - byte[]? serverBlob = null; - do - { - clientBlob = ntAuth.GetOutgoingBlob(serverBlob, throwOnError: false, out SecurityStatusPal status); - if (clientBlob != null) - { - Assert.False(fakeNegotiateServer.IsAuthenticated); - // Send the client blob to the fake server - serverBlob = fakeNegotiateServer.GetOutgoingBlob(clientBlob); - } - - if (status.ErrorCode == SecurityStatusPalErrorCode.OK) - { - Assert.True(ntAuth.IsCompleted); - Assert.True(fakeNegotiateServer.IsAuthenticated); - Assert.True(fakeNtlmServer.IsAuthenticated); - } - else if (status.ErrorCode == SecurityStatusPalErrorCode.ContinueNeeded) - { - Assert.NotNull(clientBlob); - Assert.NotNull(serverBlob); - } - else - { - Assert.Fail(status.ErrorCode.ToString()); - } - } - while (!ntAuth.IsCompleted); - } } } diff --git a/src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs b/src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs new file mode 100644 index 0000000000000..796e6148f3c05 --- /dev/null +++ b/src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs @@ -0,0 +1,185 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers.Binary; +using System.IO; +using System.Net.Security; +using System.Text; +using System.Threading.Tasks; +using System.Net.Test.Common; +using Xunit; + +namespace System.Net.Security.Tests +{ + public class NegotiateAuthenticationTests + { + private static bool IsNtlmAvailable => Capability.IsNtlmInstalled() || OperatingSystem.IsAndroid() || OperatingSystem.IsTvOS(); + + private static NetworkCredential s_testCredentialRight = new NetworkCredential("rightusername", "rightpassword"); + private static NetworkCredential s_testCredentialWrong = new NetworkCredential("rightusername", "wrongpassword"); + private static readonly byte[] s_Hello = "Hello"u8.ToArray(); + + [Fact] + public void NtlmProtocolExampleTest() + { + // Mirrors the NTLMv2 example in the NTLM specification: + NetworkCredential credential = new NetworkCredential("User", "Password", "Domain"); + FakeNtlmServer fakeNtlmServer = new FakeNtlmServer(credential); + fakeNtlmServer.SendTimestamp = false; + fakeNtlmServer.TargetIsServer = true; + fakeNtlmServer.PreferUnicode = false; + + // NEGOTIATE_MESSAGE + // Flags: + // NTLMSSP_NEGOTIATE_KEY_EXCH + // NTLMSSP_NEGOTIATE_56 + // NTLMSSP_NEGOTIATE_128 + // NTLMSSP_NEGOTIATE_VERSION + // NTLMSSP_NEGOTIATE_TARGET_INFO + // NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY + // NTLMSSP_TARGET_TYPE_SERVER + // NTLMSSP_NEGOTIATE_ALWAYS_SIGN + // NTLMSSP_NEGOTIATE_NTLM + // NTLMSSP_NEGOTIATE_SEAL + // NTLMSSP_NEGOTIATE_SIGN + // NTLMSSP_NEGOTIATE_OEM + // NTLMSSP_NEGOTIATE_UNICODE + // Domain: (empty) (should be "Domain" but the fake server doesn't check) + // Workstation: (empty) (should be "COMPUTER" but the fake server doesn't check) + // Version: 6.1.7600 / 15 + byte[] negotiateBlob = Convert.FromHexString("4e544c4d535350000100000033828ae2000000000000000000000000000000000601b01d0000000f"); + byte[]? challengeBlob = fakeNtlmServer.GetOutgoingBlob(negotiateBlob); + + // CHALLENGE_MESSAGE from 4.2.4.3 Messages + byte[] expectedChallengeBlob = Convert.FromHexString( + "4e544c4d53535000020000000c000c003800000033828ae20123456789abcdef" + + "00000000000000002400240044000000060070170000000f5300650072007600" + + "6500720002000c0044006f006d00610069006e0001000c005300650072007600" + + "6500720000000000"); + Assert.Equal(expectedChallengeBlob, challengeBlob); + + // AUTHENTICATE_MESSAGE from 4.2.4.3 Messages + byte[] authenticateBlob = Convert.FromHexString( + "4e544c4d5353500003000000180018006c00000054005400840000000c000c00" + + "480000000800080054000000100010005c00000010001000d8000000358288e2" + + "0501280a0000000f44006f006d00610069006e00550073006500720043004f00" + + "4d005000550054004500520086c35097ac9cec102554764a57cccc19aaaaaaaa" + + "aaaaaaaa68cd0ab851e51c96aabc927bebef6a1c010100000000000000000000" + + "00000000aaaaaaaaaaaaaaaa0000000002000c0044006f006d00610069006e00" + + "01000c005300650072007600650072000000000000000000c5dad2544fc97990" + + "94ce1ce90bc9d03e"); + byte[]? empty = fakeNtlmServer.GetOutgoingBlob(authenticateBlob); + Assert.Null(empty); + Assert.True(fakeNtlmServer.IsAuthenticated); + Assert.False(fakeNtlmServer.IsMICPresent); + } + + [ConditionalFact(nameof(IsNtlmAvailable))] + public void NtlmCorrectExchangeTest() + { + FakeNtlmServer fakeNtlmServer = new FakeNtlmServer(s_testCredentialRight); + NegotiateAuthentication ntAuth = new NegotiateAuthentication( + new NegotiateAuthenticationClientOptions + { + Package = "NTLM", + Credential = s_testCredentialRight, + TargetName = "HTTP/foo", + RequiredProtectionLevel = ProtectionLevel.Sign + }); + + DoNtlmExchange(fakeNtlmServer, ntAuth); + + Assert.True(fakeNtlmServer.IsAuthenticated); + // NTLMSSP on Linux doesn't send the MIC and sends incorrect SPN (drops the service prefix) + if (!OperatingSystem.IsLinux()) + { + Assert.True(fakeNtlmServer.IsMICPresent); + Assert.Equal("HTTP/foo", fakeNtlmServer.ClientSpecifiedSpn); + } + } + + [ConditionalFact(nameof(IsNtlmAvailable))] + public void NtlmIncorrectExchangeTest() + { + FakeNtlmServer fakeNtlmServer = new FakeNtlmServer(s_testCredentialRight); + NegotiateAuthentication ntAuth = new NegotiateAuthentication( + new NegotiateAuthenticationClientOptions + { + Package = "NTLM", + Credential = s_testCredentialWrong, + TargetName = "HTTP/foo", + RequiredProtectionLevel = ProtectionLevel.Sign + }); + + DoNtlmExchange(fakeNtlmServer, ntAuth); + + Assert.False(fakeNtlmServer.IsAuthenticated); + } + + private void DoNtlmExchange(FakeNtlmServer fakeNtlmServer, NegotiateAuthentication ntAuth) + { + NegotiateAuthenticationStatusCode statusCode; + byte[]? negotiateBlob = ntAuth.GetOutgoingBlob((byte[])null, out statusCode); + Assert.Equal(NegotiateAuthenticationStatusCode.ContinueNeeded, statusCode); + Assert.NotNull(negotiateBlob); + byte[]? challengeBlob = fakeNtlmServer.GetOutgoingBlob(negotiateBlob); + Assert.NotNull(challengeBlob); + byte[]? authenticateBlob = ntAuth.GetOutgoingBlob(challengeBlob, out statusCode); + Assert.Equal(NegotiateAuthenticationStatusCode.Completed, statusCode); + Assert.NotNull(authenticateBlob); + byte[]? empty = fakeNtlmServer.GetOutgoingBlob(authenticateBlob); + Assert.Null(empty); + } + + [ConditionalTheory(nameof(IsNtlmAvailable))] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, false)] + public void NegotiateCorrectExchangeTest(bool requestMIC, bool requestConfidentiality) + { + // Older versions of gss-ntlmssp on Linux generate MIC at incorrect offset unless ForceNegotiateVersion is specified + FakeNtlmServer fakeNtlmServer = new FakeNtlmServer(s_testCredentialRight) { ForceNegotiateVersion = true }; + FakeNegotiateServer fakeNegotiateServer = new FakeNegotiateServer(fakeNtlmServer) { RequestMIC = requestMIC }; + NegotiateAuthentication ntAuth = new NegotiateAuthentication( + new NegotiateAuthenticationClientOptions + { + Package = "Negotiate", + Credential = s_testCredentialRight, + TargetName = "HTTP/foo", + RequiredProtectionLevel = requestConfidentiality ? ProtectionLevel.EncryptAndSign : ProtectionLevel.Sign + }); + + byte[]? clientBlob = null; + byte[]? serverBlob = null; + NegotiateAuthenticationStatusCode statusCode; + do + { + clientBlob = ntAuth.GetOutgoingBlob(serverBlob, out statusCode); + if (clientBlob != null) + { + Assert.False(fakeNegotiateServer.IsAuthenticated); + // Send the client blob to the fake server + serverBlob = fakeNegotiateServer.GetOutgoingBlob(clientBlob); + } + + if (statusCode == NegotiateAuthenticationStatusCode.Completed) + { + Assert.True(ntAuth.IsAuthenticated); + Assert.True(fakeNegotiateServer.IsAuthenticated); + Assert.True(fakeNtlmServer.IsAuthenticated); + } + else if (statusCode == NegotiateAuthenticationStatusCode.ContinueNeeded) + { + Assert.NotNull(clientBlob); + Assert.NotNull(serverBlob); + } + else + { + Assert.Fail(statusCode.ToString()); + } + } + while (!ntAuth.IsAuthenticated); + } + } +} diff --git a/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj b/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj index 3f61524d58c4c..771a712cc57bb 100644 --- a/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj +++ b/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj @@ -27,6 +27,7 @@ + From 7e85f10db1f3f27e63395d9c250aa17e340cc966 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 14 Jun 2022 14:14:39 +0000 Subject: [PATCH 18/28] Add exceptions for invalid inputs/states --- .../Net/Security/NegotiateAuthentication.cs | 8 ++++++++ .../UnitTests/NegotiateAuthenticationTests.cs | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs index 1c14456ce85c3..4800b7464657e 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs @@ -23,6 +23,8 @@ public sealed class NegotiateAuthentication : IDisposable /// The property bag for the authentication options. public NegotiateAuthentication(NegotiateAuthenticationClientOptions clientOptions) { + ArgumentNullException.ThrowIfNull(clientOptions); + ContextFlagsPal contextFlags = ContextFlagsPal.Connection | clientOptions.RequiredProtectionLevel switch { ProtectionLevel.Sign => ContextFlagsPal.InitIntegrity, @@ -47,6 +49,8 @@ public NegotiateAuthentication(NegotiateAuthenticationClientOptions clientOption /// The property bag for the authentication options. public NegotiateAuthentication(NegotiateAuthenticationServerOptions serverOptions) { + ArgumentNullException.ThrowIfNull(serverOptions); + ContextFlagsPal contextFlags = ContextFlagsPal.Connection | serverOptions.RequiredProtectionLevel switch { ProtectionLevel.Sign => ContextFlagsPal.AcceptIntegrity, @@ -162,6 +166,10 @@ public IIdentity RemoteIdentity IIdentity? identity = _remoteIdentity; if (identity is null) { + if (!IsAuthenticated) + { + throw new InvalidOperationException(SR.net_auth_noauth); + } if (IsServer) { // Server authentication is not supported on tvOS diff --git a/src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs b/src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs index 796e6148f3c05..2ceb7a53b88a8 100644 --- a/src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs +++ b/src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs @@ -20,6 +20,24 @@ public class NegotiateAuthenticationTests private static NetworkCredential s_testCredentialWrong = new NetworkCredential("rightusername", "wrongpassword"); private static readonly byte[] s_Hello = "Hello"u8.ToArray(); + [Fact] + public void Constructor_Overloads_Validation() + { + AssertExtensions.Throws("clientOptions", () => { new NegotiateAuthentication((NegotiateAuthenticationClientOptions)null); }); + AssertExtensions.Throws("serverOptions", () => { new NegotiateAuthentication((NegotiateAuthenticationServerOptions)null); }); + } + + // TODO: Currently fails both on Linux (proceeds with Negotiate) and managed implementation (throws PlatformNotSupportedException) + /*[Fact] + public void Unsupported_Package() + { + NegotiateAuthenticationClientOptions clientOptions = new NegotiateAuthenticationClientOptions { Package = "INVALID", Credential = s_testCredentialRight, TargetName = "HTTP/foo" }; + NegotiateAuthentication negotiateAuthentication = new NegotiateAuthentication(clientOptions); + NegotiateAuthenticationStatusCode statusCode; + negotiateAuthentication.GetOutgoingBlob((byte[]?)null, out statusCode); + Assert.Equal(NegotiateAuthenticationStatusCode.Unsupported, statusCode); + }*/ + [Fact] public void NtlmProtocolExampleTest() { From 7c870bd34d700b54cbed08acc68c6b5f71a9f0b6 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Thu, 16 Jun 2022 06:22:54 +0000 Subject: [PATCH 19/28] Remove tvOS unsupported marker, managed NTLM is used on tvOS --- src/libraries/Common/src/System/Net/NTAuthentication.Common.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs index c54e12353a2ad..bd354c4816965 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs @@ -11,7 +11,6 @@ namespace System.Net { - [UnsupportedOSPlatform("tvos")] internal sealed partial class NTAuthentication { private bool _isServer; From 1bd0cdea8e2aae9ba95a56c38c032182e757d853 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 14 Jun 2022 18:35:59 +0200 Subject: [PATCH 20/28] Apply suggestions from code review Co-authored-by: Stephen Toub --- .../src/System/Net/NTAuthentication.Common.cs | 9 +++----- .../System/Net/NTAuthentication.Managed.cs | 4 ++-- .../Security/NegotiateStreamPal.Windows.cs | 12 +++------- .../Net/Security/NegotiateAuthentication.cs | 22 +++++++++---------- 4 files changed, 19 insertions(+), 28 deletions(-) diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs index bd354c4816965..b4cb6c6401b65 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs @@ -94,10 +94,7 @@ internal bool IsNTLM { get { - if (_lastProtocolName == null) - { - _lastProtocolName = ProtocolName; - } + _lastProtocolName ??= ProtocolName; return (object)_lastProtocolName == (object)NegotiationInfoClass.NTLM; } @@ -232,13 +229,13 @@ internal int MakeSignature(byte[] buffer, int offset, int count, [AllowNull] ref internal byte[]? GetOutgoingBlob(byte[]? incomingBlob, bool throwOnError) { - return GetOutgoingBlob(incomingBlob != null ? incomingBlob.AsSpan() : default, throwOnError, out _); + return GetOutgoingBlob(incomingBlob.AsSpan(), throwOnError, out _); } // Accepts an incoming binary security blob and returns an outgoing binary security blob. internal byte[]? GetOutgoingBlob(byte[]? incomingBlob, bool throwOnError, out SecurityStatusPal statusCode) { - return GetOutgoingBlob(incomingBlob != null ? incomingBlob.AsSpan() : default, throwOnError, out statusCode); + return GetOutgoingBlob(incomingBlob.AsSpan(), throwOnError, out statusCode); } internal byte[]? GetOutgoingBlob(ReadOnlySpan incomingBlob, bool throwOnError, out SecurityStatusPal statusCode) diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs index 6d117cdb24cfd..0de29b67644f4 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs @@ -330,13 +330,13 @@ internal void CloseContext() internal byte[]? GetOutgoingBlob(byte[]? incomingBlob, bool throwOnError) { - return GetOutgoingBlob(incomingBlob != null ? incomingBlob.AsSpan() : default, throwOnError, out _); + return GetOutgoingBlob(incomingBlob.AsSpan(), throwOnError, out _); } // Accepts an incoming binary security blob and returns an outgoing binary security blob. internal byte[]? GetOutgoingBlob(byte[]? incomingBlob, bool throwOnError, out SecurityStatusPal statusCode) { - return GetOutgoingBlob(incomingBlob != null ? incomingBlob.AsSpan() : default, throwOnError, out statusCode); + return GetOutgoingBlob(incomingBlob.AsSpan(), throwOnError, out statusCode); } internal unsafe byte[]? GetOutgoingBlob(ReadOnlySpan incomingBlob, bool throwOnError, out SecurityStatusPal statusCode) diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs index 065c3600b324b..664a3ef4d9c32 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs @@ -395,15 +395,9 @@ internal static int Decrypt( securityBuffer[0] = new SecurityBuffer(buffer, offset, count, SecurityBufferType.SECBUFFER_STREAM); securityBuffer[1] = new SecurityBuffer(0, SecurityBufferType.SECBUFFER_DATA); - int errorCode; - if (isConfidential) - { - errorCode = SSPIWrapper.DecryptMessage(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); - } - else - { - errorCode = SSPIWrapper.VerifySignature(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); - } + int errorCode = isConfidential ? + SSPIWrapper.DecryptMessage(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber) : + SSPIWrapper.VerifySignature(GlobalSSPI.SSPIAuth, securityContext, securityBuffer, sequenceNumber); if (errorCode != 0) { diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs index 4800b7464657e..f2ef20a031304 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs @@ -25,12 +25,12 @@ public NegotiateAuthentication(NegotiateAuthenticationClientOptions clientOption { ArgumentNullException.ThrowIfNull(clientOptions); - ContextFlagsPal contextFlags = ContextFlagsPal.Connection | clientOptions.RequiredProtectionLevel switch + ContextFlagsPal contextFlags = clientOptions.RequiredProtectionLevel switch { ProtectionLevel.Sign => ContextFlagsPal.InitIntegrity, ProtectionLevel.EncryptAndSign => ContextFlagsPal.InitIntegrity | ContextFlagsPal.Confidentiality, _ => 0 - }; + } | ContextFlagsPal.Connection; _isServer = false; _ntAuthentication = new NTAuthentication( @@ -51,12 +51,12 @@ public NegotiateAuthentication(NegotiateAuthenticationServerOptions serverOption { ArgumentNullException.ThrowIfNull(serverOptions); - ContextFlagsPal contextFlags = ContextFlagsPal.Connection | serverOptions.RequiredProtectionLevel switch + ContextFlagsPal contextFlags = serverOptions.RequiredProtectionLevel switch { ProtectionLevel.Sign => ContextFlagsPal.AcceptIntegrity, ProtectionLevel.EncryptAndSign => ContextFlagsPal.AcceptIntegrity | ContextFlagsPal.Confidentiality, _ => 0 - }; + } | ContextFlagsPal.Connection; _isServer = true; _ntAuthentication = new NTAuthentication( @@ -97,10 +97,10 @@ public void Dispose() /// level specified by or /// . /// - public ProtectionLevel ProtectionLevel - { - get => IsSigned ? (IsEncrypted ? ProtectionLevel.EncryptAndSign : ProtectionLevel.Sign) : ProtectionLevel.None; - } + public ProtectionLevel ProtectionLevel => + !IsSigned ? ProtectionLevel.None : + !IsEncrypted ? ProtectionLevel.Sign : + ProtectionLevel.EncryptAndSign; /// /// Indicates whether data signing was negotiated. @@ -170,10 +170,10 @@ public IIdentity RemoteIdentity { throw new InvalidOperationException(SR.net_auth_noauth); } + if (IsServer) { - // Server authentication is not supported on tvOS - Debug.Assert(!OperatingSystem.IsTvOS()); + Debug.Assert(!OperatingSystem.IsTvOS(), "Server authentication is not supported on tvOS"); _remoteIdentity = identity = NegotiateStreamPal.GetIdentity(_ntAuthentication); } else @@ -272,7 +272,7 @@ public IIdentity RemoteIdentity public string? GetOutgoingBlob(string? incomingBlob, out NegotiateAuthenticationStatusCode statusCode) { byte[]? decodedIncomingBlob = null; - if (incomingBlob != null && incomingBlob.Length > 0) + if (!string.IsNullOrEmpty(incomingBlob)) { decodedIncomingBlob = Convert.FromBase64String(incomingBlob); } From e93f0737b740bf675e539c33fd33402ac3641563 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 14 Jun 2022 22:28:53 +0200 Subject: [PATCH 21/28] Fix typo --- .../src/System/Net/Security/NegotiateStreamPal.Windows.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs index 664a3ef4d9c32..29603b58d2ce5 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs @@ -143,7 +143,7 @@ internal static SecurityStatusPal AcceptSecurityContext( ref ContextFlagsPal contextFlags) { InputSecurityBuffers inputBuffers = default; - if (incomingBlob.IsEmpty) + if (!incomingBlob.IsEmpty) { inputBuffers.SetNextBuffer(new InputSecurityBuffer(incomingBlob, SecurityBufferType.SECBUFFER_TOKEN)); } From 5aae44d90d7ddba09f385529bbfcfedd80aa5751 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Thu, 16 Jun 2022 06:24:21 +0000 Subject: [PATCH 22/28] Remove reference equality checks from IsNTLM/IsKerberos --- .../Common/src/System/Net/NTAuthentication.Common.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs index b4cb6c6401b65..57d577b87b432 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs @@ -81,12 +81,8 @@ internal bool IsKerberos { get { - if (_lastProtocolName == null) - { - _lastProtocolName = ProtocolName; - } - - return (object)_lastProtocolName == (object)NegotiationInfoClass.Kerberos; + _lastProtocolName ??= ProtocolName; + return _lastProtocolName == NegotiationInfoClass.Kerberos; } } @@ -95,8 +91,7 @@ internal bool IsNTLM get { _lastProtocolName ??= ProtocolName; - - return (object)_lastProtocolName == (object)NegotiationInfoClass.NTLM; + return _lastProtocolName == NegotiationInfoClass.NTLM; } } From b74659098795b6950fd6b404be25c5f95a71134c Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Thu, 16 Jun 2022 06:32:58 +0000 Subject: [PATCH 23/28] Remove NTAuthentication.AssociatedName to make it more obvious which exceptions are thrown --- .../src/System/Net/NTAuthentication.Common.cs | 15 --------------- .../Net/Security/NegotiateStreamPal.Unix.cs | 5 ----- .../NegotiateStreamPal.PlatformNotSupported.cs | 5 ----- .../Net/Security/NegotiateStreamPal.Windows.cs | 10 ++++++---- 4 files changed, 6 insertions(+), 29 deletions(-) diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs index 57d577b87b432..8597a30cfef93 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs @@ -95,21 +95,6 @@ internal bool IsNTLM } } - internal string? AssociatedName - { - get - { - if (!(IsValidContext && IsCompleted)) - { - throw new Win32Exception((int)SecurityStatusPalErrorCode.InvalidHandle); - } - - string? name = NegotiateStreamPal.QueryContextAssociatedName(_securityContext!); - if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"NTAuthentication: The context is associated with [{name}]"); - return name; - } - } - // // This overload does not attempt to impersonate because the caller either did it already or the original thread context is still preserved. // diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs index cb038ae9f1dfa..89d465086114d 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs @@ -28,11 +28,6 @@ internal static partial class NegotiateStreamPal // defined in winerror.h private const int NTE_FAIL = unchecked((int)0x80090020); - internal static string QueryContextAssociatedName(SafeDeleteContext? securityContext) - { - throw new PlatformNotSupportedException(SR.net_nego_server_not_supported); - } - internal static string QueryContextClientSpecifiedSpn(SafeDeleteContext securityContext) { throw new PlatformNotSupportedException(SR.net_nego_server_not_supported); diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.PlatformNotSupported.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.PlatformNotSupported.cs index 056449dbc407c..841a3c59f25d9 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.PlatformNotSupported.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.PlatformNotSupported.cs @@ -20,11 +20,6 @@ internal static IIdentity GetIdentity(NTAuthentication context) throw new PlatformNotSupportedException(); } - internal static string QueryContextAssociatedName(SafeDeleteContext? securityContext) - { - throw new PlatformNotSupportedException(SR.net_nego_server_not_supported); - } - internal static void ValidateImpersonationLevel(TokenImpersonationLevel impersonationLevel) { throw new PlatformNotSupportedException(); diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Windows.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Windows.cs index 39248f0fc7bf6..1fb9995d90dd6 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Windows.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStreamPal.Windows.cs @@ -21,7 +21,7 @@ internal static partial class NegotiateStreamPal internal static IIdentity GetIdentity(NTAuthentication context) { IIdentity? result; - string name = context.IsServer ? context.AssociatedName! : context.Spn!; + string? name = context.IsServer ? null : context.Spn; string protocol = context.ProtocolName; if (context.IsServer) @@ -29,13 +29,15 @@ internal static IIdentity GetIdentity(NTAuthentication context) SecurityContextTokenHandle? token = null; try { - SecurityStatusPal status; - SafeDeleteContext? securityContext = context.GetContext(out status); + SafeDeleteContext? securityContext = context.GetContext(out SecurityStatusPal status); if (status.ErrorCode != SecurityStatusPalErrorCode.OK) { throw new Win32Exception((int)SecurityStatusAdapterPal.GetInteropFromSecurityStatusPal(status)); } + name = QueryContextAssociatedName(securityContext!); + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(context, $"NTAuthentication: The context is associated with [{name}]"); + // This will return a client token when conducted authentication on server side. // This token can be used for impersonation. We use it to create a WindowsIdentity and hand it out to the server app. Interop.SECURITY_STATUS winStatus = (Interop.SECURITY_STATUS)SSPIWrapper.QuerySecurityContextToken( @@ -64,7 +66,7 @@ internal static IIdentity GetIdentity(NTAuthentication context) } // On the client we don't have access to the remote side identity. - result = new GenericIdentity(name, protocol); + result = new GenericIdentity(name ?? string.Empty, protocol); return result; } From dd7dd9abe0ccfb5ab4dd074eea049710e8ab420a Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Thu, 16 Jun 2022 06:40:59 +0000 Subject: [PATCH 24/28] Add comment --- .../src/System/Net/Security/NegotiateAuthentication.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs index f2ef20a031304..9b6cdff847230 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.ComponentModel; using System.Diagnostics; using System.Security.Principal; @@ -159,6 +160,7 @@ public void Dispose() /// An object that describes the identity of the remote endpoint. /// /// Authentication failed or has not occurred. + /// System error occurred when trying to retrieve the identity. public IIdentity RemoteIdentity { get From 39a43b25a84ad51e4b58abd02df455bb42e9ed6d Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Thu, 16 Jun 2022 08:11:31 +0000 Subject: [PATCH 25/28] Add more tests, handle unsupported protocols --- .../src/System/Net/NTAuthentication.Common.cs | 1 + .../System/Net/NTAuthentication.Managed.cs | 2 +- .../Net/Security/NegotiateStreamPal.Unix.cs | 6 ++ .../Net/Security/NegotiateAuthentication.cs | 74 +++++++++++++------ .../UnitTests/NegotiateAuthenticationTests.cs | 59 ++++++++++++++- 5 files changed, 114 insertions(+), 28 deletions(-) diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs index 8597a30cfef93..cdb7103cdcd8d 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs @@ -155,6 +155,7 @@ internal void CloseContext() { _securityContext.Dispose(); } + _isCompleted = false; } internal int VerifySignature(byte[] buffer, int offset, int count) diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs index 0de29b67644f4..ad6b0eacbc89b 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs @@ -296,7 +296,7 @@ internal void CloseContext() _serverSeal = null; _clientSequenceNumber = 0; _serverSequenceNumber = 0; - IsCompleted = true; + IsCompleted = false; } internal string? GetOutgoingBlob(string? incomingBlob) diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs index 89d465086114d..47daaaf9872f6 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs @@ -514,6 +514,12 @@ internal static SafeFreeCredentials AcquireCredentialsHandle(string package, boo throw new PlatformNotSupportedException(SR.net_ntlm_not_possible_default_cred); } + if (!ntlmOnly && !string.Equals(package, NegotiationInfoClass.Negotiate)) + { + // Native shim currently supports only NTLM and Negotiate + throw new PlatformNotSupportedException(SR.net_securitypackagesupport); + } + try { return isEmptyCredential ? diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs index 9b6cdff847230..0871b91e24fa0 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs @@ -13,7 +13,8 @@ namespace System.Net.Security /// public sealed class NegotiateAuthentication : IDisposable { - private readonly NTAuthentication _ntAuthentication; + private readonly NTAuthentication? _ntAuthentication; + private readonly string _requestedPackage; private readonly bool _isServer; private IIdentity? _remoteIdentity; @@ -34,13 +35,23 @@ public NegotiateAuthentication(NegotiateAuthenticationClientOptions clientOption } | ContextFlagsPal.Connection; _isServer = false; - _ntAuthentication = new NTAuthentication( - isServer: false, - clientOptions.Package, - clientOptions.Credential, - clientOptions.TargetName, - contextFlags, - clientOptions.Binding); + _requestedPackage = clientOptions.Package; + try + { + _ntAuthentication = new NTAuthentication( + isServer: false, + clientOptions.Package, + clientOptions.Credential, + clientOptions.TargetName, + contextFlags, + clientOptions.Binding); + } + catch (PlatformNotSupportedException) + { + } + catch (Win32Exception) + { + } } /// @@ -60,13 +71,23 @@ public NegotiateAuthentication(NegotiateAuthenticationServerOptions serverOption } | ContextFlagsPal.Connection; _isServer = true; - _ntAuthentication = new NTAuthentication( - isServer: true, - serverOptions.Package, - serverOptions.Credential, - null, - contextFlags, - serverOptions.Binding); + _requestedPackage = serverOptions.Package; + try + { + _ntAuthentication = new NTAuthentication( + isServer: true, + serverOptions.Package, + serverOptions.Credential, + null, + contextFlags, + serverOptions.Binding); + } + catch (PlatformNotSupportedException) + { + } + catch (Win32Exception) + { + } } /// @@ -75,7 +96,7 @@ public NegotiateAuthentication(NegotiateAuthenticationServerOptions serverOption /// public void Dispose() { - _ntAuthentication.CloseContext(); + _ntAuthentication?.CloseContext(); if (_remoteIdentity is IDisposable disposableRemoteIdentity) { disposableRemoteIdentity.Dispose(); @@ -86,7 +107,7 @@ public void Dispose() /// Indicates whether authentication was successfully completed and the session /// was established. /// - public bool IsAuthenticated => _ntAuthentication.IsCompleted; + public bool IsAuthenticated => _ntAuthentication?.IsCompleted ?? false; /// /// Indicates the negotiated level of protection. @@ -106,17 +127,17 @@ public void Dispose() /// /// Indicates whether data signing was negotiated. /// - public bool IsSigned => _ntAuthentication.IsIntegrityFlag; + public bool IsSigned => _ntAuthentication?.IsIntegrityFlag ?? false; /// /// Indicates whether data encryption was negotiated. /// - public bool IsEncrypted => _ntAuthentication.IsConfidentialityFlag; + public bool IsEncrypted => _ntAuthentication?.IsConfidentialityFlag ?? false; /// /// Indicates whether both server and client have been authenticated. /// - public bool IsMutuallyAuthenticated => _ntAuthentication.IsMutualAuthFlag; + public bool IsMutuallyAuthenticated => _ntAuthentication?.IsMutualAuthFlag ?? false; /// /// Indicates whether the local side of the authentication is representing @@ -139,7 +160,7 @@ public void Dispose() /// property will be Kerberos, NTLM, or any other specific protocol that was /// negotiated between both sides of the authentication. /// - public string Package => _ntAuthentication.ProtocolName; + public string Package => _ntAuthentication?.ProtocolName ?? _requestedPackage; /// /// Gets target name (service principal name) of the server. @@ -151,7 +172,7 @@ public void Dispose() /// For client-side of the authentication the property returns the target name /// specified in . /// - public string? TargetName => IsServer ? _ntAuthentication.ClientSpecifiedSpn : _ntAuthentication.Spn; + public string? TargetName => IsServer ? _ntAuthentication?.ClientSpecifiedSpn : _ntAuthentication?.Spn; /// /// Gets information about the identity of the remote party. @@ -168,7 +189,7 @@ public IIdentity RemoteIdentity IIdentity? identity = _remoteIdentity; if (identity is null) { - if (!IsAuthenticated) + if (!IsAuthenticated || _ntAuthentication == null) { throw new InvalidOperationException(SR.net_auth_noauth); } @@ -206,6 +227,13 @@ public IIdentity RemoteIdentity /// public byte[]? GetOutgoingBlob(ReadOnlySpan incomingBlob, out NegotiateAuthenticationStatusCode statusCode) { + if (_ntAuthentication == null) + { + // Unsupported protocol + statusCode = NegotiateAuthenticationStatusCode.Unsupported; + return null; + } + byte[]? blob = _ntAuthentication.GetOutgoingBlob(incomingBlob, false, out SecurityStatusPal securityStatus); // Map error codes diff --git a/src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs b/src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs index 2ceb7a53b88a8..4cc089db463b5 100644 --- a/src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs +++ b/src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs @@ -15,6 +15,7 @@ namespace System.Net.Security.Tests public class NegotiateAuthenticationTests { private static bool IsNtlmAvailable => Capability.IsNtlmInstalled() || OperatingSystem.IsAndroid() || OperatingSystem.IsTvOS(); + private static bool IsNtlmUnavailable => !IsNtlmAvailable; private static NetworkCredential s_testCredentialRight = new NetworkCredential("rightusername", "rightpassword"); private static NetworkCredential s_testCredentialWrong = new NetworkCredential("rightusername", "wrongpassword"); @@ -27,16 +28,66 @@ public void Constructor_Overloads_Validation() AssertExtensions.Throws("serverOptions", () => { new NegotiateAuthentication((NegotiateAuthenticationServerOptions)null); }); } - // TODO: Currently fails both on Linux (proceeds with Negotiate) and managed implementation (throws PlatformNotSupportedException) - /*[Fact] - public void Unsupported_Package() + [Fact] + public void RemoteIdentity_ThrowsOnUnauthenticated() + { + NegotiateAuthenticationClientOptions clientOptions = new NegotiateAuthenticationClientOptions { Credential = s_testCredentialRight, TargetName = "HTTP/foo" }; + NegotiateAuthentication negotiateAuthentication = new NegotiateAuthentication(clientOptions); + Assert.Throws(() => { _ = negotiateAuthentication.RemoteIdentity; }); + } + + [ConditionalFact(nameof(IsNtlmAvailable))] + public void RemoteIdentity_ThrowsOnDisposed() + { + FakeNtlmServer fakeNtlmServer = new FakeNtlmServer(s_testCredentialRight); + NegotiateAuthentication negotiateAuthentication = new NegotiateAuthentication( + new NegotiateAuthenticationClientOptions + { + Package = "NTLM", + Credential = s_testCredentialRight, + TargetName = "HTTP/foo", + RequiredProtectionLevel = ProtectionLevel.Sign + }); + + DoNtlmExchange(fakeNtlmServer, negotiateAuthentication); + + Assert.True(fakeNtlmServer.IsAuthenticated); + Assert.True(negotiateAuthentication.IsAuthenticated); + _ = negotiateAuthentication.RemoteIdentity; + + negotiateAuthentication.Dispose(); + Assert.Throws(() => { _ = negotiateAuthentication.RemoteIdentity; }); + } + + [Fact] + public void Package_Unsupported() { NegotiateAuthenticationClientOptions clientOptions = new NegotiateAuthenticationClientOptions { Package = "INVALID", Credential = s_testCredentialRight, TargetName = "HTTP/foo" }; NegotiateAuthentication negotiateAuthentication = new NegotiateAuthentication(clientOptions); NegotiateAuthenticationStatusCode statusCode; negotiateAuthentication.GetOutgoingBlob((byte[]?)null, out statusCode); Assert.Equal(NegotiateAuthenticationStatusCode.Unsupported, statusCode); - }*/ + } + + [ConditionalFact(nameof(IsNtlmAvailable))] + public void Package_Supported_NTLM() + { + NegotiateAuthenticationClientOptions clientOptions = new NegotiateAuthenticationClientOptions { Package = "NTLM", Credential = s_testCredentialRight, TargetName = "HTTP/foo" }; + NegotiateAuthentication negotiateAuthentication = new NegotiateAuthentication(clientOptions); + NegotiateAuthenticationStatusCode statusCode; + negotiateAuthentication.GetOutgoingBlob((byte[]?)null, out statusCode); + Assert.Equal(NegotiateAuthenticationStatusCode.ContinueNeeded, statusCode); + } + + [ConditionalFact(nameof(IsNtlmUnavailable))] + public void Package_Unsupported_NTLM() + { + NegotiateAuthenticationClientOptions clientOptions = new NegotiateAuthenticationClientOptions { Package = "NTLM", Credential = s_testCredentialRight, TargetName = "HTTP/foo" }; + NegotiateAuthentication negotiateAuthentication = new NegotiateAuthentication(clientOptions); + NegotiateAuthenticationStatusCode statusCode; + negotiateAuthentication.GetOutgoingBlob((byte[]?)null, out statusCode); + Assert.Equal(NegotiateAuthenticationStatusCode.Unsupported, statusCode); + } [Fact] public void NtlmProtocolExampleTest() From 9a4ccdf2b82ec245d06f5bc5aeb4afce095a0119 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Thu, 16 Jun 2022 09:57:19 +0000 Subject: [PATCH 26/28] Handle NotSupportedException from NTAuthentication constructor --- .../System/Net/Security/NegotiateAuthentication.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs index 0871b91e24fa0..1592c94ea65a1 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs @@ -46,10 +46,13 @@ public NegotiateAuthentication(NegotiateAuthenticationClientOptions clientOption contextFlags, clientOptions.Binding); } - catch (PlatformNotSupportedException) + catch (PlatformNotSupportedException) // Managed implementation, Unix { } - catch (Win32Exception) + catch (NotSupportedException) // Windows implementation + { + } + catch (Win32Exception) // Unix implementation in native layer { } } @@ -82,10 +85,13 @@ public NegotiateAuthentication(NegotiateAuthenticationServerOptions serverOption contextFlags, serverOptions.Binding); } - catch (PlatformNotSupportedException) + catch (PlatformNotSupportedException) // Managed implementation, Unix + { + } + catch (NotSupportedException) // Windows implementation { } - catch (Win32Exception) + catch (Win32Exception) // Unix implementation in native layer { } } From e86ce793eb129629d3b270541cae2d7aff7d5a77 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Fri, 17 Jun 2022 11:31:06 +0000 Subject: [PATCH 27/28] Add workaround for linker issue --- .../Common/src/System/Net/NTAuthentication.Managed.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs index ad6b0eacbc89b..8550ac503b440 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs @@ -282,6 +282,7 @@ internal NTAuthentication(bool isServer, string package, NetworkCredential crede _spn = spn; _channelBinding = channelBinding; _contextFlags = requestedContextFlags; + IsServer = isServer; } internal void CloseContext() @@ -1029,7 +1030,7 @@ internal int Decrypt(byte[] payload, int offset, int count, out int newOffset, u internal bool IsKerberos => false; - internal bool IsServer => false; + internal bool IsServer { get; set; } internal bool IsValidContext => true; From ffed0be89c48cc7929b270ad7ad1a0b3f69136d8 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Mon, 20 Jun 2022 10:47:19 +0200 Subject: [PATCH 28/28] Apply suggestions from code review --- .../System/Net/Security/NegotiateAuthenticationClientOptions.cs | 2 +- .../System/Net/Security/NegotiateAuthenticationServerOptions.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationClientOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationClientOptions.cs index a18f15288fb1e..e1c17698d8df1 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationClientOptions.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationClientOptions.cs @@ -33,7 +33,7 @@ public class NegotiateAuthenticationClientOptions public ChannelBinding? Binding { get; set; } /// - /// Indicates the requires level of protection of the authentication exchange + /// Indicates the required level of protection of the authentication exchange /// and any further data exchange. Default value is None. /// public ProtectionLevel RequiredProtectionLevel { get; set; } = ProtectionLevel.None; diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationServerOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationServerOptions.cs index c96df1c9e60ab..6cb4919355802 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationServerOptions.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationServerOptions.cs @@ -28,7 +28,7 @@ public class NegotiateAuthenticationServerOptions public ChannelBinding? Binding { get; set; } /// - /// Indicates the requires level of protection of the authentication exchange + /// Indicates the required level of protection of the authentication exchange /// and any further data exchange. Default value is None. /// public ProtectionLevel RequiredProtectionLevel { get; set; } = ProtectionLevel.None;