Skip to content

Commit a6508a0

Browse files
filipnavarawfurtSteve Pfister
authored
Managed implementation of NTLM for Android and tvOS (#66879)
* Move MD4 implementation into Common/src/System/Net/Security * Add minimal RC4 implementation * WIP: Integrate managed Ntlm implementation into SocketsHttpHandler Co-authored-by: Tomas Weinfurt <tweinfurt@yahoo.com> * WIP: Makeshift tests for NTLM/Negotiate authentication * Fix compilation, clean up some of the hashing * Avoid using a temporary buffer * Add computation of signing keys, sealing keys and mechListMIC * Various cleanups * Send SPN in target information * Add some validation, mark spots with missing validation * Clean up some of the memory offset manipulation * Move NTLM version into static variable * Add support for channel bindings, clean up * Fix hash calculation in makeNtlm2Hash accidentally broken with last commit. Read NegTokenResp explicitly. Add mechListMIC reading and verification. * Verify last authentication token in HTTP Negotiate authentication * Address feedback * Fix tvOS builds by making few methods static * Enable System.Net.Security tests on Android and iOS Co-authored-by: Tomas Weinfurt <tweinfurt@yahoo.com> Co-authored-by: Steve Pfister <steve.pfister@microsoft.com>
1 parent 0b39dee commit a6508a0

File tree

10 files changed

+1074
-14
lines changed

10 files changed

+1074
-14
lines changed

src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs

Lines changed: 955 additions & 0 deletions
Large diffs are not rendered by default.
File renamed without changes.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
//
5+
// ARC4Managed.cs: Alleged RC4(tm) compatible symmetric stream cipher
6+
// RC4 is a trademark of RSA Security
7+
//
8+
// Permission is hereby granted, free of charge, to any person obtaining
9+
// a copy of this software and associated documentation files (the
10+
// "Software"), to deal in the Software without restriction, including
11+
// without limitation the rights to use, copy, modify, merge, publish,
12+
// distribute, sublicense, and/or sell copies of the Software, and to
13+
// permit persons to whom the Software is furnished to do so, subject to
14+
// the following conditions:
15+
//
16+
// The above copyright notice and this permission notice shall be
17+
// included in all copies or substantial portions of the Software.
18+
//
19+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20+
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22+
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23+
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24+
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25+
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26+
//
27+
28+
using System;
29+
using System.Buffers;
30+
using System.Diagnostics;
31+
using System.Security.Cryptography;
32+
33+
namespace System.Net.Security
34+
{
35+
// References:
36+
// a. Usenet 1994 - RC4 Algorithm revealed
37+
// http://www.qrst.de/html/dsds/rc4.htm
38+
internal class RC4 : IDisposable
39+
{
40+
private byte[]? state;
41+
private byte x;
42+
private byte y;
43+
44+
public RC4(ReadOnlySpan<byte> key)
45+
{
46+
state = ArrayPool<byte>.Shared.Rent(256);
47+
48+
byte index1 = 0;
49+
byte index2 = 0;
50+
51+
for (int counter = 0; counter < 256; counter++)
52+
{
53+
state[counter] = (byte)counter;
54+
}
55+
56+
for (int counter = 0; counter < 256; counter++)
57+
{
58+
index2 = (byte)(key[index1] + state[counter] + index2);
59+
(state[counter], state[index2]) = (state[index2], state[counter]);
60+
index1 = (byte)((index1 + 1) % key.Length);
61+
}
62+
}
63+
64+
public void Dispose()
65+
{
66+
if (state != null)
67+
{
68+
x = 0;
69+
y = 0;
70+
CryptographicOperations.ZeroMemory(state.AsSpan(0, 256));
71+
ArrayPool<byte>.Shared.Return(state);
72+
state = null;
73+
}
74+
}
75+
76+
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
77+
{
78+
Debug.Assert(input.Length == output.Length);
79+
Debug.Assert(state != null);
80+
81+
for (int counter = 0; counter < input.Length; counter++)
82+
{
83+
x = (byte)(x + 1);
84+
y = (byte)(state[x] + y);
85+
(state[x], state[y]) = (state[y], state[x]);
86+
byte xorIndex = (byte)(state[x] + state[y]);
87+
output[counter] = (byte)(input[counter] ^ state[xorIndex]);
88+
}
89+
}
90+
}
91+
}

src/libraries/Common/src/System/Net/Security/Unix/SecChannelBindings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace System.Net.Security
88
[StructLayout(LayoutKind.Sequential)]
99
internal struct SecChannelBindings
1010
{
11+
internal int InitiatorAddrType;
1112
internal int InitiatorLength;
1213
internal int InitiatorOffset;
1314
internal int AcceptorAddrType;

src/libraries/System.Net.Http/src/Resources/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,9 @@
447447
<data name="net_nego_protection_level_not_supported" xml:space="preserve">
448448
<value>Requested protection level is not supported with the GSSAPI implementation currently installed.</value>
449449
</data>
450+
<data name="net_nego_mechanism_not_supported" xml:space="preserve">
451+
<value>The security package '{0}' is not supported.</value>
452+
</data>
450453
<data name="net_context_buffer_too_small" xml:space="preserve">
451454
<value>Insufficient buffer space. Required: {0} Actual: {1}.</value>
452455
</data>

src/libraries/System.Net.Http/src/System.Net.Http.csproj

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<!-- DesignTimeBuild requires all the TargetFramework Derived Properties to not be present in the first property group. -->
1010
<PropertyGroup>
1111
<TargetPlatformIdentifier>$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))</TargetPlatformIdentifier>
12+
<UseManagedNtlm Condition="'$(UseManagedNtlm)' == '' and ('$(TargetPlatformIdentifier)' == 'Android' or '$(TargetPlatformIdentifier)' == 'tvOS')">true</UseManagedNtlm>
1213
<GeneratePlatformNotSupportedAssemblyMessage Condition="'$(TargetPlatformIdentifier)' == ''">SR.PlatformNotSupported_NetHttp</GeneratePlatformNotSupportedAssemblyMessage>
1314
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'OSX' or '$(TargetPlatformIdentifier)' == 'iOS' or '$(TargetPlatformIdentifier)' == 'tvOS' or '$(TargetPlatformIdentifier)' == 'MacCatalyst'">$(DefineConstants);SYSNETHTTP_NO_OPENSSL</DefineConstants>
1415
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'Android' or '$(TargetPlatformIdentifier)' == 'iOS' or '$(TargetPlatformIdentifier)' == 'MacCatalyst' or '$(TargetPlatformIdentifier)' == 'tvOS'">$(DefineConstants);TARGET_MOBILE</DefineConstants>
@@ -159,7 +160,7 @@
159160
<Compile Include="System\Net\Http\Headers\KnownHeader.Http2And3.cs" />
160161
<Compile Include="System\Net\Http\SocketsHttpHandler\AuthenticationHelper.cs" />
161162
<Compile Include="System\Net\Http\SocketsHttpHandler\AuthenticationHelper.Digest.cs" />
162-
<Compile Condition="'$(TargetPlatformIdentifier)' != 'tvOS'" Include="System\Net\Http\SocketsHttpHandler\AuthenticationHelper.NtAuth.cs" />
163+
<Compile Include="System\Net\Http\SocketsHttpHandler\AuthenticationHelper.NtAuth.cs" />
163164
<Compile Include="System\Net\Http\SocketsHttpHandler\ChunkedEncodingReadStream.cs" />
164165
<Compile Include="System\Net\Http\SocketsHttpHandler\ChunkedEncodingWriteStream.cs" />
165166
<Compile Include="System\Net\Http\SocketsHttpHandler\ConnectHelper.cs" />
@@ -208,7 +209,7 @@
208209
<Compile Include="System\Net\Http\SocketsHttpHandler\SystemProxyInfo.cs" />
209210
<Compile Include="System\Net\Http\SocketsHttpHandler\SocksHelper.cs" />
210211
<Compile Include="System\Net\Http\SocketsHttpHandler\SocksException.cs" />
211-
<Compile Condition="'$(TargetPlatformIdentifier)' != 'tvOS'" Include="$(CommonPath)System\Net\NTAuthentication.Common.cs"
212+
<Compile Condition="'$(UseManagedNtlm)' != 'true'" Include="$(CommonPath)System\Net\NTAuthentication.Common.cs"
212213
Link="Common\System\Net\NTAuthentication.Common.cs" />
213214
<Compile Include="$(CommonPath)System\Net\ContextFlagsPal.cs"
214215
Link="Common\System\Net\ContextFlagsPal.cs" />
@@ -346,7 +347,7 @@
346347
<Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.IsNtlmInstalled.cs"
347348
Link="Common\Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.IsNtlmInstalled.cs" />
348349
</ItemGroup>
349-
<ItemGroup Condition="'$(TargetPlatformIdentifier)' != '' and '$(TargetPlatformIdentifier)' != 'windows' and '$(TargetPlatformIdentifier)' != 'Browser' and '$(TargetPlatformIdentifier)' != 'tvOS'">
350+
<ItemGroup Condition="'$(TargetPlatformIdentifier)' != '' and '$(TargetPlatformIdentifier)' != 'windows' and '$(TargetPlatformIdentifier)' != 'Browser' and '$(UseManagedNtlm)' != 'true'">
350351
<Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.GssApiException.cs"
351352
Link="Common\Interop\Unix\System.Net.Security.Native\Interop.GssApiException.cs" />
352353
<Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.GssBuffer.cs"
@@ -362,12 +363,13 @@
362363
<Compile Include="$(CommonPath)System\Net\Security\NegotiateStreamPal.Unix.cs"
363364
Link="Common\System\Net\Security\NegotiateStreamPal.Unix.cs" />
364365
</ItemGroup>
365-
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'tvOS'">
366-
<Compile Include="System\Net\Http\SocketsHttpHandler\AuthenticationHelper.NtAuth.tvOS.cs" />
367-
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\GssSafeHandles.PlatformNotSupported.cs"
368-
Link="Common\Microsoft\Win32\SafeHandles\GssSafeHandles.PlatformNotSupported.cs" />
369-
<Compile Include="$(CommonPath)System\Net\Security\NegotiateStreamPal.PlatformNotSupported.cs"
370-
Link="Common\System\Net\Security\NegotiateStreamPal.PlatformNotSupported.cs" />
366+
<ItemGroup Condition="'$(UseManagedNtlm)' == 'true'">
367+
<Compile Include="$(CommonPath)System\Net\NTAuthentication.Managed.cs"
368+
Link="Common\System\Net\NTAuthentication.Managed.cs" />
369+
<Compile Include="$(CommonPath)System\Net\Security\MD4.cs"
370+
Link="Common\System\Net\Security\MD4.cs" />
371+
<Compile Include="$(CommonPath)System\Net\Security\RC4.cs"
372+
Link="Common\System\Net\Security\RC4.cs" />
371373
</ItemGroup>
372374
<ItemGroup Condition="'$(TargetPlatformIdentifier)' != '' and '$(TargetPlatformIdentifier)' != 'windows' and '$(TargetPlatformIdentifier)' != 'Browser' and '$(TargetPlatformIdentifier)' != 'OSX' and '$(TargetPlatformIdentifier)' != 'iOS' and '$(TargetPlatformIdentifier)' != 'tvOS'">
373375
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpNoProxy.cs" />
@@ -675,6 +677,9 @@
675677
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Browser'">
676678
<ProjectReference Include="$(LibrariesProjectRoot)System.Private.Runtime.InteropServices.JavaScript\src\System.Private.Runtime.InteropServices.JavaScript.csproj" />
677679
</ItemGroup>
680+
<ItemGroup Condition="'$(UseManagedNtlm)' == 'true'">
681+
<ProjectReference Include="$(LibrariesProjectRoot)System.Formats.Asn1\src\System.Formats.Asn1.csproj" />
682+
</ItemGroup>
678683
<ItemGroup>
679684
<None Include="Resources\SR.resx" />
680685
</ItemGroup>

src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,18 @@ private static async Task<HttpResponseMessage> SendWithNtAuthAsync(HttpRequestMe
187187
SetRequestAuthenticationHeaderValue(request, new AuthenticationHeaderValue(challenge.SchemeName, challengeResponse), isProxyAuth);
188188

189189
response = await InnerSendAsync(request, async, isProxyAuth, connectionPool, connection, cancellationToken).ConfigureAwait(false);
190-
if (authContext.IsCompleted || !TryGetRepeatedChallenge(response, challenge.SchemeName, isProxyAuth, out challengeData))
190+
if (authContext.IsCompleted || !TryGetChallengeDataForScheme(challenge.SchemeName, GetResponseAuthenticationHeaderValues(response, isProxyAuth), out challengeData))
191191
{
192192
break;
193193
}
194194

195+
if (!IsAuthenticationChallenge(response, isProxyAuth))
196+
{
197+
// Tail response for Negoatiate on successful authentication. Validate it before we proceed.
198+
authContext.GetOutgoingBlob(challengeData);
199+
break;
200+
}
201+
195202
needDrain = true;
196203
}
197204
}

src/libraries/System.Net.Security/src/System.Net.Security.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,6 @@
362362
<Compile Include="$(CommonPath)Interop\Android\System.Security.Cryptography.Native.Android\Interop.X509.cs"
363363
Link="Common\Interop\Android\System.Security.Cryptography.Native.Android\Interop.X509.cs" />
364364
<Compile Include="System\Net\CertificateValidationPal.Android.cs" />
365-
<Compile Include="System\Net\Security\MD4.cs" />
366365
<Compile Include="System\Net\Security\Pal.Android\SafeDeleteSslContext.cs" />
367366
<Compile Include="System\Net\Security\Pal.Managed\SafeFreeSslCredentials.cs" />
368367
<Compile Include="System\Net\Security\Pal.Managed\SslProtocolsValidation.cs" />

src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
</ItemGroup>
2020
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Android'">
2121
<Compile Include="MD4Tests.cs" />
22-
<Compile Include="..\..\src\System\Net\Security\MD4.cs" />
22+
<Compile Include="$(CommonPath)System\Net\Security\MD4.cs"
23+
Link="ProductionCode\Common\System\Net\Security\MD4.cs" />
2324
</ItemGroup>
2425
<ItemGroup Condition="'$(TargetPlatformIdentifier)' != 'Browser'">
2526
<Compile Include="SslApplicationProtocolTests.cs" />

src/libraries/tests.proj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@
125125
<ItemGroup Condition="'$(TargetOS)' == 'Android' and '$(RunDisabledAndroidTests)' != 'true'">
126126
<!-- Tests time out intermittently -->
127127
<ProjectExclusions Include="$(MSBuildThisFileDirectory)Microsoft.Extensions.Hosting\tests\UnitTests\Microsoft.Extensions.Hosting.Unit.Tests.csproj" />
128-
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Net.Security\tests\FunctionalTests\System.Net.Security.Tests.csproj" />
129128

130129
<!-- Tests crash -->
131130
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Globalization\tests\Invariant\Invariant.Tests.csproj" />
@@ -284,7 +283,6 @@
284283
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Dynamic.Runtime/tests/System.Dynamic.Runtime.Tests.csproj" />
285284
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Linq.Expressions/tests/System.Linq.Expressions.Tests.csproj" />
286285
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Net.Requests/tests/System.Net.Requests.Tests.csproj" />
287-
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj" />
288286
<ProjectExclusions Include="$(RepoRoot)/src/tests/FunctionalTests/iOS/Simulator/PInvoke/iOS.Simulator.PInvoke.Test.csproj" />
289287
<ProjectExclusions Include="$(RepoRoot)/src/tests/FunctionalTests/tvOS/Simulator/AOT/tvOS.Simulator.Aot.Test.csproj" />
290288
</ItemGroup>

0 commit comments

Comments
 (0)