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-05-07 06:47:08 +0300
committerGitHub <noreply@github.com>2022-05-07 06:47:08 +0300
commit6e49909235a9f4f5826af130550bb95faee26928 (patch)
tree510d013d0c84a23fd73cec436801dea009554297 /src/libraries/System.Net.Security/tests/UnitTests
parent1b930a99d387f16801b24700251ec1a4a876ef89 (diff)
Add basic fake NTLM server to test NTAuthentication round-trip scenarios (#65611)
* Add basic NTLM fake server to test NTAuthentication round-trip scenarios * Treat MIC as optional on Linux * Add guard for IsNtlmInstalled * Add comments; add protocol exchange example from NTLM specification * Test both correct and incorrect credentials * Check the SPN sent by client * Replace NtlmAssert with regular Xunit Assert to get better messages * Tweak algorithm for calculating flags in CHALLENGE_MESSAGE, add support for OEM encoding * Set the NegotiateTargetInfo on CHALLENGE_MESSAGE; relax the check for Linux due to a bug in gss-ntlmssp * Remove duplicate implementation of RC4. * Fix paths. * Rename EnableDllImportGenerator to EnableLibraryImportGenerator.
Diffstat (limited to 'src/libraries/System.Net.Security/tests/UnitTests')
-rw-r--r--src/libraries/System.Net.Security/tests/UnitTests/Fakes/FakeNtlmServer.cs359
-rw-r--r--src/libraries/System.Net.Security/tests/UnitTests/NTAuthenticationTests.cs119
-rw-r--r--src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj159
3 files changed, 632 insertions, 5 deletions
diff --git a/src/libraries/System.Net.Security/tests/UnitTests/Fakes/FakeNtlmServer.cs b/src/libraries/System.Net.Security/tests/UnitTests/Fakes/FakeNtlmServer.cs
new file mode 100644
index 00000000000..ffb78a5c88f
--- /dev/null
+++ b/src/libraries/System.Net.Security/tests/UnitTests/Fakes/FakeNtlmServer.cs
@@ -0,0 +1,359 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Buffers.Binary;
+using System.Text;
+using System.Net;
+using System.Security.Cryptography;
+using System.Net.Security;
+using Xunit;
+
+namespace System.Net.Security
+{
+ // Implementation of subset of the NTLM specification
+ // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4
+ //
+ // Server-side implementation of the NTLMv2 exchange is implemented with
+ // basic verification of the messages passed by the client against a
+ // specified set of authentication credentials.
+ //
+ // This is not indended as a production-quality code for implementing the
+ // NTLM authentication. It's merely to serve as a validation of challenges
+ // and responses for unit test purposes. The validation checks the
+ // structure of the messages, their integrity and use of specified
+ // features (eg. MIC).
+ internal class FakeNtlmServer
+ {
+ public FakeNtlmServer(NetworkCredential expectedCredential)
+ {
+ _expectedCredential = expectedCredential;
+ }
+
+ // Behavior modifiers
+ public bool SendTimestamp { get; set; } = true;
+ public byte[] Version { get; set; } = new byte[] { 0x06, 0x00, 0x70, 0x17, 0x00, 0x00, 0x00, 0x0f }; // 6.0.6000 / 15
+ public bool TargetIsServer { get; set; } = false;
+ public bool PreferUnicode { get; set; } = true;
+
+ // Negotiation results
+ public bool IsAuthenticated { get; set; }
+ public bool IsMICPresent { get; set; }
+ public string? ClientSpecifiedSpn { get; set; }
+
+ private NetworkCredential _expectedCredential;
+
+ // Saved Negotiate and Challenge messages for MIC calculation
+ private byte[]? _negotiateMessage;
+ private byte[]? _challengeMessage;
+
+ private MessageType _expectedMessageType = MessageType.Negotiate;
+
+ // Minimal set of required negotiation flags
+ private const Flags _requiredFlags =
+ Flags.NegotiateNtlm2 | Flags.NegotiateNtlm | Flags.NegotiateAlwaysSign;
+
+ // Fixed server challenge (same value as in Protocol Examples section of the specification)
+ private byte[] _serverChallenge = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef };
+
+ private static ReadOnlySpan<byte> NtlmHeader => new byte[] {
+ (byte)'N', (byte)'T', (byte)'L', (byte)'M',
+ (byte)'S', (byte)'S', (byte)'P', 0 };
+
+ private enum MessageType : uint
+ {
+ Negotiate = 1,
+ Challenge = 2,
+ Authenticate = 3,
+ }
+
+ [Flags]
+ private enum Flags : uint
+ {
+ NegotiateUnicode = 0x00000001,
+ NegotiateOEM = 0x00000002,
+ RequestTargetName = 0x00000004,
+ NegotiateSign = 0x00000010,
+ NegotiateSeal = 0x00000020,
+ NegotiateDatagram = 0x00000040,
+ NegotiateLMKey = 0x00000080,
+ NegotiateNtlm = 0x00000200,
+ NegotiateAnonymous = 0x00000800,
+ NegotiateDomainSupplied = 0x00001000,
+ NegotiateWorkstationSupplied = 0x00002000,
+ NegotiateAlwaysSign = 0x00008000,
+ TargetTypeDomain = 0x00010000,
+ TargetTypeServer = 0x00020000,
+ NegotiateNtlm2 = 0x00080000,
+ RequestIdenityToken = 0x00100000,
+ RequestNonNtSessionKey = 0x00400000,
+ NegotiateTargetInfo = 0x00800000,
+ NegotiateVersion = 0x02000000,
+ Negotiate128 = 0x20000000,
+ NegotiateKeyExchange = 0x40000000,
+ Negotiate56 = 0x80000000,
+
+ AllSupported =
+ NegotiateUnicode | NegotiateOEM | RequestTargetName |
+ NegotiateSign | NegotiateSeal | NegotiateDatagram |
+ /* NegotiateLMKey | */ NegotiateNtlm | /* NegotiateAnonymous | */
+ /* NegotiateDomainSupplied | NegotiateWorkstationSupplied | */
+ NegotiateAlwaysSign | TargetTypeDomain | TargetTypeServer |
+ NegotiateNtlm2 | /* RequestIdenityToken | RequestNonNtSessionKey | */
+ NegotiateTargetInfo | NegotiateVersion | Negotiate128 |
+ NegotiateKeyExchange | Negotiate56,
+ }
+
+ private enum AvId
+ {
+ EOL,
+ NbComputerName,
+ NbDomainName,
+ DnsComputerName,
+ DnsDomainName,
+ DnsTreeName,
+ Flags,
+ Timestamp,
+ SingleHost,
+ TargetName,
+ ChannelBindings,
+ }
+
+ [Flags]
+ private enum AvFlags : uint
+ {
+ ConstrainedAuthentication = 1,
+ MICPresent = 2,
+ UntrustedSPN = 4,
+ }
+
+ private static ReadOnlySpan<byte> GetField(ReadOnlySpan<byte> payload, int fieldOffset)
+ {
+ uint offset = BinaryPrimitives.ReadUInt32LittleEndian(payload.Slice(fieldOffset + 4));
+ ushort length = BinaryPrimitives.ReadUInt16LittleEndian(payload.Slice(fieldOffset));
+
+ if (length == 0 || offset + length > payload.Length)
+ {
+ return ReadOnlySpan<byte>.Empty;
+ }
+
+ return payload.Slice((int)offset, length);
+ }
+
+ public byte[]? GetOutgoingBlob(byte[]? incomingBlob)
+ {
+ // Ensure the message is long enough
+ Assert.True(incomingBlob.Length >= 12);
+ Assert.Equal(NtlmHeader.ToArray(), incomingBlob.AsSpan(0, 8).ToArray());
+
+ var messageType = (MessageType)BinaryPrimitives.ReadUInt32LittleEndian(incomingBlob.AsSpan(8, 4));
+ Assert.Equal(_expectedMessageType, messageType);
+
+ switch (messageType)
+ {
+ case MessageType.Negotiate:
+ // We don't negotiate, we just verify
+ Assert.True(incomingBlob.Length >= 32);
+ Flags flags = (Flags)BinaryPrimitives.ReadUInt32LittleEndian(incomingBlob.AsSpan(12, 4));
+ Assert.Equal(_requiredFlags, (flags & _requiredFlags));
+ Assert.True((flags & (Flags.NegotiateOEM | Flags.NegotiateUnicode)) != 0);
+ if (flags.HasFlag(Flags.NegotiateDomainSupplied))
+ {
+ string domain = Encoding.ASCII.GetString(GetField(incomingBlob, 16));
+ Assert.Equal(_expectedCredential.Domain, domain);
+ }
+ _expectedMessageType = MessageType.Authenticate;
+ _negotiateMessage = incomingBlob;
+ return _challengeMessage = GenerateChallenge(flags);
+
+ case MessageType.Authenticate:
+ // Validate the authentication!
+ ValidateAuthentication(incomingBlob);
+ _expectedMessageType = 0;
+ return null;
+
+ default:
+ Assert.Fail($"Incorrect message type {messageType}");
+ return null;
+ }
+ }
+
+ private static int WriteAvIdString(Span<byte> buffer, AvId avId, string value)
+ {
+ int size = Encoding.Unicode.GetByteCount(value);
+ BinaryPrimitives.WriteUInt16LittleEndian(buffer, (ushort)avId);
+ BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(2), (ushort)size);
+ Encoding.Unicode.GetBytes(value, buffer.Slice(4));
+ return size + 4;
+ }
+
+ private byte[] GenerateChallenge(Flags flags)
+ {
+ byte[] buffer = new byte[1000];
+ byte[] targetName = Encoding.Unicode.GetBytes(TargetIsServer ? "Server" : _expectedCredential.Domain);
+ int payloadOffset = 56;
+
+ // Loosely follow the flag manipulation in
+ // 3.2.5.1.1 Server Receives a NEGOTIATE_MESSAGE from the Client
+ flags &= ~(Flags.NegotiateLMKey | Flags.TargetTypeServer | Flags.TargetTypeDomain);
+ flags |= Flags.NegotiateNtlm | Flags.NegotiateAlwaysSign | Flags.NegotiateTargetInfo;
+ // Specification says to set Flags.RequestTargetName but it's valid only in NEGOTIATE_MESSAGE?!
+ flags |= TargetIsServer ? Flags.TargetTypeServer : Flags.TargetTypeDomain;
+ if (PreferUnicode && flags.HasFlag(Flags.NegotiateUnicode))
+ {
+ flags &= ~Flags.NegotiateOEM;
+ }
+ // Remove any unsupported flags here
+ flags &= Flags.AllSupported;
+
+ NtlmHeader.CopyTo(buffer.AsSpan(0, 8));
+ BinaryPrimitives.WriteUInt32LittleEndian(buffer.AsSpan(8), (uint)MessageType.Challenge);
+ BinaryPrimitives.WriteUInt16LittleEndian(buffer.AsSpan(12), (ushort)targetName.Length);
+ BinaryPrimitives.WriteUInt16LittleEndian(buffer.AsSpan(14), (ushort)targetName.Length);
+ BinaryPrimitives.WriteUInt32LittleEndian(buffer.AsSpan(16), (ushort)payloadOffset);
+ targetName.CopyTo(buffer.AsSpan(payloadOffset, targetName.Length));
+ payloadOffset += targetName.Length;
+ BinaryPrimitives.WriteUInt32LittleEndian(buffer.AsSpan(20), (uint)flags);
+ _serverChallenge.CopyTo(buffer.AsSpan(24, 8));
+ // 8 bytes reserved
+ // 8 bytes of TargetInfoFields (written below)
+ Version.CopyTo(buffer.AsSpan(48, 8));
+
+ int targetInfoOffset = payloadOffset;
+ int targetInfoCurrentOffset = targetInfoOffset;
+ targetInfoCurrentOffset += WriteAvIdString(buffer.AsSpan(targetInfoCurrentOffset), AvId.NbDomainName, _expectedCredential.Domain);
+ targetInfoCurrentOffset += WriteAvIdString(buffer.AsSpan(targetInfoCurrentOffset), AvId.NbComputerName, "Server");
+
+ if (SendTimestamp)
+ {
+ BinaryPrimitives.WriteUInt16LittleEndian(buffer.AsSpan(targetInfoCurrentOffset), (ushort)AvId.Timestamp);
+ BinaryPrimitives.WriteUInt16LittleEndian(buffer.AsSpan(targetInfoCurrentOffset + 2), (ushort)8);
+ BinaryPrimitives.WriteInt64LittleEndian(buffer.AsSpan(targetInfoCurrentOffset + 4), DateTime.UtcNow.ToFileTimeUtc());
+ targetInfoCurrentOffset += 12;
+ }
+
+ // TODO: DNS machine, domain, forest?
+ // EOL
+ targetInfoCurrentOffset += 4;
+ int targetInfoSize = targetInfoCurrentOffset - targetInfoOffset;
+
+ BinaryPrimitives.WriteUInt16LittleEndian(buffer.AsSpan(40), (ushort)targetInfoSize);
+ BinaryPrimitives.WriteUInt16LittleEndian(buffer.AsSpan(42), (ushort)targetInfoSize);
+ BinaryPrimitives.WriteUInt32LittleEndian(buffer.AsSpan(44), (uint)targetInfoOffset);
+
+ return buffer.AsSpan(0, targetInfoCurrentOffset).ToArray();
+ }
+
+ private byte[] MakeNtlm2Hash()
+ {
+ byte[] pwHash = new byte[16];
+ byte[] pwBytes = Encoding.Unicode.GetBytes(_expectedCredential.Password);
+ MD4.HashData(pwBytes, pwHash);
+ using (IncrementalHash hmac = IncrementalHash.CreateHMAC(HashAlgorithmName.MD5, pwHash))
+ {
+ hmac.AppendData(Encoding.Unicode.GetBytes(_expectedCredential.UserName.ToUpper() + _expectedCredential.Domain));
+ return hmac.GetHashAndReset();
+ }
+ }
+
+ private void ValidateAuthentication(byte[] incomingBlob)
+ {
+ ReadOnlySpan<byte> lmChallengeResponse = GetField(incomingBlob, 12);
+ ReadOnlySpan<byte> ntChallengeResponse = GetField(incomingBlob, 20);
+ ReadOnlySpan<byte> encryptedRandomSessionKey = GetField(incomingBlob, 52);
+ ReadOnlySpan<byte> mic = incomingBlob.AsSpan(72, 16);
+
+ Flags flags = (Flags)BinaryPrimitives.ReadUInt32LittleEndian(incomingBlob.AsSpan(60));
+ Assert.Equal(_requiredFlags, (flags & _requiredFlags));
+
+ // Only one encoding can be selected by the client
+ Assert.True((flags & (Flags.NegotiateOEM | Flags.NegotiateUnicode)) != 0);
+ Assert.True((flags & (Flags.NegotiateOEM | Flags.NegotiateUnicode)) != (Flags.NegotiateOEM | Flags.NegotiateUnicode));
+ Encoding encoding = flags.HasFlag(Flags.NegotiateUnicode) ? Encoding.Unicode : Encoding.ASCII;
+
+ string domainName = encoding.GetString(GetField(incomingBlob, 28));
+ string userName = encoding.GetString(GetField(incomingBlob, 36));
+ string workstation = encoding.GetString(GetField(incomingBlob, 44));
+ Assert.Equal(_expectedCredential.UserName, userName);
+ Assert.Equal(_expectedCredential.Domain, domainName);
+
+ byte[] ntlm2hash = MakeNtlm2Hash();
+ Span<byte> sessionBaseKey = stackalloc byte[16];
+ using (IncrementalHash hmac = IncrementalHash.CreateHMAC(HashAlgorithmName.MD5, ntlm2hash))
+ {
+ hmac.AppendData(_serverChallenge);
+ hmac.AppendData(ntChallengeResponse.Slice(16));
+ // If this matches then the password matched
+ IsAuthenticated = hmac.GetHashAndReset().AsSpan().SequenceEqual(ntChallengeResponse.Slice(0, 16));
+
+ if (!IsAuthenticated)
+ {
+ // Bail out
+ return;
+ }
+
+ // Compute sessionBaseKey
+ hmac.AppendData(ntChallengeResponse.Slice(0, 16));
+ hmac.GetHashAndReset(sessionBaseKey);
+ }
+
+ ReadOnlySpan<byte> avPairs = ntChallengeResponse.Slice(16 + 28);
+ AvFlags avFlags = 0;
+ while (avPairs[0] != (byte)AvId.EOL)
+ {
+ AvId id = (AvId)avPairs[0];
+ Assert.Equal(0, avPairs[1]);
+ ushort length = BinaryPrimitives.ReadUInt16LittleEndian(avPairs.Slice(2, 2));
+
+ if (id == AvId.Flags)
+ {
+ Assert.Equal(4, length);
+ avFlags = (AvFlags)BinaryPrimitives.ReadUInt32LittleEndian(avPairs.Slice(4, 4));
+ }
+ else if (id == AvId.TargetName)
+ {
+ ClientSpecifiedSpn = Encoding.Unicode.GetString(avPairs.Slice(4, length));
+ }
+
+ avPairs = avPairs.Slice(length + 4);
+ }
+
+ // Decrypt exportedSessionKey with sessionBaseKey
+ Span<byte> exportedSessionKey = stackalloc byte[16];
+ if (flags.HasFlag(Flags.NegotiateKeyExchange) &&
+ (flags.HasFlag(Flags.NegotiateSeal) || flags.HasFlag(Flags.NegotiateSign)))
+ {
+ using (RC4 rc4 = new RC4(sessionBaseKey))
+ {
+ rc4.Transform(encryptedRandomSessionKey, exportedSessionKey);
+ }
+ }
+ else
+ {
+ sessionBaseKey.CopyTo(exportedSessionKey);
+ }
+
+ // Calculate and verify message integrity if enabled
+ if (avFlags.HasFlag(AvFlags.MICPresent))
+ {
+ IsMICPresent = true;
+
+ Assert.NotNull(_negotiateMessage);
+ Assert.NotNull(_challengeMessage);
+ byte[] calculatedMic = new byte[16];
+ using (var hmacMic = IncrementalHash.CreateHMAC(HashAlgorithmName.MD5, exportedSessionKey))
+ {
+ hmacMic.AppendData(_negotiateMessage);
+ hmacMic.AppendData(_challengeMessage);
+ // Authenticate message with the MIC erased
+ hmacMic.AppendData(incomingBlob.AsSpan(0, 72));
+ hmacMic.AppendData(new byte[16]);
+ hmacMic.AppendData(incomingBlob.AsSpan(72 + 16));
+ hmacMic.GetHashAndReset(calculatedMic);
+ }
+ Assert.Equal(mic.ToArray(), calculatedMic);
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Net.Security/tests/UnitTests/NTAuthenticationTests.cs b/src/libraries/System.Net.Security/tests/UnitTests/NTAuthenticationTests.cs
new file mode 100644
index 00000000000..6a90b7665f9
--- /dev/null
+++ b/src/libraries/System.Net.Security/tests/UnitTests/NTAuthenticationTests.cs
@@ -0,0 +1,119 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.IO;
+using System.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 NetworkCredential s_testCredentialWrong = new NetworkCredential("rightusername", "wrongpassword");
+
+ [Fact]
+ public void NtlmProtocolExampleTest()
+ {
+ // Mirrors the NTLMv2 example in the NTLM specification:
+ NetworkCredential credential = new NetworkCredential("User", "Password", "Domain");
+ FakeNtlmServer fakeNtlmServer = new FakeNtlmServer(credential);
+ fakeNtlmServer.SendTimestamp = false;
+ fakeNtlmServer.TargetIsServer = true;
+ fakeNtlmServer.PreferUnicode = false;
+
+ // NEGOTIATE_MESSAGE
+ // Flags:
+ // NTLMSSP_NEGOTIATE_KEY_EXCH
+ // NTLMSSP_NEGOTIATE_56
+ // NTLMSSP_NEGOTIATE_128
+ // NTLMSSP_NEGOTIATE_VERSION
+ // NTLMSSP_NEGOTIATE_TARGET_INFO
+ // NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
+ // NTLMSSP_TARGET_TYPE_SERVER
+ // NTLMSSP_NEGOTIATE_ALWAYS_SIGN
+ // NTLMSSP_NEGOTIATE_NTLM
+ // NTLMSSP_NEGOTIATE_SEAL
+ // NTLMSSP_NEGOTIATE_SIGN
+ // NTLMSSP_NEGOTIATE_OEM
+ // NTLMSSP_NEGOTIATE_UNICODE
+ // Domain: (empty) (should be "Domain" but the fake server doesn't check)
+ // Workstation: (empty) (should be "COMPUTER" but the fake server doesn't check)
+ // Version: 6.1.7600 / 15
+ byte[] negotiateBlob = Convert.FromHexString("4e544c4d535350000100000033828ae2000000000000000000000000000000000601b01d0000000f");
+ byte[]? challengeBlob = fakeNtlmServer.GetOutgoingBlob(negotiateBlob);
+
+ // CHALLENGE_MESSAGE from 4.2.4.3 Messages
+ byte[] expectedChallengeBlob = Convert.FromHexString(
+ "4e544c4d53535000020000000c000c003800000033828ae20123456789abcdef" +
+ "00000000000000002400240044000000060070170000000f5300650072007600" +
+ "6500720002000c0044006f006d00610069006e0001000c005300650072007600" +
+ "6500720000000000");
+ Assert.Equal(expectedChallengeBlob, challengeBlob);
+
+ // AUTHENTICATE_MESSAGE from 4.2.4.3 Messages
+ byte[] authenticateBlob = Convert.FromHexString(
+ "4e544c4d5353500003000000180018006c00000054005400840000000c000c00" +
+ "480000000800080054000000100010005c00000010001000d8000000358288e2" +
+ "0501280a0000000f44006f006d00610069006e00550073006500720043004f00" +
+ "4d005000550054004500520086c35097ac9cec102554764a57cccc19aaaaaaaa" +
+ "aaaaaaaa68cd0ab851e51c96aabc927bebef6a1c010100000000000000000000" +
+ "00000000aaaaaaaaaaaaaaaa0000000002000c0044006f006d00610069006e00" +
+ "01000c005300650072007600650072000000000000000000c5dad2544fc97990" +
+ "94ce1ce90bc9d03e");
+ byte[]? empty = fakeNtlmServer.GetOutgoingBlob(authenticateBlob);
+ Assert.Null(empty);
+ Assert.True(fakeNtlmServer.IsAuthenticated);
+ Assert.False(fakeNtlmServer.IsMICPresent);
+ }
+
+ [ConditionalFact(nameof(IsNtlmInstalled))]
+ public void NtlmCorrectExchangeTest()
+ {
+ FakeNtlmServer fakeNtlmServer = new FakeNtlmServer(s_testCredentialRight);
+ NTAuthentication ntAuth = new NTAuthentication(
+ isServer: false, "NTLM", s_testCredentialRight, "HTTP/foo",
+ ContextFlagsPal.Connection | ContextFlagsPal.InitIntegrity, null);
+
+ DoNtlmExchange(fakeNtlmServer, ntAuth);
+
+ Assert.True(fakeNtlmServer.IsAuthenticated);
+ // NTLMSSP on Linux doesn't send the MIC and sends incorrect SPN (drops the service prefix)
+ if (!OperatingSystem.IsLinux())
+ {
+ Assert.True(fakeNtlmServer.IsMICPresent);
+ Assert.Equal("HTTP/foo", fakeNtlmServer.ClientSpecifiedSpn);
+ }
+ }
+
+ [ConditionalFact(nameof(IsNtlmInstalled))]
+ public void NtlmIncorrectExchangeTest()
+ {
+ FakeNtlmServer fakeNtlmServer = new FakeNtlmServer(s_testCredentialRight);
+ NTAuthentication ntAuth = new NTAuthentication(
+ isServer: false, "NTLM", s_testCredentialWrong, "HTTP/foo",
+ ContextFlagsPal.Connection | ContextFlagsPal.InitIntegrity, null);
+
+ DoNtlmExchange(fakeNtlmServer, ntAuth);
+
+ Assert.False(fakeNtlmServer.IsAuthenticated);
+ }
+
+ 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/System.Net.Security.Unit.Tests.csproj b/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj
index 61c345127c8..c0f761856c5 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
@@ -13,15 +13,11 @@
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser;$(NetCoreAppCurrent)-OSX;$(NetCoreAppCurrent)-iOS;$(NetCoreAppCurrent)-Android</TargetFrameworks>
<Nullable>annotations</Nullable>
<IgnoreForCI Condition="'$(TargetOS)' == 'Browser'">true</IgnoreForCI>
+ <EnableLibraryImportGenerator>true</EnableLibraryImportGenerator>
</PropertyGroup>
<ItemGroup>
<Compile Include="AssemblyInfo.cs" />
</ItemGroup>
- <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Android'">
- <Compile Include="MD4Tests.cs" />
- <Compile Include="$(CommonPath)System\Net\Security\MD4.cs"
- Link="ProductionCode\Common\System\Net\Security\MD4.cs" />
- </ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' != 'Browser'">
<Compile Include="SslApplicationProtocolTests.cs" />
<Compile Include="SslAuthenticationOptionsTests.cs" />
@@ -30,10 +26,15 @@
<Compile Include="System\Security\Authentication\ExtendedProtection\ExtendedProtectionPolicyTest.cs" />
<Compile Include="System\Security\Authentication\InvalidCredentialExceptionTest.cs" />
<Compile Include="TlsAlertsMatchWindowsInterop.cs" />
+ <Compile Include="MD4Tests.cs" />
+ <Compile Include="NTAuthenticationTests.cs" />
<!-- Fakes -->
<Compile Include="Fakes\FakeSslStream.Implementation.cs" />
<Compile Include="Fakes\FakeAuthenticatedStream.cs" />
+ <Compile Include="Fakes\FakeNtlmServer.cs" />
<!-- Common test files -->
+ <Compile Include="$(CommonPath)DisableRuntimeMarshalling.cs"
+ Link="Common\DisableRuntimeMarshalling.cs" />
<Compile Include="$(CommonTestPath)System\Net\SslProtocolSupport.cs"
Link="CommonTest\System\Net\SslProtocolSupport.cs" />
<Compile Include="$(CommonPath)System\HexConverter.cs"
@@ -41,6 +42,10 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' != 'Browser'">
<!-- Production code references -->
+ <Compile Include="$(CommonPath)System\Net\Security\MD4.cs"
+ Link="Common\System\Net\Security\MD4.cs" />
+ <Compile Include="$(CommonPath)System\Net\Security\RC4.cs"
+ Link="Common\System\Net\Security\RC4.cs" />
<Compile Include="..\..\src\System\Net\Security\NetEventSource.Security.cs"
Link="ProductionCode\System\Net\Security\NetEventSource.Security.cs" />
<Compile Include="..\..\src\System\Net\Security\SslStream.cs"
@@ -86,4 +91,148 @@
<Compile Include="$(CommonPath)System\Net\ArrayBuffer.cs"
Link="Common\System\Net\ArrayBuffer.cs" />
</ItemGroup>
+
+ <!-- NT Authentication -->
+ <ItemGroup Condition="'$(TargetPlatformIdentifier)' != 'Browser'">
+ <Compile Include="$(CommonPath)System\Net\ContextFlagsPal.cs"
+ Link="Common\System\Net\ContextFlagsPal.cs" />
+ <Compile Include="$(CommonPath)System\Net\DebugSafeHandleZeroOrMinusOneIsInvalid.cs"
+ Link="Common\System\Net\DebugSafeHandleZeroOrMinusOneIsInvalid.cs" />
+ <Compile Include="$(CommonPath)System\Net\DebugSafeHandle.cs"
+ Link="Common\System\Net\DebugSafeHandle.cs" />
+ <Compile Include="$(CommonPath)System\Net\ExceptionCheck.cs"
+ Link="Common\System\Net\ExceptionCheck.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" />
+ </ItemGroup>
+
+ <!-- Unix specific files (NT Authentication) -->
+ <ItemGroup Condition="'$(TargetPlatformIdentifier)' != 'windows' and '$(TargetPlatformIdentifier)' != 'Browser'">
+ <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" />
+ <Compile Include="$(CommonTestPath)System\Net\Capability.Security.Unix.cs"
+ Link="Common\System\Net\Capability.Security.Unix.cs" />
+ </ItemGroup>
+
+ <!-- Windows specific files (NT Authentication) -->
+ <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'windows'">
+ <Compile Include="$(CommonPath)System\Collections\Generic\BidirectionalDictionary.cs"
+ Link="Common\System\Collections\Generic\BidirectionalDictionary.cs" />
+ <Compile Include="$(CommonPath)System\NotImplemented.cs"
+ Link="Common\System\NotImplemented.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.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" />
+ <Compile Include="$(CommonTestPath)System\Net\Capability.Security.Windows.cs"
+ Link="Common\System\Net\Capability.Security.Windows.cs" />
+ </ItemGroup>
</Project>