diff --git a/src/libraries/System.Net.Security/src/System.Net.Security.csproj b/src/libraries/System.Net.Security/src/System.Net.Security.csproj index 9f90dbd3951f05..047cbdcc98aea9 100644 --- a/src/libraries/System.Net.Security/src/System.Net.Security.csproj +++ b/src/libraries/System.Net.Security/src/System.Net.Security.csproj @@ -363,6 +363,7 @@ + diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/MD4.cs b/src/libraries/System.Net.Security/src/System/Net/Security/MD4.cs new file mode 100644 index 00000000000000..bdf3547912c911 --- /dev/null +++ b/src/libraries/System.Net.Security/src/System/Net/Security/MD4.cs @@ -0,0 +1,256 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// +// MD4.cs - Message Digest 4 Abstract class +// +// Author: +// Sebastien Pouliot (sebastien@xamarin.com) +// +// (C) 2003 Motus Technologies Inc. (http://www.motus.com) +// Copyright 2013 Xamarin Inc. (http://www.xamarin.com) +// + +// +// 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.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 source, Span destination) + { + Debug.Assert(destination.Length == 128 >> 3); + + Span buffer = stackalloc byte[64]; + buffer.Clear(); + // Initialize the context + Span state = stackalloc uint[4] { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 }; + Span count = stackalloc uint[2] { 0, 0 }; + + HashCore(source, state, count, buffer); + + // Save number of bits + Span 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 padding = stackalloc byte[padLen]; + padding.Clear(); + 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 input, Span state, Span count, Span 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) + { + if (index != 0) + { + input.Slice(0, partLen).CopyTo(buffer.Slice(index)); + MD4Transform(state, buffer); + index = 0; + } + else + { + partLen = 0; + } + + for (i = partLen; i + 63 < input.Length; i += 64) + { + MD4Transform(state, input.Slice(i)); + } + } + + // Buffer remaining input + input.Slice(i).CopyTo(buffer.Slice(index)); + } + + //--- private methods --------------------------------------------------- + + // 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 output, Span 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 output, ReadOnlySpan 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 state, ReadOnlySpan block) + { + uint a = state[0]; + uint b = state[1]; + uint c = state[2]; + uint d = state[3]; + Span 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; + } + } +} diff --git a/src/libraries/System.Net.Security/tests/UnitTests/MD4Tests.cs b/src/libraries/System.Net.Security/tests/UnitTests/MD4Tests.cs new file mode 100644 index 00000000000000..a3667ebb896e54 --- /dev/null +++ b/src/libraries/System.Net.Security/tests/UnitTests/MD4Tests.cs @@ -0,0 +1,158 @@ +// 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 input = new byte[0]; + ReadOnlySpan 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 input = new ReadOnlySpan(Encoding.Default.GetBytes("a")); + ReadOnlySpan 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 input = new ReadOnlySpan(Encoding.Default.GetBytes("abc")); + ReadOnlySpan 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 input = new ReadOnlySpan(Encoding.Default.GetBytes("message digest")); + ReadOnlySpan 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 input = new ReadOnlySpan(Encoding.Default.GetBytes("abcdefghijklmnopqrstuvwxyz")); + ReadOnlySpan 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 input = new ReadOnlySpan((Encoding.Default.GetBytes("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"))); + ReadOnlySpan 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 input = new ReadOnlySpan(Encoding.Default.GetBytes("12345678901234567890123456789012345678901234567890123456789012345678901234567890")); + ReadOnlySpan expected = new byte[] { 0xe3, 0x3b, 0x4d, 0xdc, 0x9c, 0x38, 0xf2, 0x19, 0x9c, 0x3e, 0x7b, 0x16, 0x4f, 0xcc, 0x05, 0x36 }; + Verify(input, expected); + } + + // MD4("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012") = 14fdf2056bf88b3491c385d8ac4f48e6 + // 55 bytes (padLen == 56 - 55 => 1) + [Fact] + public void TryEncrypt_55bytes_HitsEdgeCaseForPaddingLength() + { + ReadOnlySpan input = new ReadOnlySpan(Encoding.Default.GetBytes("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012")); + ReadOnlySpan expected = new byte[] { 0x14, 0xfd, 0xf2, 0x05, 0x6b, 0xf8, 0x8b, 0x34, 0x91, 0xc3, 0x85, 0xd8, 0xac, 0x4f, 0x48, 0xe6 }; + Verify(input, expected); + } + + // MD4("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123") = db837dbb6098a50a2d3974bc1cc76133 + // 56 bytes (padLen == 120 - 56 => 64) + [Fact] + public void TryEncrypt_56bytes_HitsEdgeCaseForPaddingLength() + { + ReadOnlySpan input = new ReadOnlySpan(Encoding.Default.GetBytes("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123")); + ReadOnlySpan expected = new byte[] { 0xdb, 0x83, 0x7d, 0xbb, 0x60, 0x98, 0xa5, 0x0a, 0x2d, 0x39, 0x74, 0xbc, 0x1c, 0xc7, 0x61, 0x33 }; + Verify(input, expected); + } + + // MD4("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890") = ce64c40ecfbe896462f3c1a925884624 + [Fact] + public void TryEncrypt_63bytes_HitsEdgeCase() + { + ReadOnlySpan input = new ReadOnlySpan(Encoding.Default.GetBytes("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890")); + ReadOnlySpan expected = new byte[] { 0xce, 0x64, 0xc4, 0x0e, 0xcf, 0xbe, 0x89, 0x64, 0x62, 0xf3, 0xc1, 0xa9, 0x25, 0x88, 0x46, 0x24 }; + Verify(input, expected); + } + + // MD4("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012345678901") = 4b0e77758d2ede1eb21d267d492ae70b + [Fact] + public void TryEncrypt_64bytes_HitsEdgeCase() + { + ReadOnlySpan input = new ReadOnlySpan(Encoding.Default.GetBytes("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012345678901")); + ReadOnlySpan expected = new byte[] { 0x4b, 0x0e, 0x77, 0x75, 0x8d, 0x2e, 0xde, 0x1e, 0xb2, 0x1d, 0x26, 0x7d, 0x49, 0x2a, 0xe7, 0x0b }; + Verify(input, expected); + } + + // MD4("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789012") = 3b46ad159b3fd800d254e3c4cc71fe36 + [Fact] + public void TryEncrypt_65bytes_HitsEdgeCase() + { + ReadOnlySpan input = new ReadOnlySpan(Encoding.Default.GetBytes("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789012")); + ReadOnlySpan expected = new byte[] { 0x3b, 0x46, 0xad, 0x15, 0x9b, 0x3f, 0xd8, 0x00, 0xd2, 0x54, 0xe3, 0xc4, 0xcc, 0x71, 0xfe, 0x36 }; + Verify(input, expected); + } + + // MD4("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012345678901ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890") = a3e23048e4ade47a0f00fa8aed2a0248 + [Fact] + public void TryEncrypt_127bytes_HitsEdgeCase() + { + ReadOnlySpan input = new ReadOnlySpan(Encoding.Default.GetBytes("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012345678901ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890")); + ReadOnlySpan expected = new byte[] { 0xa3, 0xe2, 0x30, 0x48, 0xe4, 0xad, 0xe4, 0x7a, 0x0f, 0x00, 0xfa, 0x8a, 0xed, 0x2a, 0x02, 0x48 }; + Verify(input, expected); + } + + // MD4("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012345678901ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012345678901") = de49da96c105be37b242f2bee86c4759 + [Fact] + public void TryEncrypt_128bytes_HitsEdgeCase() + { + ReadOnlySpan input = new ReadOnlySpan(Encoding.Default.GetBytes("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012345678901ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012345678901")); + ReadOnlySpan expected = new byte[] { 0xde, 0x49, 0xda, 0x96, 0xc1, 0x05, 0xbe, 0x37, 0xb2, 0x42, 0xf2, 0xbe, 0xe8, 0x6c, 0x47, 0x59 }; + Verify(input, expected); + } + + // MD4("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012345678901ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789012") = a6c10c320f8827d08248a2d8b124b040 + [Fact] + public void TryEncrypt_129bytes_HitsEdgeCase() + { + ReadOnlySpan input = new ReadOnlySpan(Encoding.Default.GetBytes("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012345678901ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789012")); + ReadOnlySpan expected = new byte[] { 0xa6, 0xc1, 0x0c, 0x32, 0x0f, 0x88, 0x27, 0xd0, 0x82, 0x48, 0xa2, 0xd8, 0xb1, 0x24, 0xb0, 0x40 }; + Verify(input, expected); + } + + private void Verify(ReadOnlySpan input, ReadOnlySpan expected) + { + Span output = stackalloc byte[expected.Length]; + MD4.HashData(input, output); + Assert.Equal(expected.ToArray(), output.ToArray()); + } + } +} 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 da4dde4e192b90..28b3f81b02b3d7 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 @@ -10,13 +10,17 @@ 436 $(NoWarn);3021 - $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser;$(NetCoreAppCurrent)-OSX;$(NetCoreAppCurrent)-iOS + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Browser;$(NetCoreAppCurrent)-OSX;$(NetCoreAppCurrent)-iOS;$(NetCoreAppCurrent)-Android annotations true + + + +