Skip to content

Commit 1b7185a

Browse files
committed
test(mssql): add unit tests for NTLMv2 handshake logic
1 parent 1290857 commit 1b7185a

3 files changed

Lines changed: 65 additions & 3 deletions

File tree

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
<Authors>vkuttyp</Authors>
88
<PackageLicenseExpression>MIT</PackageLicenseExpression>
99
<RepositoryUrl>https://github.com/vkuttyp/CosmoSQLClient-Dotnet</RepositoryUrl>
10-
<Version>1.6.0</Version>
10+
<Version>1.6.1</Version>
1111
</PropertyGroup>
1212
</Project>

src/CosmoSQLClient.MsSql/Auth/NtlmAuth.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace CosmoSQLClient.MsSql.Auth;
77
/// Implements the NTLMv2 authentication protocol used by Windows domain auth.
88
/// Ported from CosmoSQLClient-Swift.
99
/// </summary>
10-
internal static class NtlmAuth
10+
public static class NtlmAuth
1111
{
1212
private static readonly byte[] NtlmSignature = { 0x4E, 0x54, 0x4C, 0x4D, 0x53, 0x53, 0x50, 0x00 };
1313
private const uint NegotiateFlags = 0x62088235;
@@ -175,7 +175,7 @@ private static byte[] ComputeHmacMd5(byte[] key, byte[] data)
175175
}
176176
}
177177

178-
internal static class MD4
178+
public static class MD4
179179
{
180180
public static byte[] Hash(byte[] input)
181181
{
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System.Text;
2+
using CosmoSQLClient.MsSql.Auth;
3+
using Xunit;
4+
5+
namespace CosmoSQLClient.MsSql.Tests;
6+
7+
public class NtlmAuthTests
8+
{
9+
[Fact]
10+
public void TestMD4_EmptyString()
11+
{
12+
var result = MD4.Hash(Array.Empty<byte>());
13+
Assert.Equal("31d6cfe0d16ae931b73c59d7e0c089c0", ToHex(result));
14+
}
15+
16+
[Fact]
17+
public void TestMD4_Message()
18+
{
19+
var result = MD4.Hash(Encoding.ASCII.GetBytes("a"));
20+
Assert.Equal("bde52cb31de33e46245e05fbdbd6fb24", ToHex(result));
21+
}
22+
23+
[Fact]
24+
public void TestMD4_LongString()
25+
{
26+
var input = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
27+
var result = MD4.Hash(Encoding.ASCII.GetBytes(input));
28+
Assert.Equal("043f8582f241db351ce627e153e7f0e4", ToHex(result));
29+
}
30+
31+
[Fact]
32+
public void TestNtlmExchange_Structure()
33+
{
34+
// 1. Test Negotiate
35+
var negotiate = NtlmAuth.BuildNegotiate();
36+
Assert.StartsWith("NTLMSSP", Encoding.ASCII.GetString(negotiate));
37+
Assert.Equal(1u, BitConverter.ToUInt32(negotiate, 8)); // Type 1
38+
39+
// 2. Test Challenge Parsing (using a synthetic challenge)
40+
var challengeBytes = new byte[64];
41+
Encoding.ASCII.GetBytes("NTLMSSP\0").CopyTo(challengeBytes, 0);
42+
BitConverter.GetBytes(2u).CopyTo(challengeBytes, 8); // Type 2
43+
var serverChallenge = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
44+
serverChallenge.CopyTo(challengeBytes, 24);
45+
BitConverter.GetBytes(56).CopyTo(challengeBytes, 44); // TargetInfo Offset
46+
47+
var parsed = NtlmAuth.ParseChallenge(challengeBytes);
48+
Assert.Equal(serverChallenge, parsed.ServerChallenge);
49+
50+
// 3. Test Authenticate (Type 3)
51+
var auth = NtlmAuth.BuildAuthenticate(challengeBytes, "User", "Password", "Domain", "WORKSTATION");
52+
Assert.StartsWith("NTLMSSP", Encoding.ASCII.GetString(auth));
53+
Assert.Equal(3u, BitConverter.ToUInt32(auth, 8)); // Type 3
54+
}
55+
56+
private static string ToHex(byte[] bytes)
57+
{
58+
var sb = new StringBuilder();
59+
foreach (var b in bytes) sb.Append(b.ToString("x2"));
60+
return sb.ToString();
61+
}
62+
}

0 commit comments

Comments
 (0)