未验证 提交 26da83df 编写于 作者: F Filip Navara 提交者: GitHub

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
上级 7d2be1e6
// 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 @@ internal void CloseContext()
_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)
......
......@@ -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 @@ private unsafe byte[] CreateSpNegoNegotiateMessage(ReadOnlySpan<byte> ntlmNegoti
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)
{
......
......@@ -567,18 +567,106 @@ internal static SafeFreeCredentials AcquireCredentialsHandle(string package, boo
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();
}
}
}
}
// 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 @@ internal static Win32Exception CreateExceptionFromError(SecurityStatusPal status
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 @@ internal static unsafe int Unwrap(SafeDeleteContext securityContext, Span<byte>
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 @@ internal static unsafe int Unwrap(SafeDeleteContext securityContext, Span<byte>
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 @@ internal static unsafe int Wrap(SafeDeleteContext securityContext, ReadOnlySpan<
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;
}
}
......
<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" />
......
......@@ -11,10 +11,8 @@ internal static class SmtpAuthenticationManager
static SmtpAuthenticationManager()
{
#if !NO_NTAUTHENTICATION
Register(new SmtpNegotiateAuthenticationModule());
Register(new SmtpNtlmAuthenticationModule());
#endif
Register(new SmtpLoginAuthenticationModule());
}
......
......@@ -18,9 +18,7 @@ internal sealed partial class SmtpConnection
#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 @@ internal bool AuthSupported(ISmtpAuthenticationModule module)
return true;
}
}
#if !NO_NTAUTHENTICATION
else if (module is SmtpNegotiateAuthenticationModule)
{
if ((_supportedAuth & SupportedAuth.GSSAPI) > 0)
......@@ -110,7 +107,6 @@ internal bool AuthSupported(ISmtpAuthenticationModule module)
return true;
}
}
#endif
return false;
}
......
// 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 @@ internal SmtpNegotiateAuthenticationModule()
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 @@ public string AuthenticationType
public void CloseContext(object sessionCookie)
{
NTAuthentication? clientContext = null;
NegotiateAuthentication? clientContext = null;
lock (_sessions)
{
if (_sessions.TryGetValue(sessionCookie, out clientContext))
......@@ -108,7 +100,7 @@ public void CloseContext(object sessionCookie)
_sessions.Remove(sessionCookie);
}
}
clientContext?.CloseContext();
clientContext?.Dispose();
}
// Function for SASL security layer negotiation after
......@@ -116,7 +108,7 @@ public void CloseContext(object sessionCookie)
//
// 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 @@ public void CloseContext(object sessionCookie)
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 @@ public void CloseContext(object sessionCookie)
// 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);
}
}
}
......@@ -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 @@ internal SmtpNtlmAuthenticationModule()
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;
}
}
......
......@@ -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"
......
......@@ -39,6 +39,7 @@ public sealed partial class NegotiateAuthentication : System.IDisposable
{
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 @@ public sealed partial class NegotiateAuthentication : System.IDisposable
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 @@ public partial class NegotiateAuthenticationServerOptions
public System.Security.Authentication.ExtendedProtection.ChannelBinding? Binding { get { throw null; } set { } }
public System.Net.NetworkCredential Credential { get { throw null; } set { } }
public string Package { get { throw null; } set { } }
public System.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 @@ public enum NegotiateAuthenticationStatusCode
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
......
......@@ -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" />
......
......@@ -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>
......
// 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 @@ public sealed class NegotiateAuthentication : IDisposable
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 @@ public NegotiateAuthentication(NegotiateAuthenticationClientOptions clientOption
{
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 @@ public NegotiateAuthentication(NegotiateAuthenticationServerOptions serverOption
_ => 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(
......@@ -214,6 +251,22 @@ public IIdentity RemoteIdentity
}
}
/// <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>
......@@ -285,6 +338,23 @@ public IIdentity RemoteIdentity
_ => 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 @@ public IIdentity RemoteIdentity
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;
}
}
}
......@@ -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 @@ public class NegotiateAuthenticationClientOptions
/// 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;
}
}
// 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 @@ public class NegotiateAuthenticationServerOptions
/// 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;
}
}
......@@ -58,6 +58,15 @@ public enum NegotiateAuthenticationStatusCode
/// <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,
}
}
// 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);
}
}
}
......@@ -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 @@ public void NtlmIncorrectExchangeTest()
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;
......
......@@ -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" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册