Skip to content

Commit

Permalink
Managed implementation of NTLM for Android and tvOS (#66879)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
3 people committed Apr 19, 2022
1 parent 0b39dee commit a6508a0
Show file tree
Hide file tree
Showing 10 changed files with 1,074 additions and 14 deletions.
955 changes: 955 additions & 0 deletions src/libraries/Common/src/System/Net/NTAuthentication.Managed.cs

Large diffs are not rendered by default.

91 changes: 91 additions & 0 deletions src/libraries/Common/src/System/Net/Security/RC4.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

//
// ARC4Managed.cs: Alleged RC4(tm) compatible symmetric stream cipher
// RC4 is a trademark of RSA Security
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System;
using System.Buffers;
using System.Diagnostics;
using System.Security.Cryptography;

namespace System.Net.Security
{
// References:
// a. Usenet 1994 - RC4 Algorithm revealed
// http://www.qrst.de/html/dsds/rc4.htm
internal class RC4 : IDisposable
{
private byte[]? state;
private byte x;
private byte y;

public RC4(ReadOnlySpan<byte> key)
{
state = ArrayPool<byte>.Shared.Rent(256);

byte index1 = 0;
byte index2 = 0;

for (int counter = 0; counter < 256; counter++)
{
state[counter] = (byte)counter;
}

for (int counter = 0; counter < 256; counter++)
{
index2 = (byte)(key[index1] + state[counter] + index2);
(state[counter], state[index2]) = (state[index2], state[counter]);
index1 = (byte)((index1 + 1) % key.Length);
}
}

public void Dispose()
{
if (state != null)
{
x = 0;
y = 0;
CryptographicOperations.ZeroMemory(state.AsSpan(0, 256));
ArrayPool<byte>.Shared.Return(state);
state = null;
}
}

public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
Debug.Assert(input.Length == output.Length);
Debug.Assert(state != null);

for (int counter = 0; counter < input.Length; counter++)
{
x = (byte)(x + 1);
y = (byte)(state[x] + y);
(state[x], state[y]) = (state[y], state[x]);
byte xorIndex = (byte)(state[x] + state[y]);
output[counter] = (byte)(input[counter] ^ state[xorIndex]);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace System.Net.Security
[StructLayout(LayoutKind.Sequential)]
internal struct SecChannelBindings
{
internal int InitiatorAddrType;
internal int InitiatorLength;
internal int InitiatorOffset;
internal int AcceptorAddrType;
Expand Down
3 changes: 3 additions & 0 deletions src/libraries/System.Net.Http/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,9 @@
<data name="net_nego_protection_level_not_supported" xml:space="preserve">
<value>Requested protection level is not supported with the GSSAPI implementation currently installed.</value>
</data>
<data name="net_nego_mechanism_not_supported" xml:space="preserve">
<value>The security package '{0}' is not supported.</value>
</data>
<data name="net_context_buffer_too_small" xml:space="preserve">
<value>Insufficient buffer space. Required: {0} Actual: {1}.</value>
</data>
Expand Down
23 changes: 14 additions & 9 deletions src/libraries/System.Net.Http/src/System.Net.Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<!-- DesignTimeBuild requires all the TargetFramework Derived Properties to not be present in the first property group. -->
<PropertyGroup>
<TargetPlatformIdentifier>$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))</TargetPlatformIdentifier>
<UseManagedNtlm Condition="'$(UseManagedNtlm)' == '' and ('$(TargetPlatformIdentifier)' == 'Android' or '$(TargetPlatformIdentifier)' == 'tvOS')">true</UseManagedNtlm>
<GeneratePlatformNotSupportedAssemblyMessage Condition="'$(TargetPlatformIdentifier)' == ''">SR.PlatformNotSupported_NetHttp</GeneratePlatformNotSupportedAssemblyMessage>
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'OSX' or '$(TargetPlatformIdentifier)' == 'iOS' or '$(TargetPlatformIdentifier)' == 'tvOS' or '$(TargetPlatformIdentifier)' == 'MacCatalyst'">$(DefineConstants);SYSNETHTTP_NO_OPENSSL</DefineConstants>
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'Android' or '$(TargetPlatformIdentifier)' == 'iOS' or '$(TargetPlatformIdentifier)' == 'MacCatalyst' or '$(TargetPlatformIdentifier)' == 'tvOS'">$(DefineConstants);TARGET_MOBILE</DefineConstants>
Expand Down Expand Up @@ -159,7 +160,7 @@
<Compile Include="System\Net\Http\Headers\KnownHeader.Http2And3.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\AuthenticationHelper.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\AuthenticationHelper.Digest.cs" />
<Compile Condition="'$(TargetPlatformIdentifier)' != 'tvOS'" Include="System\Net\Http\SocketsHttpHandler\AuthenticationHelper.NtAuth.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\AuthenticationHelper.NtAuth.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\ChunkedEncodingReadStream.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\ChunkedEncodingWriteStream.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\ConnectHelper.cs" />
Expand Down Expand Up @@ -208,7 +209,7 @@
<Compile Include="System\Net\Http\SocketsHttpHandler\SystemProxyInfo.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\SocksHelper.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\SocksException.cs" />
<Compile Condition="'$(TargetPlatformIdentifier)' != 'tvOS'" Include="$(CommonPath)System\Net\NTAuthentication.Common.cs"
<Compile Condition="'$(UseManagedNtlm)' != 'true'" Include="$(CommonPath)System\Net\NTAuthentication.Common.cs"
Link="Common\System\Net\NTAuthentication.Common.cs" />
<Compile Include="$(CommonPath)System\Net\ContextFlagsPal.cs"
Link="Common\System\Net\ContextFlagsPal.cs" />
Expand Down Expand Up @@ -346,7 +347,7 @@
<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" />
</ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' != '' and '$(TargetPlatformIdentifier)' != 'windows' and '$(TargetPlatformIdentifier)' != 'Browser' and '$(TargetPlatformIdentifier)' != 'tvOS'">
<ItemGroup Condition="'$(TargetPlatformIdentifier)' != '' and '$(TargetPlatformIdentifier)' != 'windows' and '$(TargetPlatformIdentifier)' != 'Browser' and '$(UseManagedNtlm)' != 'true'">
<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.GssBuffer.cs"
Expand All @@ -362,12 +363,13 @@
<Compile Include="$(CommonPath)System\Net\Security\NegotiateStreamPal.Unix.cs"
Link="Common\System\Net\Security\NegotiateStreamPal.Unix.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'tvOS'">
<Compile Include="System\Net\Http\SocketsHttpHandler\AuthenticationHelper.NtAuth.tvOS.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\GssSafeHandles.PlatformNotSupported.cs"
Link="Common\Microsoft\Win32\SafeHandles\GssSafeHandles.PlatformNotSupported.cs" />
<Compile Include="$(CommonPath)System\Net\Security\NegotiateStreamPal.PlatformNotSupported.cs"
Link="Common\System\Net\Security\NegotiateStreamPal.PlatformNotSupported.cs" />
<ItemGroup Condition="'$(UseManagedNtlm)' == 'true'">
<Compile Include="$(CommonPath)System\Net\NTAuthentication.Managed.cs"
Link="Common\System\Net\NTAuthentication.Managed.cs" />
<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" />
</ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' != '' and '$(TargetPlatformIdentifier)' != 'windows' and '$(TargetPlatformIdentifier)' != 'Browser' and '$(TargetPlatformIdentifier)' != 'OSX' and '$(TargetPlatformIdentifier)' != 'iOS' and '$(TargetPlatformIdentifier)' != 'tvOS'">
<Compile Include="System\Net\Http\SocketsHttpHandler\HttpNoProxy.cs" />
Expand Down Expand Up @@ -675,6 +677,9 @@
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Browser'">
<ProjectReference Include="$(LibrariesProjectRoot)System.Private.Runtime.InteropServices.JavaScript\src\System.Private.Runtime.InteropServices.JavaScript.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(UseManagedNtlm)' == 'true'">
<ProjectReference Include="$(LibrariesProjectRoot)System.Formats.Asn1\src\System.Formats.Asn1.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\SR.resx" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,11 +187,18 @@ private static async Task<HttpResponseMessage> SendWithNtAuthAsync(HttpRequestMe
SetRequestAuthenticationHeaderValue(request, new AuthenticationHeaderValue(challenge.SchemeName, challengeResponse), isProxyAuth);

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

if (!IsAuthenticationChallenge(response, isProxyAuth))
{
// Tail response for Negoatiate on successful authentication. Validate it before we proceed.
authContext.GetOutgoingBlob(challengeData);
break;
}

needDrain = true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,6 @@
<Compile Include="$(CommonPath)Interop\Android\System.Security.Cryptography.Native.Android\Interop.X509.cs"
Link="Common\Interop\Android\System.Security.Cryptography.Native.Android\Interop.X509.cs" />
<Compile Include="System\Net\CertificateValidationPal.Android.cs" />
<Compile Include="System\Net\Security\MD4.cs" />
<Compile Include="System\Net\Security\Pal.Android\SafeDeleteSslContext.cs" />
<Compile Include="System\Net\Security\Pal.Managed\SafeFreeSslCredentials.cs" />
<Compile Include="System\Net\Security\Pal.Managed\SslProtocolsValidation.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Android'">
<Compile Include="MD4Tests.cs" />
<Compile Include="..\..\src\System\Net\Security\MD4.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" />
Expand Down
2 changes: 0 additions & 2 deletions src/libraries/tests.proj
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,6 @@
<ItemGroup Condition="'$(TargetOS)' == 'Android' and '$(RunDisabledAndroidTests)' != 'true'">
<!-- Tests time out intermittently -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)Microsoft.Extensions.Hosting\tests\UnitTests\Microsoft.Extensions.Hosting.Unit.Tests.csproj" />
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Net.Security\tests\FunctionalTests\System.Net.Security.Tests.csproj" />

<!-- Tests crash -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Globalization\tests\Invariant\Invariant.Tests.csproj" />
Expand Down Expand Up @@ -284,7 +283,6 @@
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Dynamic.Runtime/tests/System.Dynamic.Runtime.Tests.csproj" />
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Linq.Expressions/tests/System.Linq.Expressions.Tests.csproj" />
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Net.Requests/tests/System.Net.Requests.Tests.csproj" />
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj" />
<ProjectExclusions Include="$(RepoRoot)/src/tests/FunctionalTests/iOS/Simulator/PInvoke/iOS.Simulator.PInvoke.Test.csproj" />
<ProjectExclusions Include="$(RepoRoot)/src/tests/FunctionalTests/tvOS/Simulator/AOT/tvOS.Simulator.Aot.Test.csproj" />
</ItemGroup>
Expand Down

0 comments on commit a6508a0

Please sign in to comment.