Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/runtime.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilip Navara <navara@emclient.com>2022-07-11 14:13:27 +0300
committerGitHub <noreply@github.com>2022-07-11 14:13:27 +0300
commit26da83df77b2391d6a74a4f24b663dc2fb4eda3f (patch)
tree36a0f93784c48e6a5d8f29d3c08a595f9d7d5876
parent7d2be1e66992c8510b8c4f899cbcfa9e88c22325 (diff)
NegotiateAuthentication: Implement additional API surface (#71777)
* Implement NegotiateAuthentication.Wrap/Unwrap/UnwrapInPlace APIs Updated unit tests Migrate System.Net.Mail to use NegotiateAuthentication API * Implementation of extended protection policy and impersonation in NegotiateAuthentication
-rw-r--r--src/libraries/Common/src/System/Net/NTAuthentication.Common.cs14
-rw-r--r--src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs74
-rw-r--r--src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs100
-rw-r--r--src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Windows.cs97
-rw-r--r--src/libraries/System.Net.Mail/src/System.Net.Mail.csproj133
-rw-r--r--src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpAuthenticationManager.cs2
-rw-r--r--src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpConnection.Auth.cs4
-rw-r--r--src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpNegotiateAuthenticationModule.cs139
-rw-r--r--src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpNtlmAuthenticationModule.cs61
-rw-r--r--src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj110
-rw-r--r--src/libraries/System.Net.Security/ref/System.Net.Security.cs11
-rw-r--r--src/libraries/System.Net.Security/ref/System.Net.Security.csproj1
-rw-r--r--src/libraries/System.Net.Security/src/Resources/Strings.resx45
-rw-r--r--src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthentication.cs233
-rw-r--r--src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationClientOptions.cs12
-rw-r--r--src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationServerOptions.cs13
-rw-r--r--src/libraries/System.Net.Security/src/System/Net/Security/NegotiateAuthenticationStatusCode.cs11
-rw-r--r--src/libraries/System.Net.Security/tests/UnitTests/NTAuthenticationTests.cs65
-rw-r--r--src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs39
-rw-r--r--src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj1
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" />