diff options
Diffstat (limited to 'src')
20 files changed, 707 insertions, 458 deletions
diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Common.cs index 4c600ddceaa..d40d00d2089 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.Buffers; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -148,14 +149,19 @@ namespace System.Net _isCompleted = false; } - internal int Wrap(ReadOnlySpan<byte> buffer, [NotNull] ref byte[]? output, bool isConfidential) + internal NegotiateAuthenticationStatusCode Wrap(ReadOnlySpan<byte> input, IBufferWriter<byte> outputWriter, bool requestEncryption, out bool isEncrypted) { - return NegotiateStreamPal.Wrap(_securityContext!, buffer, ref output, isConfidential); + return NegotiateStreamPal.Wrap(_securityContext!, input, outputWriter, requestEncryption, out isEncrypted); } - internal int Unwrap(Span<byte> buffer, out int newOffset, out bool wasConfidential) + internal NegotiateAuthenticationStatusCode Unwrap(ReadOnlySpan<byte> input, IBufferWriter<byte> outputWriter, out bool wasEncrypted) { - return NegotiateStreamPal.Unwrap(_securityContext!, buffer, out newOffset, out wasConfidential); + return NegotiateStreamPal.Unwrap(_securityContext!, input, outputWriter, out wasEncrypted); + } + + internal NegotiateAuthenticationStatusCode UnwrapInPlace(Span<byte> input, out int unwrappedOffset, out int unwrappedLength, out bool wasEncrypted) + { + return NegotiateStreamPal.UnwrapInPlace(_securityContext!, input, out unwrappedOffset, out unwrappedLength, out wasEncrypted); } internal string? GetOutgoingBlob(string? incomingBlob) diff --git a/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs b/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs index 8e6cf07d86c..95324f1485d 100644 --- a/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs +++ b/src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs @@ -2,8 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.ComponentModel; +using System.Buffers; using System.Buffers.Binary; +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Formats.Asn1; @@ -1012,6 +1013,77 @@ namespace System.Net return null; } + internal NegotiateAuthenticationStatusCode Wrap(ReadOnlySpan<byte> input, IBufferWriter<byte> outputWriter, bool requestEncryption, out bool isEncrypted) + { + if (_clientSeal == null) + { + throw new InvalidOperationException(SR.net_auth_noauth); + } + + Span<byte> output = outputWriter.GetSpan(input.Length + SignatureLength); + _clientSeal.Transform(input, output.Slice(SignatureLength, input.Length)); + CalculateSignature(input, _clientSequenceNumber, _clientSigningKey, _clientSeal, output.Slice(0, SignatureLength)); + _clientSequenceNumber++; + + isEncrypted = true; + outputWriter.Advance(input.Length + SignatureLength); + + return NegotiateAuthenticationStatusCode.Completed; + } + + internal NegotiateAuthenticationStatusCode Unwrap(ReadOnlySpan<byte> input, IBufferWriter<byte> outputWriter, out bool wasEncrypted) + { + wasEncrypted = true; + + if (_serverSeal == null) + { + throw new InvalidOperationException(SR.net_auth_noauth); + } + + if (input.Length < SignatureLength) + { + return NegotiateAuthenticationStatusCode.InvalidToken; + } + + Span<byte> output = outputWriter.GetSpan(input.Length - SignatureLength); + _serverSeal.Transform(input.Slice(SignatureLength), output.Slice(0, input.Length - SignatureLength)); + if (!VerifyMIC(output.Slice(0, input.Length - SignatureLength), input.Slice(0, SignatureLength))) + { + CryptographicOperations.ZeroMemory(output); + return NegotiateAuthenticationStatusCode.MessageAltered; + } + + outputWriter.Advance(input.Length - SignatureLength); + + return NegotiateAuthenticationStatusCode.Completed; + } + + internal NegotiateAuthenticationStatusCode UnwrapInPlace(Span<byte> input, out int unwrappedOffset, out int unwrappedLength, out bool wasEncrypted) + { + wasEncrypted = true; + unwrappedOffset = SignatureLength; + unwrappedLength = input.Length - SignatureLength; + + if (_serverSeal == null) + { + throw new InvalidOperationException(SR.net_auth_noauth); + } + + if (input.Length < SignatureLength) + { + return NegotiateAuthenticationStatusCode.InvalidToken; + } + + _serverSeal.Transform(input.Slice(SignatureLength), input.Slice(SignatureLength)); + if (!VerifyMIC(input.Slice(SignatureLength), input.Slice(0, SignatureLength))) + { + CryptographicOperations.ZeroMemory(input.Slice(SignatureLength)); + return NegotiateAuthenticationStatusCode.MessageAltered; + } + + return NegotiateAuthenticationStatusCode.Completed; + } + #pragma warning disable CA1822 internal int Encrypt(ReadOnlySpan<byte> buffer, [NotNull] ref byte[]? output) { 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 a23b96e8ae1..73043391901 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs @@ -567,18 +567,106 @@ namespace System.Net.Security return GssUnwrap(gssContext, out _, buffer); } - internal static unsafe int Unwrap(SafeDeleteContext securityContext, Span<byte> buffer, out int newOffset, out bool wasConfidential) + internal static NegotiateAuthenticationStatusCode Unwrap( + SafeDeleteContext securityContext, + ReadOnlySpan<byte> input, + IBufferWriter<byte> outputWriter, + out bool isEncrypted) { SafeGssContextHandle gssContext = ((SafeDeleteNegoContext)securityContext).GssContext!; - newOffset = 0; - return GssUnwrap(gssContext, out wasConfidential, buffer); + Interop.NetSecurityNative.GssBuffer decryptedBuffer = default(Interop.NetSecurityNative.GssBuffer); + try + { + Interop.NetSecurityNative.Status minorStatus; + Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.UnwrapBuffer(out minorStatus, gssContext, out isEncrypted, input, ref decryptedBuffer); + if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) + { + return status switch + { + Interop.NetSecurityNative.Status.GSS_S_BAD_SIG => NegotiateAuthenticationStatusCode.MessageAltered, + _ => NegotiateAuthenticationStatusCode.InvalidToken + }; + } + + decryptedBuffer.Span.CopyTo(outputWriter.GetSpan(decryptedBuffer.Span.Length)); + outputWriter.Advance(decryptedBuffer.Span.Length); + return NegotiateAuthenticationStatusCode.Completed; + } + finally + { + decryptedBuffer.Dispose(); + } + } + + internal static NegotiateAuthenticationStatusCode UnwrapInPlace( + SafeDeleteContext securityContext, + Span<byte> input, + out int unwrappedOffset, + out int unwrappedLength, + out bool isEncrypted) + { + SafeGssContextHandle gssContext = ((SafeDeleteNegoContext)securityContext).GssContext!; + Interop.NetSecurityNative.GssBuffer decryptedBuffer = default(Interop.NetSecurityNative.GssBuffer); + try + { + Interop.NetSecurityNative.Status minorStatus; + Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.UnwrapBuffer(out minorStatus, gssContext, out isEncrypted, input, ref decryptedBuffer); + if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) + { + unwrappedOffset = 0; + unwrappedLength = 0; + return status switch + { + Interop.NetSecurityNative.Status.GSS_S_BAD_SIG => NegotiateAuthenticationStatusCode.MessageAltered, + _ => NegotiateAuthenticationStatusCode.InvalidToken + }; + } + + decryptedBuffer.Span.CopyTo(input); + unwrappedOffset = 0; + unwrappedLength = decryptedBuffer.Span.Length; + return NegotiateAuthenticationStatusCode.Completed; + } + finally + { + decryptedBuffer.Dispose(); + } } - internal static unsafe int Wrap(SafeDeleteContext securityContext, ReadOnlySpan<byte> buffer, [NotNull] ref byte[]? output, bool isConfidential) + internal static NegotiateAuthenticationStatusCode Wrap( + SafeDeleteContext securityContext, + ReadOnlySpan<byte> input, + IBufferWriter<byte> outputWriter, + bool requestEncryption, + out bool isEncrypted) { SafeGssContextHandle gssContext = ((SafeDeleteNegoContext)securityContext).GssContext!; - output = GssWrap(gssContext, ref isConfidential, buffer); - return output.Length; + Interop.NetSecurityNative.GssBuffer encryptedBuffer = default; + try + { + Interop.NetSecurityNative.Status minorStatus; + bool encrypt = requestEncryption; + Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.WrapBuffer( + out minorStatus, + gssContext, + ref encrypt, + input, + ref encryptedBuffer); + isEncrypted = encrypt; + if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) + { + return NegotiateAuthenticationStatusCode.GenericFailure; + } + + encryptedBuffer.Span.CopyTo(outputWriter.GetSpan(encryptedBuffer.Span.Length)); + outputWriter.Advance(encryptedBuffer.Span.Length); + return NegotiateAuthenticationStatusCode.Completed; + } + finally + { + encryptedBuffer.Dispose(); + } + } } } 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 39d92d1b554..7fe7bbb0670 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.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.Buffers; using System.Buffers.Binary; using System.ComponentModel; using System.Diagnostics; @@ -194,16 +195,45 @@ namespace System.Net.Security return new Win32Exception((int)SecurityStatusAdapterPal.GetInteropFromSecurityStatusPal(statusCode)); } - internal static unsafe int Unwrap(SafeDeleteContext securityContext, Span<byte> buffer, out int newOffset, out bool wasConfidential) + internal static NegotiateAuthenticationStatusCode Unwrap( + SafeDeleteContext securityContext, + ReadOnlySpan<byte> input, + IBufferWriter<byte> outputWriter, + out bool wasEncrypted) { - fixed (byte* bufferPtr = buffer) + Span<byte> outputBuffer = outputWriter.GetSpan(input.Length).Slice(0, input.Length); + NegotiateAuthenticationStatusCode statusCode; + + input.CopyTo(outputBuffer); + statusCode = UnwrapInPlace(securityContext, outputBuffer, out int unwrappedOffset, out int unwrappedLength, out wasEncrypted); + + if (statusCode == NegotiateAuthenticationStatusCode.Completed) + { + if (unwrappedOffset > 0) + { + outputBuffer.Slice(unwrappedOffset, unwrappedLength).CopyTo(outputBuffer); + } + outputWriter.Advance(unwrappedLength); + } + + return statusCode; + } + + internal static unsafe NegotiateAuthenticationStatusCode UnwrapInPlace( + SafeDeleteContext securityContext, + Span<byte> input, + out int unwrappedOffset, + out int unwrappedLength, + out bool wasEncrypted) + { + fixed (byte* inputPtr = input) { Interop.SspiCli.SecBuffer* unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[2]; Interop.SspiCli.SecBuffer* streamBuffer = &unmanagedBuffer[0]; Interop.SspiCli.SecBuffer* dataBuffer = &unmanagedBuffer[1]; streamBuffer->BufferType = SecurityBufferType.SECBUFFER_STREAM; - streamBuffer->pvBuffer = (IntPtr)bufferPtr; - streamBuffer->cbBuffer = buffer.Length; + streamBuffer->pvBuffer = (IntPtr)inputPtr; + streamBuffer->cbBuffer = input.Length; dataBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA; dataBuffer->pvBuffer = IntPtr.Zero; dataBuffer->cbBuffer = 0; @@ -217,9 +247,14 @@ namespace System.Net.Security int errorCode = GlobalSSPI.SSPIAuth.DecryptMessage(securityContext, ref sdcInOut, out qop); if (errorCode != 0) { - Exception e = new Win32Exception(errorCode); - if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, e); - throw new Win32Exception(errorCode); + unwrappedOffset = 0; + unwrappedLength = 0; + wasEncrypted = false; + return errorCode switch + { + (int)Interop.SECURITY_STATUS.MessageAltered => NegotiateAuthenticationStatusCode.MessageAltered, + _ => NegotiateAuthenticationStatusCode.InvalidToken + }; } if (dataBuffer->BufferType != SecurityBufferType.SECBUFFER_DATA) @@ -227,32 +262,37 @@ namespace System.Net.Security throw new InternalException(dataBuffer->BufferType); } - wasConfidential = qop != Interop.SspiCli.SECQOP_WRAP_NO_ENCRYPT; + wasEncrypted = qop != Interop.SspiCli.SECQOP_WRAP_NO_ENCRYPT; - Debug.Assert((nint)dataBuffer->pvBuffer >= (nint)bufferPtr); - Debug.Assert((nint)dataBuffer->pvBuffer + dataBuffer->cbBuffer <= (nint)bufferPtr + buffer.Length); - newOffset = (int)((byte*)dataBuffer->pvBuffer - bufferPtr); - return dataBuffer->cbBuffer; + Debug.Assert((nint)dataBuffer->pvBuffer >= (nint)inputPtr); + Debug.Assert((nint)dataBuffer->pvBuffer + dataBuffer->cbBuffer <= (nint)inputPtr + input.Length); + unwrappedOffset = (int)((byte*)dataBuffer->pvBuffer - inputPtr); + unwrappedLength = dataBuffer->cbBuffer; + return NegotiateAuthenticationStatusCode.Completed; } } - internal static unsafe int Wrap(SafeDeleteContext securityContext, ReadOnlySpan<byte> buffer, [NotNull] ref byte[]? output, bool isConfidential) + internal static unsafe NegotiateAuthenticationStatusCode Wrap( + SafeDeleteContext securityContext, + ReadOnlySpan<byte> input, + IBufferWriter<byte> outputWriter, + bool requestEncryption, + out bool isEncrypted) { SecPkgContext_Sizes sizes = default; bool success = SSPIWrapper.QueryBlittableContextAttributes(GlobalSSPI.SSPIAuth, securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_SIZES, ref sizes); Debug.Assert(success); // alloc new output buffer if not supplied or too small - int resultSize = buffer.Length + sizes.cbMaxSignature; - if (output == null || output.Length < resultSize) - { - output = new byte[resultSize]; - } + int resultSize = input.Length + sizes.cbMaxSignature; + Span<byte> outputBuffer = outputWriter.GetSpan(resultSize); // make a copy of user data for in-place encryption - buffer.CopyTo(output.AsSpan(sizes.cbMaxSignature, buffer.Length)); + input.CopyTo(outputBuffer.Slice(sizes.cbMaxSignature, input.Length)); - fixed (byte* outputPtr = output) + isEncrypted = requestEncryption; + + fixed (byte* outputPtr = outputBuffer) { // Prepare buffers TOKEN(signature), DATA and Padding. Interop.SspiCli.SecBuffer* unmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[2]; @@ -263,25 +303,28 @@ namespace System.Net.Security tokenBuffer->cbBuffer = sizes.cbMaxSignature; dataBuffer->BufferType = SecurityBufferType.SECBUFFER_DATA; dataBuffer->pvBuffer = (IntPtr)(outputPtr + sizes.cbMaxSignature); - dataBuffer->cbBuffer = buffer.Length; + dataBuffer->cbBuffer = input.Length; Interop.SspiCli.SecBufferDesc sdcInOut = new Interop.SspiCli.SecBufferDesc(2) { pBuffers = unmanagedBuffer }; - uint qop = isConfidential ? 0 : Interop.SspiCli.SECQOP_WRAP_NO_ENCRYPT; + uint qop = requestEncryption ? 0 : Interop.SspiCli.SECQOP_WRAP_NO_ENCRYPT; int errorCode = GlobalSSPI.SSPIAuth.EncryptMessage(securityContext, ref sdcInOut, qop); if (errorCode != 0) { - Exception e = new Win32Exception(errorCode); - if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, e); - throw new Win32Exception(errorCode); + return errorCode switch + { + (int)Interop.SECURITY_STATUS.ContextExpired => NegotiateAuthenticationStatusCode.ContextExpired, + (int)Interop.SECURITY_STATUS.QopNotSupported => NegotiateAuthenticationStatusCode.QopNotSupported, + _ => NegotiateAuthenticationStatusCode.GenericFailure, + }; } - // return signed size - return tokenBuffer->cbBuffer + dataBuffer->cbBuffer; + outputWriter.Advance(tokenBuffer->cbBuffer + dataBuffer->cbBuffer); + return NegotiateAuthenticationStatusCode.Completed; } } diff --git a/src/libraries/System.Net.Mail/src/System.Net.Mail.csproj b/src/libraries/System.Net.Mail/src/System.Net.Mail.csproj index e55a2a7dbcb..e8178dfff5e 100644 --- a/src/libraries/System.Net.Mail/src/System.Net.Mail.csproj +++ b/src/libraries/System.Net.Mail/src/System.Net.Mail.csproj @@ -1,13 +1,12 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> - <TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser;$(NetCoreAppCurrent)-tvOS;$(NetCoreAppCurrent)</TargetFrameworks> + <TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser;$(NetCoreAppCurrent)</TargetFrameworks> </PropertyGroup> <!-- DesignTimeBuild requires all the TargetFramework Derived Properties to not be present in the first property group. --> <PropertyGroup> <TargetPlatformIdentifier>$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))</TargetPlatformIdentifier> <GeneratePlatformNotSupportedAssemblyMessage Condition="'$(TargetPlatformIdentifier)' == ''">SR.PlatformNotSupported_NetMail</GeneratePlatformNotSupportedAssemblyMessage> - <DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'tvOS'">$(DefineConstants);NO_NTAUTHENTICATION</DefineConstants> </PropertyGroup> <ItemGroup Condition="'$(TargetPlatformIdentifier)' != ''"> <Compile Include="System\Net\Base64Stream.cs" /> @@ -93,6 +92,8 @@ <Compile Include="System\Net\Mail\SmtpReplyReaderFactory.cs" /> <Compile Include="System\Net\Mail\SmtpTransport.cs" /> <Compile Include="System\Net\Mail\SmtpLoginAuthenticationModule.cs" /> + <Compile Include="System\Net\Mail\SmtpNegotiateAuthenticationModule.cs" /> + <Compile Include="System\Net\Mail\SmtpNtlmAuthenticationModule.cs" /> <Compile Include="System\Net\Mail\MailWriter.cs" /> <Compile Include="System\Net\Mail\NetEventSource.Mail.cs" /> <Compile Include="$(CommonPath)System\Net\ContextAwareResult.cs" @@ -117,142 +118,16 @@ Link="Common\System\Net\SecurityProtocol.cs" /> </ItemGroup> - <!-- NT authentication specific files --> - <ItemGroup Condition="'$(TargetPlatformIdentifier)' != '' and '$(TargetPlatformIdentifier)' != 'Browser' and '$(TargetPlatformIdentifier)' != 'tvOS'"> - <Compile Include="$(CommonPath)System\Net\ContextFlagsPal.cs" - Link="Common\System\Net\ContextFlagsPal.cs" /> - <Compile Include="$(CommonPath)System\Net\NegotiationInfoClass.cs" - Link="Common\System\Net\NegotiationInfoClass.cs" /> - <Compile Include="$(CommonPath)System\Net\NTAuthentication.Common.cs" - Link="Common\System\Net\NTAuthentication.Common.cs" /> - <Compile Include="$(CommonPath)System\Net\SecurityStatusPal.cs" - Link="Common\System\Net\SecurityStatusPal.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\SafeCredentialReference.cs" - Link="Common\System\Net\Security\SafeCredentialReference.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\SSPIHandleCache.cs" - Link="Common\System\Net\Security\SSPIHandleCache.cs" /> - <Compile Include="System\Net\Mail\SmtpNegotiateAuthenticationModule.cs" /> - <Compile Include="System\Net\Mail\SmtpNtlmAuthenticationModule.cs" /> - </ItemGroup> - <!-- Unix specific files --> - <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Unix' or '$(TargetPlatformIdentifier)' == 'tvOS'"> + <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Unix'"> <Compile Include="$(CommonPath)System\Net\ContextAwareResult.Unix.cs" Link="Common\System\Net\ContextAwareResult.Unix.cs" /> </ItemGroup> - <!-- Unix specific files (NT Authentication) --> - <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Unix'"> - <Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs" - Link="Common\Interop\Unix\Interop.Libraries.cs" /> - <Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.GssBuffer.cs" - Link="Common\Interop\Unix\System.Net.Security.Native\Interop.GssBuffer.cs" /> - <Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.GssApiException.cs" - Link="Common\Interop\Unix\System.Net.Security.Native\Interop.GssApiException.cs" /> - <Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.cs" - Link="Common\Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.cs" /> - <Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.GssFlags.cs" - Link="Common\Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.GssFlags.cs" /> - <Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.Status.cs" - Link="Common\Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.Status.cs" /> - <Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.IsNtlmInstalled.cs" - Link="Common\Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.IsNtlmInstalled.cs" /> - <Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\GssSafeHandles.cs" - Link="Common\Microsoft\Win32\SafeHandles\GssSafeHandles.cs" /> - <Compile Include="$(CommonPath)System\Net\ContextFlagsAdapterPal.Unix.cs" - Link="Common\System\Net\ContextFlagsAdapterPal.Unix.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\Unix\SecChannelBindings.cs" - Link="Common\System\Net\Security\Unix\SecChannelBindings.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\Unix\SafeDeleteContext.cs" - Link="Common\System\Net\Security\Unix\SafeDeleteContext.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\Unix\SafeDeleteNegoContext.cs" - Link="Common\System\Net\Security\Unix\SafeDeleteNegoContext.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\Unix\SafeFreeCredentials.cs" - Link="Common\System\Net\Security\Unix\SafeFreeCredentials.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\Unix\SafeFreeNegoCredentials.cs" - Link="Common\System\Net\Security\Unix\SafeFreeNegoCredentials.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\NegotiateStreamPal.Unix.cs" - Link="Common\System\Net\Security\NegotiateStreamPal.Unix.cs" /> - </ItemGroup> - <!-- Windows specific files --> <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'windows'"> - <Compile Include="$(CommonPath)System\Net\Security\SecurityBuffer.Windows.cs" - Link="Common\System\Net\Security\SecurityBuffer.Windows.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\SecurityBufferType.Windows.cs" - Link="Common\System\Net\Security\SecurityBufferType.Windows.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\SecurityContextTokenHandle.cs" - Link="Common\System\Net\Security\SecurityContextTokenHandle.cs" /> <Compile Include="$(CommonPath)System\Net\ContextAwareResult.Windows.cs" Link="Common\System\Net\ContextAwareResult.Windows.cs" /> - <Compile Include="$(CommonPath)System\Net\SecurityStatusAdapterPal.Windows.cs" - Link="Common\System\Net\SecurityStatusAdapterPal.Windows.cs" /> - <Compile Include="$(CommonPath)System\Net\ContextFlagsAdapterPal.Windows.cs" - Link="Common\System\Net\ContextFlagsAdapterPal.Windows.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\NegotiateStreamPal.Windows.cs" - Link="Common\System\Net\Security\NegotiateStreamPal.Windows.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\NetEventSource.Security.Windows.cs" - Link="Common\System\Net\Security\NetEventSource.Security.Windows.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CERT_CONTEXT.cs" - Link="Common\Interop\Windows\Crypt32\Interop.CERT_CONTEXT.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertFreeCertificateContext.cs" - Link="Common\Interop\Windows\Crypt32\Interop.Interop.CertFreeCertificateContext.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CERT_INFO.cs" - Link="Common\Interop\Windows\Crypt32\Interop.CERT_INFO.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CERT_PUBLIC_KEY_INFO.cs" - Link="Common\Interop\Windows\Crypt32\Interop.CERT_PUBLIC_KEY_INFO.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CRYPT_ALGORITHM_IDENTIFIER.cs" - Link="Common\Interop\Windows\Crypt32\Interop.Interop.CRYPT_ALGORITHM_IDENTIFIER.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CRYPT_BIT_BLOB.cs" - Link="Common\Interop\Windows\Crypt32\Interop.Interop.CRYPT_BIT_BLOB.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.DATA_BLOB.cs" - Link="Common\Interop\Windows\Crypt32\Interop.DATA_BLOB.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.MsgEncodingType.cs" - Link="Common\Interop\Windows\Crypt32\Interop.Interop.MsgEncodingType.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Interop.BOOL.cs" - Link="Common\Interop\Windows\Interop.BOOL.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Interop.UNICODE_STRING.cs" - Link="Common\Interop\Windows\Interop.UNICODE_STRING.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs" - Link="Common\Interop\Windows\Interop.Libraries.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecPkgContext_Bindings.cs" - Link="Common\Interop\Windows\SspiCli\SecPkgContext_Bindings.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SChannel\Interop.SECURITY_STATUS.cs" - Link="Common\Interop\Windows\SChannel\Interop.SECURITY_STATUS.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CloseHandle.cs" - Link="Common\Interop\Windows\Kernel32\Interop.CloseHandle.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecPkgContext_StreamSizes.cs" - Link="Common\Interop\Windows\SspiCli\SecPkgContext_StreamSizes.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecPkgContext_NegotiationInfoW.cs" - Link="Common\Interop\Windows\SspiCli\SecPkgContext_NegotiationInfoW.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\NegotiationInfoClass.cs" - Link="Common\Interop\Windows\SspiCli\NegotiationInfoClass.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SChannel\SecPkgContext_ConnectionInfo.cs" - Link="Common\Interop\Windows\SChannel\SecPkgContext_ConnectionInfo.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SChannel\SecPkgContext_CipherInfo.cs" - Link="Common\Interop\Windows\SChannel\SecPkgContext_CipherInfo.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SSPISecureChannelType.cs" - Link="Common\Interop\Windows\SspiCli\SSPISecureChannelType.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\ISSPIInterface.cs" - Link="Common\Interop\Windows\SspiCli\ISSPIInterface.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SSPIAuthType.cs" - Link="Common\Interop\Windows\SspiCli\SSPIAuthType.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecurityPackageInfoClass.cs" - Link="Common\Interop\Windows\SspiCli\SecurityPackageInfoClass.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecurityPackageInfo.cs" - Link="Common\Interop\Windows\SspiCli\SecurityPackageInfo.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecPkgContext_Sizes.cs" - Link="Common\Interop\Windows\SspiCli\SecPkgContext_Sizes.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SafeDeleteContext.cs" - Link="Common\Interop\Windows\SspiCli\SafeDeleteContext.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\GlobalSSPI.cs" - Link="Common\Interop\Windows\SspiCli\GlobalSSPI.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\Interop.SSPI.cs" - Link="Common\Interop\Windows\SspiCli\Interop.SSPI.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecuritySafeHandles.cs" - Link="Common\Interop\Windows\SspiCli\SecuritySafeHandles.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SSPIWrapper.cs" - Link="Common\Interop\Windows\SspiCli\SSPIWrapper.cs" /> </ItemGroup> <ItemGroup> <Reference Include="Microsoft.Win32.Primitives" /> diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpAuthenticationManager.cs b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpAuthenticationManager.cs index 0cbe1faef60..9b60e9302e2 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpAuthenticationManager.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpAuthenticationManager.cs @@ -11,10 +11,8 @@ namespace System.Net.Mail static SmtpAuthenticationManager() { -#if !NO_NTAUTHENTICATION Register(new SmtpNegotiateAuthenticationModule()); Register(new SmtpNtlmAuthenticationModule()); -#endif Register(new SmtpLoginAuthenticationModule()); } diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpConnection.Auth.cs b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpConnection.Auth.cs index 590e84788fd..0fa1b892fd9 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpConnection.Auth.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpConnection.Auth.cs @@ -18,9 +18,7 @@ namespace System.Net.Mail #pragma warning disable CS0414 // Field is not used in test project private bool _serverSupportsStartTls; #pragma warning restore CS0414 -#if !NO_NTAUTHENTICATION private bool _sawNegotiate; -#endif private SupportedAuth _supportedAuth = SupportedAuth.None; private readonly ISmtpAuthenticationModule[] _authenticationModules; @@ -93,7 +91,6 @@ namespace System.Net.Mail return true; } } -#if !NO_NTAUTHENTICATION else if (module is SmtpNegotiateAuthenticationModule) { if ((_supportedAuth & SupportedAuth.GSSAPI) > 0) @@ -110,7 +107,6 @@ namespace System.Net.Mail return true; } } -#endif return false; } diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpNegotiateAuthenticationModule.cs b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpNegotiateAuthenticationModule.cs index faf36ea0089..ac0fa56fb9d 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpNegotiateAuthenticationModule.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpNegotiateAuthenticationModule.cs @@ -1,8 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Collections.Generic; using System.ComponentModel; +using System.Net.Security; using System.Security.Authentication.ExtendedProtection; namespace System.Net.Mail @@ -10,7 +12,7 @@ namespace System.Net.Mail internal sealed class SmtpNegotiateAuthenticationModule : ISmtpAuthenticationModule { private static byte[] _saslNoSecurtyLayerToken = new byte[] { 1, 0, 0, 0 }; - private readonly Dictionary<object, NTAuthentication> _sessions = new Dictionary<object, NTAuthentication>(); + private readonly Dictionary<object, NegotiateAuthentication> _sessions = new Dictionary<object, NegotiateAuthentication>(); internal SmtpNegotiateAuthenticationModule() { @@ -18,75 +20,65 @@ namespace System.Net.Mail public Authorization? Authenticate(string? challenge, NetworkCredential? credential, object sessionCookie, string? spn, ChannelBinding? channelBindingToken) { - try + lock (_sessions) { - lock (_sessions) + NegotiateAuthentication? clientContext; + if (!_sessions.TryGetValue(sessionCookie, out clientContext)) { - NTAuthentication? clientContext; - if (!_sessions.TryGetValue(sessionCookie, out clientContext)) + if (credential == null) { - if (credential == null) - { - return null; - } - - ContextFlagsPal contextFlags = ContextFlagsPal.Connection | ContextFlagsPal.InitIntegrity; - // Workaround for https://github.com/gssapi/gss-ntlmssp/issues/77 - // GSSAPI NTLM SSP does not support gss_wrap/gss_unwrap unless confidentiality - // is negotiated. - if (OperatingSystem.IsLinux()) - { - contextFlags |= ContextFlagsPal.Confidentiality; - } - - _sessions[sessionCookie] = - clientContext = - new NTAuthentication(false, "Negotiate", credential, spn, - contextFlags, channelBindingToken); + return null; } - byte[]? byteResp; - string? resp = null; - - if (!clientContext.IsCompleted) + ProtectionLevel protectionLevel = ProtectionLevel.Sign; + // Workaround for https://github.com/gssapi/gss-ntlmssp/issues/77 + // GSSAPI NTLM SSP does not support gss_wrap/gss_unwrap unless confidentiality + // is negotiated. + if (OperatingSystem.IsLinux()) { + protectionLevel = ProtectionLevel.EncryptAndSign; + } + + _sessions[sessionCookie] = clientContext = + new NegotiateAuthentication( + new NegotiateAuthenticationClientOptions + { + Credential = credential, + TargetName = spn, + RequiredProtectionLevel = protectionLevel, + Binding = channelBindingToken + }); + } - // If auth is not yet completed keep producing - // challenge responses with GetOutgoingBlob - - byte[]? decodedChallenge = null; - if (challenge != null) - { - decodedChallenge = - Convert.FromBase64String(challenge); - } - byteResp = clientContext.GetOutgoingBlob(decodedChallenge, false); - if (clientContext.IsCompleted && byteResp == null) - { - resp = "\r\n"; - } - if (byteResp != null) - { - resp = Convert.ToBase64String(byteResp); - } + string? resp = null; + NegotiateAuthenticationStatusCode statusCode; + + if (!clientContext.IsAuthenticated) + { + // If auth is not yet completed keep producing + // challenge responses with GetOutgoingBlob + resp = clientContext.GetOutgoingBlob(challenge, out statusCode); + if (statusCode != NegotiateAuthenticationStatusCode.Completed && + statusCode != NegotiateAuthenticationStatusCode.ContinueNeeded) + { + return null; } - else + if (clientContext.IsAuthenticated && resp == null) { - // If auth completed and still have a challenge then - // server may be doing "correct" form of GSSAPI SASL. - // Validate incoming and produce outgoing SASL security - // layer negotiate message. - - resp = GetSecurityLayerOutgoingBlob(challenge, clientContext); + resp = "\r\n"; } + } + else + { + // If auth completed and still have a challenge then + // server may be doing "correct" form of GSSAPI SASL. + // Validate incoming and produce outgoing SASL security + // layer negotiate message. - return new Authorization(resp, clientContext.IsCompleted); + resp = GetSecurityLayerOutgoingBlob(challenge, clientContext); } - } - // From reflected type NTAuthentication in System.Net.Security. - catch (NullReferenceException) - { - return null; + + return new Authorization(resp, clientContext.IsAuthenticated); } } @@ -100,7 +92,7 @@ namespace System.Net.Mail public void CloseContext(object sessionCookie) { - NTAuthentication? clientContext = null; + NegotiateAuthentication? clientContext = null; lock (_sessions) { if (_sessions.TryGetValue(sessionCookie, out clientContext)) @@ -108,7 +100,7 @@ namespace System.Net.Mail _sessions.Remove(sessionCookie); } } - clientContext?.CloseContext(); + clientContext?.Dispose(); } // Function for SASL security layer negotiation after @@ -116,7 +108,7 @@ namespace System.Net.Mail // // Returns null for failure, Base64 encoded string on // success. - private static string? GetSecurityLayerOutgoingBlob(string? challenge, NTAuthentication clientContext) + private static string? GetSecurityLayerOutgoingBlob(string? challenge, NegotiateAuthentication clientContext) { // must have a security layer challenge @@ -127,20 +119,15 @@ namespace System.Net.Mail byte[] input = Convert.FromBase64String(challenge); - int len; - int newOffset; Span<byte> unwrappedChallenge; + NegotiateAuthenticationStatusCode statusCode; - try + statusCode = clientContext.UnwrapInPlace(input, out int newOffset, out int newLength, out _); + if (statusCode != NegotiateAuthenticationStatusCode.Completed) { - len = clientContext.Unwrap(input, out newOffset, out _); - unwrappedChallenge = input.AsSpan(newOffset, len); - } - catch (Win32Exception) - { - // any decrypt failure is an auth failure return null; } + unwrappedChallenge = input.AsSpan(newOffset, newLength); // Per RFC 2222 Section 7.2.2: // the client should then expect the server to issue a @@ -180,19 +167,15 @@ namespace System.Net.Mail // So now this contructs the "wrapped" response. // let MakeSignature figure out length of output - byte[]? output = null; - try - { - len = clientContext.Wrap(_saslNoSecurtyLayerToken, ref output, false); - } - catch (Win32Exception) + ArrayBufferWriter<byte> outputWriter = new ArrayBufferWriter<byte>(); + statusCode = clientContext.Wrap(_saslNoSecurtyLayerToken, outputWriter, false, out _); + if (statusCode != NegotiateAuthenticationStatusCode.Completed) { - // any encrypt failure is an auth failure return null; } // return Base64 encoded string of signed payload - return Convert.ToBase64String(output, 0, len); + return Convert.ToBase64String(outputWriter.WrittenSpan); } } } diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpNtlmAuthenticationModule.cs b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpNtlmAuthenticationModule.cs index d5557afbe3f..b4696a9a4ba 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpNtlmAuthenticationModule.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpNtlmAuthenticationModule.cs @@ -2,13 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Net.Security; using System.Security.Authentication.ExtendedProtection; namespace System.Net.Mail { internal sealed class SmtpNtlmAuthenticationModule : ISmtpAuthenticationModule { - private readonly Dictionary<object, NTAuthentication> _sessions = new Dictionary<object, NTAuthentication>(); + private readonly Dictionary<object, NegotiateAuthentication> _sessions = new Dictionary<object, NegotiateAuthentication>(); internal SmtpNtlmAuthenticationModule() { @@ -16,41 +17,45 @@ namespace System.Net.Mail public Authorization? Authenticate(string? challenge, NetworkCredential? credential, object sessionCookie, string? spn, ChannelBinding? channelBindingToken) { - try + lock (_sessions) { - lock (_sessions) + NegotiateAuthentication? clientContext; + if (!_sessions.TryGetValue(sessionCookie, out clientContext)) { - NTAuthentication? clientContext; - if (!_sessions.TryGetValue(sessionCookie, out clientContext)) + if (credential == null) { - if (credential == null) - { - return null; - } + return null; + } - _sessions[sessionCookie] = - clientContext = - new NTAuthentication(false, "Ntlm", credential, spn, ContextFlagsPal.Connection, channelBindingToken); + _sessions[sessionCookie] = clientContext = + new NegotiateAuthentication( + new NegotiateAuthenticationClientOptions + { + Credential = credential, + TargetName = spn, + Binding = channelBindingToken + }); + } - } + NegotiateAuthenticationStatusCode statusCode; + string? resp = clientContext.GetOutgoingBlob(challenge, out statusCode); - string? resp = clientContext.GetOutgoingBlob(challenge); + if (statusCode != NegotiateAuthenticationStatusCode.Completed && + statusCode != NegotiateAuthenticationStatusCode.ContinueNeeded) + { + return null; + } - if (!clientContext.IsCompleted) - { - return new Authorization(resp, false); - } - else - { - _sessions.Remove(sessionCookie); - return new Authorization(resp, true); - } + if (!clientContext.IsAuthenticated) + { + return new Authorization(resp, false); + } + else + { + _sessions.Remove(sessionCookie); + clientContext.Dispose(); + return new Authorization(resp, true); } - } - // From reflected type NTAuthentication in System.Net.Security. - catch (NullReferenceException) - { - return null; } } diff --git a/src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj b/src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj index 890853df320..b203a1c6d10 100644 --- a/src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj +++ b/src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj @@ -150,117 +150,13 @@ <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Unix'"> <Compile Include="$(CommonPath)System\Net\ContextAwareResult.Unix.cs" Link="Common\System\Net\ContextAwareResult.Unix.cs" /> - <Compile Include="$(CommonPath)System\Net\ContextFlagsAdapterPal.Unix.cs" - Link="Common\System\Net\ContextFlagsAdapterPal.Unix.cs" /> <Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs" Link="Common\Interop\Unix\Interop.Libraries.cs" /> - <Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.GssBuffer.cs" - Link="Common\Interop\Unix\System.Net.Security.Native\Interop.GssBuffer.cs" /> - <Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.GssApiException.cs" - Link="Common\Interop\Unix\System.Net.Security.Native\Interop.GssApiException.cs" /> - <Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.cs" - Link="Common\Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.cs" /> - <Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.GssFlags.cs" - Link="Common\Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.GssFlags.cs" /> - <Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.IsNtlmInstalled.cs" - Link="Common\Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.IsNtlmInstalled.cs" /> - <Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.Status.cs" - Link="Common\Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.Status.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\Unix\SafeDeleteNegoContext.cs" - Link="Common\System\Net\Security\Unix\SafeDeleteNegoContext.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\Unix\SafeFreeCredentials.cs" - Link="Common\System\Net\Security\Unix\SafeFreeCredentials.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\Unix\SafeDeleteContext.cs" - Link="Common\System\Net\Security\Unix\SafeDeleteContext.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\NegotiateStreamPal.Unix.cs" - Link="Common\System\Net\Security\NegotiateStreamPal.Unix.cs" /> - <Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\GssSafeHandles.cs" - Link="Common\Microsoft\Win32\SafeHandles\GssSafeHandles.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\Unix\SafeFreeNegoCredentials.cs" - Link="Common\System\Net\Security\Unix\SafeFreeNegoCredentials.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\Unix\SecChannelBindings.cs" - Link="Common\System\Net\Security\Unix\SecChannelBindings.cs" /> </ItemGroup> <!-- Windows specific files --> <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'windows'"> - <Compile Include="$(CommonPath)Interop\Windows\Interop.BOOL.cs" - Link="Common\Interop\Windows\Interop.BOOL.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SChannel\Interop.SecPkgContext_ApplicationProtocol.cs" - Link="Common\Interop\Windows\SChannel\Interop.SecPkgContext_ApplicationProtocol.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\SecurityBuffer.Windows.cs" - Link="Common\System\Net\Security\SecurityBuffer.Windows.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\SecurityBufferType.Windows.cs" - Link="Common\System\Net\Security\SecurityBufferType.Windows.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\SecurityContextTokenHandle.cs" - Link="Common\System\Net\Security\SecurityContextTokenHandle.cs" /> <Compile Include="$(CommonPath)System\Net\ContextAwareResult.Windows.cs" Link="Common\System\Net\ContextAwareResult.Windows.cs" /> - <Compile Include="$(CommonPath)System\Net\SecurityStatusAdapterPal.Windows.cs" - Link="Common\System\Net\SecurityStatusAdapterPal.Windows.cs" /> - <Compile Include="$(CommonPath)System\Net\ContextFlagsAdapterPal.Windows.cs" - Link="Common\System\Net\ContextFlagsAdapterPal.Windows.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\NegotiateStreamPal.Windows.cs" - Link="Common\System\Net\Security\NegotiateStreamPal.Windows.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\NetEventSource.Security.Windows.cs" - Link="Common\System\Net\Security\NetEventSource.Security.Windows.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CERT_CONTEXT.cs" - Link="Common\Interop\Windows\Crypt32\Interop.CERT_CONTEXT.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CERT_INFO.cs" - Link="Common\Interop\Windows\Crypt32\Interop.CERT_INFO.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CERT_PUBLIC_KEY_INFO.cs" - Link="Common\Interop\Windows\Crypt32\Interop.CERT_PUBLIC_KEY_INFO.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CRYPT_ALGORITHM_IDENTIFIER.cs" - Link="Common\Interop\Windows\Crypt32\Interop.Interop.CRYPT_ALGORITHM_IDENTIFIER.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CRYPT_BIT_BLOB.cs" - Link="Common\Interop\Windows\Crypt32\Interop.Interop.CRYPT_BIT_BLOB.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.DATA_BLOB.cs" - Link="Common\Interop\Windows\Crypt32\Interop.DATA_BLOB.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.MsgEncodingType.cs" - Link="Common\Interop\Windows\Crypt32\Interop.Interop.MsgEncodingType.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertFreeCertificateContext.cs" - Link="Common\Interop\Windows\Crypt32\Interop.Interop.CertFreeCertificateContext.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs" - Link="Common\Interop\Windows\Interop.Libraries.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecPkgContext_Bindings.cs" - Link="Common\Interop\Windows\SspiCli\SecPkgContext_Bindings.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SChannel\Interop.SECURITY_STATUS.cs" - Link="Common\Interop\Windows\SChannel\Interop.SECURITY_STATUS.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CloseHandle.cs" - Link="Common\Interop\Windows\Kernel32\Interop.CloseHandle.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecPkgContext_StreamSizes.cs" - Link="Common\Interop\Windows\SspiCli\SecPkgContext_StreamSizes.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecPkgContext_NegotiationInfoW.cs" - Link="Common\Interop\Windows\SspiCli\SecPkgContext_NegotiationInfoW.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\NegotiationInfoClass.cs" - Link="Common\Interop\Windows\SspiCli\NegotiationInfoClass.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SChannel\SecPkgContext_ConnectionInfo.cs" - Link="Common\Interop\Windows\SChannel\SecPkgContext_ConnectionInfo.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SChannel\SecPkgContext_CipherInfo.cs" - Link="Common\Interop\Windows\SChannel\SecPkgContext_CipherInfo.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SSPISecureChannelType.cs" - Link="Common\Interop\Windows\SspiCli\SSPISecureChannelType.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\ISSPIInterface.cs" - Link="Common\Interop\Windows\SspiCli\ISSPIInterface.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SSPIAuthType.cs" - Link="Common\Interop\Windows\SspiCli\SSPIAuthType.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecurityPackageInfoClass.cs" - Link="Common\Interop\Windows\SspiCli\SecurityPackageInfoClass.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecurityPackageInfo.cs" - Link="Common\Interop\Windows\SspiCli\SecurityPackageInfo.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecPkgContext_Sizes.cs" - Link="Common\Interop\Windows\SspiCli\SecPkgContext_Sizes.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SafeDeleteContext.cs" - Link="Common\Interop\Windows\SspiCli\SafeDeleteContext.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\GlobalSSPI.cs" - Link="Common\Interop\Windows\SspiCli\GlobalSSPI.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\Interop.SSPI.cs" - Link="Common\Interop\Windows\SspiCli\Interop.SSPI.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SecuritySafeHandles.cs" - Link="Common\Interop\Windows\SspiCli\SecuritySafeHandles.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\SspiCli\SSPIWrapper.cs" - Link="Common\Interop\Windows\SspiCli\SSPIWrapper.cs" /> - <Compile Include="$(CommonPath)Interop\Windows\Interop.UNICODE_STRING.cs" - Link="Common\Interop\Windows\Interop.UNICODE_STRING.cs" /> </ItemGroup> <!-- Non Browser specific files - internal and security --> @@ -271,12 +167,6 @@ Link="ProductionCode\SmtpNegotiateAuthenticationModule.cs" /> <Compile Include="..\..\src\System\Net\Mail\SmtpNtlmAuthenticationModule.cs" Link="ProductionCode\SmtpNtlmAuthenticationModule.cs" /> - <Compile Include="$(CommonPath)System\Net\NTAuthentication.Common.cs" - Link="Common\System\Net\NTAuthentication.Common.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\SSPIHandleCache.cs" - Link="Common\System\Net\Security\SSPIHandleCache.cs" /> - <Compile Include="$(CommonPath)System\Net\Security\SafeCredentialReference.cs" - Link="Common\System\Net\Security\SafeCredentialReference.cs" /> <Compile Include="$(CommonPath)System\Net\ContextAwareResult.cs" Link="Common\System\Net\ContextAwareResult.cs" /> <Compile Include="..\..\src\System\Net\Mail\SmtpConnection.Auth.cs" 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 a5a9f3083a6..e9a813ec175 100644 --- a/src/libraries/System.Net.Security/ref/System.Net.Security.cs +++ b/src/libraries/System.Net.Security/ref/System.Net.Security.cs @@ -39,6 +39,7 @@ namespace System.Net.Security { public NegotiateAuthentication(System.Net.Security.NegotiateAuthenticationClientOptions clientOptions) { } public NegotiateAuthentication(System.Net.Security.NegotiateAuthenticationServerOptions serverOptions) { } + public System.Security.Principal.TokenImpersonationLevel ImpersonationLevel { get { throw null; } } public bool IsAuthenticated { get { throw null; } } public bool IsEncrypted { get { throw null; } } public bool IsMutuallyAuthenticated { get { throw null; } } @@ -51,14 +52,19 @@ namespace System.Net.Security public void Dispose() { } public byte[]? GetOutgoingBlob(System.ReadOnlySpan<byte> incomingBlob, out System.Net.Security.NegotiateAuthenticationStatusCode statusCode) { throw null; } public string? GetOutgoingBlob(string? incomingBlob, out System.Net.Security.NegotiateAuthenticationStatusCode statusCode) { throw null; } + public System.Net.Security.NegotiateAuthenticationStatusCode Wrap(System.ReadOnlySpan<byte> input, System.Buffers.IBufferWriter<byte> outputWriter, bool requestEncryption, out bool isEncrypted) { throw null; } + public System.Net.Security.NegotiateAuthenticationStatusCode Unwrap(System.ReadOnlySpan<byte> input, System.Buffers.IBufferWriter<byte> outputWriter, out bool wasEncrypted) { throw null; } + public System.Net.Security.NegotiateAuthenticationStatusCode UnwrapInPlace(System.Span<byte> input, out int unwrappedOffset, out int unwrappedLength, out bool wasEncrypted) { throw null; } } public partial class NegotiateAuthenticationClientOptions { public NegotiateAuthenticationClientOptions() { } + public System.Security.Principal.TokenImpersonationLevel AllowedImpersonationLevel { get { throw null; } set { } } 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 bool RequireMutualAuthentication { get { throw null; } set { } } public string? TargetName { get { throw null; } set { } } } public partial class NegotiateAuthenticationServerOptions @@ -67,6 +73,8 @@ namespace System.Net.Security 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.Security.Authentication.ExtendedProtection.ExtendedProtectionPolicy? Policy { get { throw null; } set { } } + public System.Security.Principal.TokenImpersonationLevel RequiredImpersonationLevel { get { throw null; } set { } } public System.Net.Security.ProtectionLevel RequiredProtectionLevel { get { throw null; } set { } } } public enum NegotiateAuthenticationStatusCode @@ -84,6 +92,9 @@ namespace System.Net.Security UnknownCredentials = 10, QopNotSupported = 11, OutOfSequence = 12, + SecurityQosFailed = 13, + TargetUnknown = 14, + ImpersonationValidationFailed = 15, } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] public partial class NegotiateStream : System.Net.Security.AuthenticatedStream diff --git a/src/libraries/System.Net.Security/ref/System.Net.Security.csproj b/src/libraries/System.Net.Security/ref/System.Net.Security.csproj index 625075c1b00..0eb7f5d81fe 100644 --- a/src/libraries/System.Net.Security/ref/System.Net.Security.csproj +++ b/src/libraries/System.Net.Security/ref/System.Net.Security.csproj @@ -10,6 +10,7 @@ <ItemGroup> <ProjectReference Include="$(LibrariesProjectRoot)System.Collections.NonGeneric\ref\System.Collections.NonGeneric.csproj" /> <ProjectReference Include="$(LibrariesProjectRoot)System.Collections\ref\System.Collections.csproj" /> + <ProjectReference Include="$(LibrariesProjectRoot)System.Memory\ref\System.Memory.csproj" /> <ProjectReference Include="$(LibrariesProjectRoot)System.Net.Primitives\ref\System.Net.Primitives.csproj" /> <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime\ref\System.Runtime.csproj" /> <ProjectReference Include="$(LibrariesProjectRoot)System.Security.Cryptography\ref\System.Security.Cryptography.csproj" /> diff --git a/src/libraries/System.Net.Security/src/Resources/Strings.resx b/src/libraries/System.Net.Security/src/Resources/Strings.resx index 2e4075f7d5e..c808bebfe2e 100644 --- a/src/libraries/System.Net.Security/src/Resources/Strings.resx +++ b/src/libraries/System.Net.Security/src/Resources/Strings.resx @@ -164,6 +164,51 @@ <data name="net_io_eof" xml:space="preserve"> <value> Received an unexpected EOF or 0 bytes from the transport stream.</value> </data> + <data name="net_log_listener_no_cbt_disabled" xml:space="preserve"> + <value>No channel binding check because extended protection is disabled.</value> + </data> + <data name="net_log_listener_no_cbt_http" xml:space="preserve"> + <value>No channel binding check for requests without a secure channel.</value> + </data> + <data name="net_log_listener_no_cbt_trustedproxy" xml:space="preserve"> + <value>No channel binding check for the trusted proxy scenario.</value> + </data> + <data name="net_log_listener_cbt" xml:space="preserve"> + <value>Channel binding check enabled.</value> + </data> + <data name="net_log_listener_no_spns" xml:space="preserve"> + <value>No service names could be determined from the registered prefixes. Specify an ExtendedProtectionPolicy object which contains an explicit list of service names.</value> + </data> + <data name="net_log_listener_no_spn_kerberos" xml:space="preserve"> + <value>No explicit service name check because Kerberos authentication already validates the service name.</value> + </data> + <data name="net_log_listener_no_spn_disabled" xml:space="preserve"> + <value>No service name check because extended protection is disabled.</value> + </data> + <data name="net_log_listener_no_spn_cbt" xml:space="preserve"> + <value>No service name check because the channel binding was already checked.</value> + </data> + <data name="net_log_listener_no_spn_whensupported" xml:space="preserve"> + <value>No service name check because the client did not provide a service name and the server was configured for PolicyEnforcement.WhenSupported.</value> + </data> + <data name="net_log_listener_spn" xml:space="preserve"> + <value>Client provided service name '{0}'.</value> + </data> + <data name="net_log_listener_spn_passed" xml:space="preserve"> + <value>Service name check succeeded.</value> + </data> + <data name="net_log_listener_spn_failed" xml:space="preserve"> + <value>Service name check failed.</value> + </data> + <data name="net_log_listener_spn_failed_always" xml:space="preserve"> + <value>Service name check failed because the client did not provide a service name and the server was configured for PolicyEnforcement.Always.</value> + </data> + <data name="net_log_listener_spn_failed_empty" xml:space="preserve"> + <value>No acceptable service names were configured!</value> + </data> + <data name="net_log_listener_spn_failed_dump" xml:space="preserve"> + <value>Dumping acceptable service names:</value> + </data> <data name="net_ssl_io_frame" xml:space="preserve"> <value>The handshake failed due to an unexpected packet format.</value> </data> 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 1592c94ea65..e03b7a509f8 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,9 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.ComponentModel; using System.Diagnostics; using System.Security.Principal; +using System.Security.Authentication.ExtendedProtection; namespace System.Net.Security { @@ -17,6 +19,10 @@ namespace System.Net.Security private readonly string _requestedPackage; private readonly bool _isServer; private IIdentity? _remoteIdentity; + private TokenImpersonationLevel _requiredImpersonationLevel; + private ProtectionLevel _requiredProtectionLevel; + private ExtendedProtectionPolicy? _extendedProtectionPolicy; + private bool _isSecureConnection; /// <summary> /// Initializes a new instance of the <see cref="NegotiateAuthentication"/> @@ -27,15 +33,28 @@ namespace System.Net.Security { ArgumentNullException.ThrowIfNull(clientOptions); - ContextFlagsPal contextFlags = clientOptions.RequiredProtectionLevel switch + ContextFlagsPal contextFlags = ContextFlagsPal.Connection; + + contextFlags |= clientOptions.RequiredProtectionLevel switch { ProtectionLevel.Sign => ContextFlagsPal.InitIntegrity, ProtectionLevel.EncryptAndSign => ContextFlagsPal.InitIntegrity | ContextFlagsPal.Confidentiality, _ => 0 - } | ContextFlagsPal.Connection; + }; + + contextFlags |= clientOptions.RequireMutualAuthentication ? ContextFlagsPal.MutualAuth : 0; + + contextFlags |= clientOptions.AllowedImpersonationLevel switch + { + TokenImpersonationLevel.Identification => ContextFlagsPal.InitIdentify, + TokenImpersonationLevel.Delegation => ContextFlagsPal.Delegate, + _ => 0 + }; _isServer = false; _requestedPackage = clientOptions.Package; + _requiredImpersonationLevel = TokenImpersonationLevel.None; + _requiredProtectionLevel = clientOptions.RequiredProtectionLevel; try { _ntAuthentication = new NTAuthentication( @@ -73,8 +92,26 @@ namespace System.Net.Security _ => 0 } | ContextFlagsPal.Connection; + if (serverOptions.Policy is not null) + { + if (serverOptions.Policy.PolicyEnforcement == PolicyEnforcement.WhenSupported) + { + contextFlags |= ContextFlagsPal.AllowMissingBindings; + } + + if (serverOptions.Policy.PolicyEnforcement != PolicyEnforcement.Never && + serverOptions.Policy.ProtectionScenario == ProtectionScenario.TrustedProxy) + { + contextFlags |= ContextFlagsPal.ProxyBindings; + } + } + _isServer = true; _requestedPackage = serverOptions.Package; + _requiredImpersonationLevel = serverOptions.RequiredImpersonationLevel; + _requiredProtectionLevel = serverOptions.RequiredProtectionLevel; + _extendedProtectionPolicy = serverOptions.Policy; + _isSecureConnection = serverOptions.Binding != null; try { _ntAuthentication = new NTAuthentication( @@ -215,6 +252,22 @@ namespace System.Net.Security } /// <summary> + /// One of the <see cref="TokenImpersonationLevel" /> values, indicating the negotiated + /// level of impresonation. + /// </summary> + public System.Security.Principal.TokenImpersonationLevel ImpersonationLevel + { + get + { + // We should suppress the delegate flag in NTLM case. + return + _ntAuthentication!.IsDelegationFlag && _ntAuthentication.ProtocolName != NegotiationInfoClass.NTLM ? TokenImpersonationLevel.Delegation : + _ntAuthentication.IsIdentifyFlag ? TokenImpersonationLevel.Identification : + TokenImpersonationLevel.Impersonation; + } + } + + /// <summary> /// Evaluates an authentication token sent by the other party and returns a token in response. /// </summary> /// <param name="incomingBlob">Incoming authentication token, or empty value when initiating the authentication exchange.</param> @@ -285,6 +338,23 @@ namespace System.Net.Security _ => NegotiateAuthenticationStatusCode.GenericFailure, }; + // Additional policy validation + if (statusCode == NegotiateAuthenticationStatusCode.Completed) + { + if (IsServer && _extendedProtectionPolicy != null && !CheckSpn()) + { + statusCode = NegotiateAuthenticationStatusCode.TargetUnknown; + } + else if (_requiredImpersonationLevel != TokenImpersonationLevel.None && ImpersonationLevel < _requiredImpersonationLevel) + { + statusCode = NegotiateAuthenticationStatusCode.ImpersonationValidationFailed; + } + else if (_requiredProtectionLevel != ProtectionLevel.None && ProtectionLevel < _requiredProtectionLevel) + { + statusCode = NegotiateAuthenticationStatusCode.SecurityQosFailed; + } + } + return blob; } @@ -322,5 +392,164 @@ namespace System.Net.Security return outgoingBlob; } + + /// <summary> + /// Wrap an input message with signature and optionally with an encryption. + /// </summary> + /// <param name="input">Input message to be wrapped.</param> + /// <param name="outputWriter">Buffer writter where the wrapped message is written.</param> + /// <param name="requestEncryption">Specifies whether encryption is requested.</param> + /// <param name="isEncrypted">Specifies whether encryption was applied in the wrapping.</param> + /// <returns> + /// <see cref="NegotiateAuthenticationStatusCode.Completed" /> on success, other + /// <see cref="NegotiateAuthenticationStatusCode" /> values on failure. + /// </returns> + /// <remarks> + /// Like the <see href="https://datatracker.ietf.org/doc/html/rfc2743#page-65">GSS_Wrap</see> API + /// the authentication protocol implementation may choose to override the requested value in the + /// requestEncryption parameter. This may result in either downgrade or upgrade of the protection + /// level. + /// </remarks> + /// <exception cref="InvalidOperationException">Authentication failed or has not occurred.</exception> + public NegotiateAuthenticationStatusCode Wrap(ReadOnlySpan<byte> input, IBufferWriter<byte> outputWriter, bool requestEncryption, out bool isEncrypted) + { + if (!IsAuthenticated || _ntAuthentication == null) + { + throw new InvalidOperationException(SR.net_auth_noauth); + } + + return _ntAuthentication.Wrap(input, outputWriter, requestEncryption, out isEncrypted); + } + + /// <summary> + /// Unwrap an input message with signature or encryption applied by the other party. + /// </summary> + /// <param name="input">Input message to be unwrapped.</param> + /// <param name="outputWriter">Buffer writter where the unwrapped message is written.</param> + /// <param name="wasEncrypted"> + /// On output specifies whether the wrapped message had encryption applied. + /// </param> + /// <returns> + /// <see cref="NegotiateAuthenticationStatusCode.Completed" /> on success. + /// <see cref="NegotiateAuthenticationStatusCode.MessageAltered" /> if the message signature was + /// invalid. + /// <see cref="NegotiateAuthenticationStatusCode.InvalidToken" /> if the wrapped message was + /// in invalid format. + /// Other <see cref="NegotiateAuthenticationStatusCode" /> values on failure. + /// </returns> + /// <exception cref="InvalidOperationException">Authentication failed or has not occurred.</exception> + public NegotiateAuthenticationStatusCode Unwrap(ReadOnlySpan<byte> input, IBufferWriter<byte> outputWriter, out bool wasEncrypted) + { + if (!IsAuthenticated || _ntAuthentication == null) + { + throw new InvalidOperationException(SR.net_auth_noauth); + } + + return _ntAuthentication.Unwrap(input, outputWriter, out wasEncrypted); + } + + /// <summary> + /// Unwrap an input message with signature or encryption applied by the other party. + /// </summary> + /// <param name="input">Input message to be unwrapped. On output contains the decoded data.</param> + /// <param name="unwrappedOffset">Offset in the input buffer where the unwrapped message was written.</param> + /// <param name="unwrappedLength">Length of the unwrapped message.</param> + /// <param name="wasEncrypted"> + /// On output specifies whether the wrapped message had encryption applied. + /// </param> + /// <returns> + /// <see cref="NegotiateAuthenticationStatusCode.Completed" /> on success. + /// <see cref="NegotiateAuthenticationStatusCode.MessageAltered" /> if the message signature was + /// invalid. + /// <see cref="NegotiateAuthenticationStatusCode.InvalidToken" /> if the wrapped message was + /// in invalid format. + /// Other <see cref="NegotiateAuthenticationStatusCode" /> values on failure. + /// </returns> + /// <exception cref="InvalidOperationException">Authentication failed or has not occurred.</exception> + public NegotiateAuthenticationStatusCode UnwrapInPlace(Span<byte> input, out int unwrappedOffset, out int unwrappedLength, out bool wasEncrypted) + { + if (!IsAuthenticated || _ntAuthentication == null) + { + throw new InvalidOperationException(SR.net_auth_noauth); + } + + return _ntAuthentication.UnwrapInPlace(input, out unwrappedOffset, out unwrappedLength, out wasEncrypted); + } + + private bool CheckSpn() + { + Debug.Assert(_ntAuthentication != null); + Debug.Assert(_extendedProtectionPolicy != null); + + if (_ntAuthentication.IsKerberos) + { + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, SR.net_log_listener_no_spn_kerberos); + return true; + } + + if (_extendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.Never) + { + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, SR.net_log_listener_no_spn_disabled); + return true; + } + + if (_isSecureConnection && _extendedProtectionPolicy.ProtectionScenario == ProtectionScenario.TransportSelected) + { + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, SR.net_log_listener_no_spn_cbt); + return true; + } + + if (_extendedProtectionPolicy.CustomServiceNames == null) + { + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, SR.net_log_listener_no_spns); + return true; + } + + string? clientSpn = _ntAuthentication.ClientSpecifiedSpn; + + if (string.IsNullOrEmpty(clientSpn)) + { + if (_extendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.WhenSupported) + { + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, SR.net_log_listener_no_spn_whensupported); + return true; + } + else + { + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, SR.net_log_listener_spn_failed_always); + return false; + } + } + + if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, SR.net_log_listener_spn, clientSpn); + bool found = _extendedProtectionPolicy.CustomServiceNames.Contains(clientSpn); + + if (NetEventSource.Log.IsEnabled()) + { + if (found) + { + NetEventSource.Info(this, SR.net_log_listener_spn_passed); + } + else + { + NetEventSource.Info(this, SR.net_log_listener_spn_failed); + + if (_extendedProtectionPolicy.CustomServiceNames.Count == 0) + { + NetEventSource.Info(this, SR.net_log_listener_spn_failed_empty); + } + else + { + NetEventSource.Info(this, SR.net_log_listener_spn_failed_dump); + foreach (string serviceName in _extendedProtectionPolicy.CustomServiceNames) + { + NetEventSource.Info(this, "\t" + serviceName); + } + } + } + } + + return found; + } } } 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 e1c17698d8d..90f5615c986 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 @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Security.Authentication.ExtendedProtection; +using System.Security.Principal; namespace System.Net.Security { @@ -37,5 +38,16 @@ namespace System.Net.Security /// and any further data exchange. Default value is None. /// </summary> public ProtectionLevel RequiredProtectionLevel { get; set; } = ProtectionLevel.None; + + /// <summary> + /// Indicates that mutual authentication is required between the client and server. + /// </summary> + public bool RequireMutualAuthentication { get; set; } + + /// <summary> + /// One of the <see cref="TokenImpersonationLevel" /> values, indicating how the server + /// can use the client's credentials to access resources. + /// </summary> + public TokenImpersonationLevel AllowedImpersonationLevel { get; set; } = TokenImpersonationLevel.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 6cb49193558..7ba6737ebb6 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 @@ -1,7 +1,9 @@ // 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; using System.Security.Authentication.ExtendedProtection; +using System.Security.Principal; namespace System.Net.Security { @@ -32,5 +34,16 @@ namespace System.Net.Security /// and any further data exchange. Default value is None. /// </summary> public ProtectionLevel RequiredProtectionLevel { get; set; } = ProtectionLevel.None; + + /// <summary> + /// Indicates extended security and validation policies. + /// </summary> + public ExtendedProtectionPolicy? Policy { get; set; } + + /// <summary> + /// One of the <see cref="TokenImpersonationLevel" /> values, indicating how the server + /// can use the client's credentials to access resources. + /// </summary> + public TokenImpersonationLevel RequiredImpersonationLevel { get; set; } = TokenImpersonationLevel.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 index 62f8227932b..0704b390bd2 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 @@ -58,6 +58,15 @@ namespace System.Net.Security /// <summary>Authentication token was identfied as duplicate, old, or out of expected sequence.</summary> /// <remarks>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.</remarks> - OutOfSequence + OutOfSequence, + + /// <status>Validation of RequiredProtectionLevel against negotiated protection level failed.</status> + SecurityQosFailed, + + /// <status>Validation of the target name failed</status> + TargetUnknown, + + /// <status>Validation of the impersonation level failed</status> + ImpersonationValidationFailed, } } diff --git a/src/libraries/System.Net.Security/tests/UnitTests/NTAuthenticationTests.cs b/src/libraries/System.Net.Security/tests/UnitTests/NTAuthenticationTests.cs deleted file mode 100644 index da1e4d23e6d..00000000000 --- a/src/libraries/System.Net.Security/tests/UnitTests/NTAuthenticationTests.cs +++ /dev/null @@ -1,65 +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.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 NTAuthenticationTests - { - private static bool IsNtlmInstalled => Capability.IsNtlmInstalled(); - - private static NetworkCredential s_testCredentialRight = new NetworkCredential("rightusername", "rightpassword"); - private static readonly byte[] s_Hello = "Hello"u8.ToArray(); - - [ConditionalFact(nameof(IsNtlmInstalled))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/65678", TestPlatforms.OSX | TestPlatforms.iOS | TestPlatforms.MacCatalyst)] - public void NtlmSignatureTest() - { - FakeNtlmServer fakeNtlmServer = new FakeNtlmServer(s_testCredentialRight); - NTAuthentication ntAuth = new NTAuthentication( - isServer: false, "NTLM", s_testCredentialRight, "HTTP/foo", - ContextFlagsPal.Connection | ContextFlagsPal.InitIntegrity | ContextFlagsPal.Confidentiality, null); - - DoNtlmExchange(fakeNtlmServer, ntAuth); - - Assert.True(fakeNtlmServer.IsAuthenticated); - - // Test MakeSignature on client side and decoding it on server side - byte[]? output = null; - int len = ntAuth.Wrap(s_Hello, ref output, true); - Assert.NotNull(output); - Assert.Equal(16 + s_Hello.Length, len); - // Unseal the content and check it - byte[] temp = new byte[s_Hello.Length]; - fakeNtlmServer.Unwrap(output, temp); - Assert.Equal(s_Hello, temp); - - // Test creating signature on server side and decoding it with VerifySignature on client side - byte[] serverSignedMessage = new byte[16 + s_Hello.Length]; - fakeNtlmServer.Wrap(s_Hello, serverSignedMessage); - len = ntAuth.Unwrap(serverSignedMessage, out int newOffset, out _); - Assert.Equal(s_Hello.Length, len); - Assert.Equal(s_Hello, serverSignedMessage.AsSpan(newOffset, len).ToArray()); - } - - private void DoNtlmExchange(FakeNtlmServer fakeNtlmServer, NTAuthentication ntAuth) - { - byte[]? negotiateBlob = ntAuth.GetOutgoingBlob(null, throwOnError: false); - Assert.NotNull(negotiateBlob); - byte[]? challengeBlob = fakeNtlmServer.GetOutgoingBlob(negotiateBlob); - Assert.NotNull(challengeBlob); - byte[]? authenticateBlob = ntAuth.GetOutgoingBlob(challengeBlob, throwOnError: false); - Assert.NotNull(authenticateBlob); - byte[]? empty = fakeNtlmServer.GetOutgoingBlob(authenticateBlob); - Assert.Null(empty); - } - } -} diff --git a/src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs b/src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs index 4cc089db463..0ccc266fa8e 100644 --- a/src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs +++ b/src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Buffers; using System.Buffers.Binary; using System.IO; using System.Net.Security; @@ -186,6 +187,44 @@ namespace System.Net.Security.Tests Assert.False(fakeNtlmServer.IsAuthenticated); } + [ConditionalFact(nameof(IsNtlmAvailable))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/65678", TestPlatforms.OSX | TestPlatforms.iOS | TestPlatforms.MacCatalyst)] + public void NtlmSignatureTest() + { + FakeNtlmServer fakeNtlmServer = new FakeNtlmServer(s_testCredentialRight); + NegotiateAuthentication ntAuth = new NegotiateAuthentication( + new NegotiateAuthenticationClientOptions + { + Package = "NTLM", + Credential = s_testCredentialRight, + TargetName = "HTTP/foo", + RequiredProtectionLevel = ProtectionLevel.EncryptAndSign + }); + + DoNtlmExchange(fakeNtlmServer, ntAuth); + + Assert.True(fakeNtlmServer.IsAuthenticated); + + // Test MakeSignature on client side and decoding it on server side + ArrayBufferWriter<byte> output = new ArrayBufferWriter<byte>(); + NegotiateAuthenticationStatusCode statusCode; + statusCode = ntAuth.Wrap(s_Hello, output, ntAuth.IsEncrypted, out bool isEncrypted); + Assert.Equal(16 + s_Hello.Length, output.WrittenCount); + // Unseal the content and check it + byte[] temp = new byte[s_Hello.Length]; + fakeNtlmServer.Unwrap(output.WrittenSpan, temp); + Assert.Equal(s_Hello, temp); + + // Test creating signature on server side and decoding it with VerifySignature on client side + byte[] serverSignedMessage = new byte[16 + s_Hello.Length]; + fakeNtlmServer.Wrap(s_Hello, serverSignedMessage); + output.Clear(); + statusCode = ntAuth.Unwrap(serverSignedMessage, output, out isEncrypted); + Assert.Equal(NegotiateAuthenticationStatusCode.Completed, statusCode); + Assert.Equal(s_Hello.Length, output.WrittenCount); + Assert.Equal(s_Hello, output.WrittenSpan.ToArray()); + } + private void DoNtlmExchange(FakeNtlmServer fakeNtlmServer, NegotiateAuthentication ntAuth) { NegotiateAuthenticationStatusCode statusCode; 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 6492a3369b9..123346f7960 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 @@ -28,7 +28,6 @@ <Compile Include="System\Security\Authentication\InvalidCredentialExceptionTest.cs" /> <Compile Include="TlsAlertsMatchWindowsInterop.cs" /> <Compile Include="MD4Tests.cs" /> - <Compile Include="NTAuthenticationTests.cs" /> <Compile Include="NegotiateAuthenticationTests.cs" /> <!-- Fakes --> <Compile Include="Fakes\FakeSslStream.Implementation.cs" /> |