Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port MD4 managed implementation from mono/mono #62074

Merged
merged 51 commits into from
Jan 24, 2022
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
3fae34d
Port MD4 managed implementation from mono/mono
MaximLipnin Nov 26, 2021
8e27e96
Merge branch 'main' into port_md4_managed_impl
MaximLipnin Dec 6, 2021
ae9cad6
Move MD4 impl to System.Net.Security
MaximLipnin Dec 6, 2021
32d479d
Fix the namespace
MaximLipnin Dec 6, 2021
fa5e3a5
Make some methods static
MaximLipnin Dec 6, 2021
5c7c417
Port some portion of MD4 tests
MaximLipnin Dec 8, 2021
4c29645
Merge branch 'main' into port_md4_managed_impl
MaximLipnin Dec 9, 2021
daad017
Feedback
MaximLipnin Dec 10, 2021
e60bbb6
Merge branch 'main' into port_md4_managed_impl
MaximLipnin Dec 10, 2021
b40203e
Add Android as a target framework to build S.N.Security tests
MaximLipnin Dec 10, 2021
187f80a
Extract the MD4 tests to a separate test suite to not depend on Syste…
MaximLipnin Dec 10, 2021
43f231c
Add MD4 tests to the smoke tests item group to avoid running all the …
MaximLipnin Dec 11, 2021
173acfa
Feedback
MaximLipnin Dec 13, 2021
443f608
Merge branch 'main' into port_md4_managed_impl
MaximLipnin Jan 10, 2022
fea9889
Merge branch 'main' into port_md4_managed_impl
MaximLipnin Jan 11, 2022
c4a4c5e
Transient move from the instance methods to static ones.
MaximLipnin Jan 11, 2022
ad4b4a2
Merge branch 'main' into port_md4_managed_impl
MaximLipnin Jan 12, 2022
68b3492
Merge branch 'main' into port_md4_managed_impl
MaximLipnin Jan 13, 2022
40ae039
Add the "little-endian" case when encoding
MaximLipnin Jan 13, 2022
6d1fd3c
Transforming static fields to local variables
MaximLipnin Jan 13, 2022
7800933
feedback
MaximLipnin Jan 14, 2022
fff4d49
Merge branch 'main' into port_md4_managed_impl
MaximLipnin Jan 14, 2022
66b7e68
Transforming static fields to local variables
MaximLipnin Jan 14, 2022
b9aa97c
Merge branch 'main' into port_md4_managed_impl
MaximLipnin Jan 17, 2022
2af7ceb
Get rid of HashValue static field
MaximLipnin Jan 17, 2022
98a1f3d
Make HashFinal write to destination directly
MaximLipnin Jan 17, 2022
99cd7d6
Eliminate redundant arrays usage
MaximLipnin Jan 17, 2022
1809d0f
static field -> local
MaximLipnin Jan 17, 2022
913a8d6
static field -> local
MaximLipnin Jan 17, 2022
cf90c33
Move up constants
MaximLipnin Jan 17, 2022
2cebfdf
Rename input param
MaximLipnin Jan 17, 2022
40f136c
Put a local variable in place where it's neded
MaximLipnin Jan 17, 2022
956f8dd
Move the HashFinal logic to the HashData method
MaximLipnin Jan 17, 2022
6462885
Clean up
MaximLipnin Jan 17, 2022
002fdd0
Update the tests
MaximLipnin Jan 17, 2022
be92981
Merge branch 'main' into port_md4_managed_impl
MaximLipnin Jan 19, 2022
1d166b8
Remove redundant parameters
MaximLipnin Jan 19, 2022
980272d
Use Span's Slice+CopyTo methods
MaximLipnin Jan 19, 2022
5f23083
Use BinaryPrimitives.ReadUInt32LittleEndian when decoding
MaximLipnin Jan 19, 2022
9b993fb
Use BitOperations.RotateLeft instead of a custom method
MaximLipnin Jan 19, 2022
31cc0b5
Remove redundant parameter
MaximLipnin Jan 19, 2022
bc3d647
Merge branch 'main' into port_md4_managed_impl
MaximLipnin Jan 21, 2022
e1071c8
Add more test cases
MaximLipnin Jan 21, 2022
42afb5b
Inline/remove the BlockCopy method
MaximLipnin Jan 21, 2022
133f71d
Preserve the license header from Mono implementation
MaximLipnin Jan 21, 2022
a42224b
Fix spaces issue
MaximLipnin Jan 21, 2022
4723c99
Skip the block copying when it's unnecessary
MaximLipnin Jan 21, 2022
0f0b8ec
Move the MD4 tests over to System.Net.Security.Unit.Tests project
MaximLipnin Jan 21, 2022
589d3ed
Revert redundant change
MaximLipnin Jan 21, 2022
2bfb2b6
Revert adding a temp test suite to the smoke tests section
MaximLipnin Jan 21, 2022
9488c1a
Address Stephen's feedback
MaximLipnin Jan 23, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@
<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
230 changes: 230 additions & 0 deletions src/libraries/System.Net.Security/src/System/Net/Security/MD4.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
// 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.Binary;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;

//
// This class is a port of the Mono managed implementation of the MD4 algorithm
// and required to support NTLM in Android only.
// It's an implementation detail and is not intended to be a public API.
// Assuming that NTLM would be System.Net.Security, it makes sense to put MD4 here as well.
//
namespace System.Net.Security
{
internal sealed class MD4
{
private const int S11 = 3;
private const int S12 = 7;
private const int S13 = 11;
private const int S14 = 19;
private const int S21 = 3;
private const int S22 = 5;
private const int S23 = 9;
private const int S24 = 13;
private const int S31 = 3;
private const int S32 = 9;
private const int S33 = 11;
private const int S34 = 15;

internal static void HashData(ReadOnlySpan<byte> source, Span<byte> destination)
{
Debug.Assert(destination.Length == 128 >> 3);
Span<byte> buffer = stackalloc byte[64];
Span<uint> state = stackalloc uint[4];
Span<uint> count = stackalloc uint[2];

// the initialize our context
count[0] = 0;
count[1] = 0;
state[0] = 0x67452301;
state[1] = 0xefcdab89;
state[2] = 0x98badcfe;
state[3] = 0x10325476;
// Zeroize sensitive information
buffer.Fill(0);

HashCore(source, state, count, buffer);

/* Save number of bits */
Span<byte> bits = stackalloc byte[8];
Encode(bits, count);

/* Pad out to 56 mod 64. */
uint index = ((count[0] >> 3) & 0x3f);
int padLen = (int)((index < 56) ? (56 - index) : (120 - index));
Span<byte> padding = stackalloc byte[padLen];
padding[0] = 0x80;
HashCore(padding, state, count, buffer);

/* Append length (before padding) */
HashCore(bits, state, count, buffer);

/* Write state to destination */
Encode(destination, state);
}

private static void HashCore(ReadOnlySpan<byte> input, Span<uint> state, Span<uint> count, Span<byte> buffer)
{
/* Compute number of bytes mod 64 */
int index = (int)((count[0] >> 3) & 0x3F);
/* Update number of bits */
count[0] += (uint)(input.Length << 3);
if (count[0] < (input.Length << 3))
count[1]++;
count[1] += (uint)(input.Length >> 29);

int partLen = 64 - index;
int i = 0;
/* Transform as many times as possible. */
if (input.Length >= partLen)
{
BlockCopy(input, 0, buffer, index, partLen);
MD4Transform(state, buffer);

for (i = partLen; i + 63 < input.Length; i += 64)
{
MD4Transform(state, input.Slice(i));
}

index = 0;
}

/* Buffer remaining input */
input.Slice(i).CopyTo(buffer.Slice(index));
}

//--- private methods ---------------------------------------------------

private static void BlockCopy(ReadOnlySpan<byte> source, int srcOffset, Span<byte> destination, int dstOffset, int count)
{
for (int srcIndex = srcOffset, dstIndex = dstOffset; srcIndex < srcOffset + count; srcIndex++, dstIndex++)
{
destination[dstIndex] = source[srcIndex];
}
}

/* F, G and H are basic MD4 functions. */
private static uint F(uint x, uint y, uint z)
{
return (uint)(((x) & (y)) | ((~x) & (z)));
}

private static uint G(uint x, uint y, uint z)
{
return (uint)(((x) & (y)) | ((x) & (z)) | ((y) & (z)));
}

private static uint H(uint x, uint y, uint z)
{
return (uint)((x) ^ (y) ^ (z));
}

/* FF, GG and HH are transformations for rounds 1, 2 and 3 */
/* Rotation is separate from addition to prevent recomputation */
private static void FF(ref uint a, uint b, uint c, uint d, uint x, byte s)
{
a += F(b, c, d) + x;
a = BitOperations.RotateLeft(a, s);
}

private static void GG(ref uint a, uint b, uint c, uint d, uint x, byte s)
{
a += G(b, c, d) + x + 0x5a827999;
a = BitOperations.RotateLeft(a, s);
}

private static void HH(ref uint a, uint b, uint c, uint d, uint x, byte s)
{
a += H(b, c, d) + x + 0x6ed9eba1;
a = BitOperations.RotateLeft(a, s);
}

private static void Encode(Span<byte> output, Span<uint> input)
{
for (int i = 0, j = 0; j < output.Length; i++, j += 4)
{
BinaryPrimitives.WriteUInt32LittleEndian(output.Slice(j), input[i]);
}
}

private static void Decode(Span<uint> output, ReadOnlySpan<byte> input)
{
for (int i = 0, j = 0; i < output.Length; i++, j += 4)
{
output[i] = BinaryPrimitives.ReadUInt32LittleEndian(input.Slice(j));
}
}

private static void MD4Transform(Span<uint> state, ReadOnlySpan<byte> block)
{
uint a = state[0];
uint b = state[1];
uint c = state[2];
uint d = state[3];
Span<uint> x = stackalloc uint[16];

Decode(x, block);

/* Round 1 */
FF(ref a, b, c, d, x[0], S11); /* 1 */
FF(ref d, a, b, c, x[1], S12); /* 2 */
FF(ref c, d, a, b, x[2], S13); /* 3 */
FF(ref b, c, d, a, x[3], S14); /* 4 */
FF(ref a, b, c, d, x[4], S11); /* 5 */
FF(ref d, a, b, c, x[5], S12); /* 6 */
FF(ref c, d, a, b, x[6], S13); /* 7 */
FF(ref b, c, d, a, x[7], S14); /* 8 */
FF(ref a, b, c, d, x[8], S11); /* 9 */
FF(ref d, a, b, c, x[9], S12); /* 10 */
FF(ref c, d, a, b, x[10], S13); /* 11 */
FF(ref b, c, d, a, x[11], S14); /* 12 */
FF(ref a, b, c, d, x[12], S11); /* 13 */
FF(ref d, a, b, c, x[13], S12); /* 14 */
FF(ref c, d, a, b, x[14], S13); /* 15 */
FF(ref b, c, d, a, x[15], S14); /* 16 */

/* Round 2 */
GG(ref a, b, c, d, x[0], S21); /* 17 */
GG(ref d, a, b, c, x[4], S22); /* 18 */
GG(ref c, d, a, b, x[8], S23); /* 19 */
GG(ref b, c, d, a, x[12], S24); /* 20 */
GG(ref a, b, c, d, x[1], S21); /* 21 */
GG(ref d, a, b, c, x[5], S22); /* 22 */
GG(ref c, d, a, b, x[9], S23); /* 23 */
GG(ref b, c, d, a, x[13], S24); /* 24 */
GG(ref a, b, c, d, x[2], S21); /* 25 */
GG(ref d, a, b, c, x[6], S22); /* 26 */
GG(ref c, d, a, b, x[10], S23); /* 27 */
GG(ref b, c, d, a, x[14], S24); /* 28 */
GG(ref a, b, c, d, x[3], S21); /* 29 */
GG(ref d, a, b, c, x[7], S22); /* 30 */
GG(ref c, d, a, b, x[11], S23); /* 31 */
GG(ref b, c, d, a, x[15], S24); /* 32 */

HH(ref a, b, c, d, x[0], S31); /* 33 */
HH(ref d, a, b, c, x[8], S32); /* 34 */
HH(ref c, d, a, b, x[4], S33); /* 35 */
HH(ref b, c, d, a, x[12], S34); /* 36 */
HH(ref a, b, c, d, x[2], S31); /* 37 */
HH(ref d, a, b, c, x[10], S32); /* 38 */
HH(ref c, d, a, b, x[6], S33); /* 39 */
HH(ref b, c, d, a, x[14], S34); /* 40 */
HH(ref a, b, c, d, x[1], S31); /* 41 */
HH(ref d, a, b, c, x[9], S32); /* 42 */
HH(ref c, d, a, b, x[5], S33); /* 43 */
HH(ref b, c, d, a, x[13], S34); /* 44 */
HH(ref a, b, c, d, x[3], S31); /* 45 */
HH(ref d, a, b, c, x[11], S32); /* 46 */
HH(ref c, d, a, b, x[7], S33); /* 47 */
HH(ref b, c, d, a, x[15], S34); /* 48 */

state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<IncludeRemoteExecutor>true</IncludeRemoteExecutor>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser;$(NetCoreAppCurrent)-OSX;$(NetCoreAppCurrent)-iOS</TargetFrameworks>
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser;$(NetCoreAppCurrent)-OSX;$(NetCoreAppCurrent)-iOS;$(NetCoreAppCurrent)-Android</TargetFrameworks>
<Nullable>annotations</Nullable>
<IgnoreForCI Condition="'$(TargetOS)' == 'Browser'">true</IgnoreForCI>
<EnableDllImportGenerator>true</EnableDllImportGenerator>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// 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 Xunit;

namespace System.Net.Security.Tests
{
public class MD4Tests
{
// MD4("") = 31d6cfe0d16ae931b73c59d7e0c089c0
[Fact]
public void TryEncrypt_Empty()
{
ReadOnlySpan<byte> input = new byte[0];
ReadOnlySpan<byte> expected = new byte[] { 0x31, 0xd6, 0xcf, 0xe0, 0xd1, 0x6a, 0xe9, 0x31, 0xb7, 0x3c, 0x59, 0xd7, 0xe0, 0xc0, 0x89, 0xc0 };
Verify(input, expected);
}

// // MD4("a") = bde52cb31de33e46245e05fbdbd6fb24
[Fact]
public void TryEncrypt_SingleLetter()
{
ReadOnlySpan<byte> input = new ReadOnlySpan<byte>(Encoding.Default.GetBytes("a"));
ReadOnlySpan<byte> expected = new byte[] { 0xbd, 0xe5, 0x2c, 0xb3, 0x1d, 0xe3, 0x3e, 0x46, 0x24, 0x5e, 0x05, 0xfb, 0xdb, 0xd6, 0xfb, 0x24 };
Verify(input, expected);
}

// MD4("abc") = a448017aaf21d8525fc10ae87aa6729d
[Fact]
public void TryEncrypt_ThreeLetters()
{
ReadOnlySpan<byte> input = new ReadOnlySpan<byte>(Encoding.Default.GetBytes("abc"));
ReadOnlySpan<byte> expected = new byte[] { 0xa4, 0x48, 0x01, 0x7a, 0xaf, 0x21, 0xd8, 0x52, 0x5f, 0xc1, 0x0a, 0xe8, 0x7a, 0xa6, 0x72, 0x9d };
Verify(input, expected);
}

// MD4("message digest") = d9130a8164549fe818874806e1c7014b
[Fact]
public void TryEncrypt_Phrase()
{
ReadOnlySpan<byte> input = new ReadOnlySpan<byte>(Encoding.Default.GetBytes("message digest"));
ReadOnlySpan<byte> expected = new byte[] { 0xd9, 0x13, 0x0a, 0x81, 0x64, 0x54, 0x9f, 0xe8, 0x18, 0x87, 0x48, 0x06, 0xe1, 0xc7, 0x01, 0x4b };
Verify(input, expected);
}

// MD4("abcdefghijklmnopqrstuvwxyz") = d79e1c308aa5bbcdeea8ed63df412da9
[Fact]
public void TryEncrypt_AlphabetInLowercase()
{
ReadOnlySpan<byte> input = new ReadOnlySpan<byte>(Encoding.Default.GetBytes("abcdefghijklmnopqrstuvwxyz"));
ReadOnlySpan<byte> expected = new byte[] { 0xd7, 0x9e, 0x1c, 0x30, 0x8a, 0xa5, 0xbb, 0xcd, 0xee, 0xa8, 0xed, 0x63, 0xdf, 0x41, 0x2d, 0xa9 };
Verify(input, expected);
}

// MD4("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") = 043f8582f241db351ce627e153e7f0e4
[Fact]
public void TryEncrypt_AlphabetInUpperLowerCasesAndNumbers()
{
ReadOnlySpan<byte> input = new ReadOnlySpan<byte>((Encoding.Default.GetBytes("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")));
ReadOnlySpan<byte> expected = new byte[] { 0x04, 0x3f, 0x85, 0x82, 0xf2, 0x41, 0xdb, 0x35, 0x1c, 0xe6, 0x27, 0xe1, 0x53, 0xe7, 0xf0, 0xe4 };
Verify(input, expected);
}

// MD4("12345678901234567890123456789012345678901234567890123456789012345678901234567890") = e33b4ddc9c38f2199c3e7b164fcc0536
[Fact]
public void TryEncrypt_RepeatedSequenceOfNumbers()
{
ReadOnlySpan<byte> input = new ReadOnlySpan<byte>(Encoding.Default.GetBytes("12345678901234567890123456789012345678901234567890123456789012345678901234567890"));
ReadOnlySpan<byte> expected = new byte[] { 0xe3, 0x3b, 0x4d, 0xdc, 0x9c, 0x38, 0xf2, 0x19, 0x9c, 0x3e, 0x7b, 0x16, 0x4f, 0xcc, 0x05, 0x36 };
Verify(input, expected);
}

private void Verify(ReadOnlySpan<byte> input, ReadOnlySpan<byte> expected)
{
Span<byte> output = stackalloc byte[expected.Length];
MD4.HashData(input, output);
Assert.Equal(expected.ToArray(), output.ToArray());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent)-Android</TargetFrameworks>
</PropertyGroup>
<ItemGroup Condition="'$(TargetsAndroid)' == 'true'">
<Compile Include="MD4Tests.cs" />
<Compile Include="..\..\src\System\Net\Security\MD4.cs" />
</ItemGroup>
</Project>
3 changes: 3 additions & 0 deletions src/libraries/tests.proj
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,9 @@ Roslyn4.0.Tests.csproj" />
<ItemGroup Condition="'$(RunSmokeTestsOnly)' == 'true'">
<ProjectReference Include="$(MSBuildThisFileDirectory)System.Runtime\tests\System.Runtime.Tests.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(RunSmokeTestsOnly)' == 'true' and '$(TargetOS)' == 'Android'">
<ProjectReference Include="$(MSBuildThisFileDirectory)System.Net.Security\tests\InternalUnitTests\System.Net.Security.InternalUnit.Tests.csproj" />
</ItemGroup>

<ItemGroup Condition="'$(RunSmokeTestsOnly)' != 'true'">
<ProjectReference Include="$(MSBuildThisFileDirectory)*\tests\**\*.Tests.csproj"
Expand Down