766 lines
28 KiB
Plaintext
766 lines
28 KiB
Plaintext
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Microsoft Research Singularity
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// Note:
|
|
//
|
|
// This app is a unit test for the NTLM authentication library. It can
|
|
// be run in two modes: local, and remote.
|
|
//
|
|
// In local mode, the program runs through a hard-coded list of known
|
|
// NTLM inputs (password and challenge) and outputs (NT and LM response).
|
|
// It uses the known inputs and computes the outputs, then compares the
|
|
// known (desired) outputs and compares them with the actual, computed
|
|
// outputs. If a difference is detected, then the NTLM implementation
|
|
// is broken.
|
|
//
|
|
// In remote mode, this program uses TCP/IP to communicate with a Windows
|
|
// machine, which must be running the NtlmWinHost program. (NtlmWinHost
|
|
// lives in Singularity/Windows/UnitTests.) NtlmWinHost listens for TCP/IP
|
|
// connections from NtlmUnitTest, receives NTLM Negotiate requests, and
|
|
// performs an NTLM exchange. The user must specify a valid account name
|
|
// and password. "Valid" means that the account name and password are
|
|
// known to (or resolvable by) the Windows host machine. Typically, this
|
|
// is a test account created on the Windows machine, NOT your Corpnet
|
|
// domain credentials!
|
|
//
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
using System;
|
|
using System.Runtime.InteropServices;
|
|
using System.Collections;
|
|
using System.Diagnostics;
|
|
using System.Net.IP;
|
|
using System.Net;
|
|
using System.Text;
|
|
using System.Net.Sockets;
|
|
|
|
using Microsoft.Contracts;
|
|
using Microsoft.Singularity;
|
|
using Microsoft.Singularity.Channels;
|
|
using Microsoft.Singularity.Directory;
|
|
using Microsoft.Singularity.Applications;
|
|
using Microsoft.Singularity.Io;
|
|
using Microsoft.Singularity.Configuration;
|
|
using Microsoft.Singularity.Security;
|
|
using Microsoft.SingSharp;
|
|
using Microsoft.SingSharp.Reflection;
|
|
|
|
using System.Security.Protocols.Ntlm;
|
|
|
|
[assembly: Transform(typeof(ApplicationResourceTransform))]
|
|
|
|
[ConsoleCategory(HelpMessage="Runs offline BVT for NTLM authentication library", DefaultAction=true)]
|
|
internal class DefaultCommand
|
|
{
|
|
[InputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdin;
|
|
|
|
[OutputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdout;
|
|
|
|
reflective internal DefaultCommand();
|
|
|
|
static void WriteLine(string! line)
|
|
{
|
|
DebugStub.WriteLine(line);
|
|
Console.WriteLine(line);
|
|
}
|
|
|
|
static void WriteLine(string! format, params object[]! list)
|
|
{
|
|
WriteLine(String.Format(format, list));
|
|
}
|
|
|
|
internal int AppMain()
|
|
{
|
|
Console.WriteLine("Checking known responses:");
|
|
|
|
int failureCount = 0;
|
|
|
|
foreach (NtlmKnownResponse known in KnownResponses)
|
|
{
|
|
string! line = String.Format("password: {0,-20} challenge: {1} LM response: {2} NT response: {3}",
|
|
"'" + known.Password + "'",
|
|
known.Challenge,
|
|
known.LmResponse,
|
|
known.NtResponse);
|
|
WriteLine(line);
|
|
|
|
byte[]! known_challenge = Util.HexStringToByteArray(known.Challenge);
|
|
byte[]! known_lm_response = Util.HexStringToByteArray(known.LmResponse);
|
|
byte[]! known_nt_response = Util.HexStringToByteArray(known.NtResponse);
|
|
|
|
byte[]! computed_lm_response = NtlmSupplicant.ComputeLmResponse(known_challenge, known.Password);
|
|
byte[]! computed_nt_response = NtlmSupplicant.ComputeNtResponse(known_challenge, known.Password);
|
|
|
|
if (Util.CompareArraySpans(known_lm_response, computed_lm_response) != 0) {
|
|
WriteLine(" FAILURE: Computed LM response differs: " + Util.ByteArrayToString(computed_lm_response));
|
|
failureCount++;
|
|
}
|
|
|
|
if (Util.CompareArraySpans(known_nt_response, computed_nt_response) != 0) {
|
|
WriteLine(" FAILURE: Computed NT response differs: " + Util.ByteArrayToString(computed_lm_response));
|
|
failureCount++;
|
|
}
|
|
}
|
|
|
|
if (failureCount == 0) {
|
|
WriteLine("All computed responses match known good values. Test passes.");
|
|
return 0;
|
|
}
|
|
else {
|
|
WriteLine("Failures: " + failureCount);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static NtlmKnownResponse[] KnownResponses = {
|
|
// These were generated using the @gen command, below.
|
|
new NtlmKnownResponse("2c87546b170c54bf", "Z_bE`", "16b291e268a4e19a23b36eeeb685c02260391bc2827fc05d", "43f56c81a2805383a7622fe0c170ad659b04f25dbeedd6a6"),
|
|
new NtlmKnownResponse("8fd1f7048f3dee73", "Z_bE`", "c2d6e331411a109341a9ba76d10506d03b262fcf3f473e9d", "cbc03bbe559766a6807db7b9d60c68d26a795c5a20a2a016"),
|
|
new NtlmKnownResponse("22abc82a344df2de", "Z_bE`", "931fa8a43c90dc2797f0335b8882c4f5593444cf3dc1b643", "652e822eaa467371770180fdc771d1dddff1cd902f144c74"),
|
|
new NtlmKnownResponse("c3a7fe9867b8c99e", "Z_bE`", "daa43f8730d593d7a4f6cfe542fb2e28102fa87b84365267", "0f8a1c55eda6cb21908be3d165011616a141e0d36af3e9d4"),
|
|
new NtlmKnownResponse("1b08c97f49cb7c01", "ra%Z)ZE&},t", "142b2855d2786061ab295d14e39f871473d949df16536005", "0cfc316813c5423f927fe9892ccaaa451068224648afcbeb"),
|
|
new NtlmKnownResponse("ed3c7bdf9575a770", "ra%Z)ZE&},t", "c1f1055ae66caf6eecc70b191b006536ed7e1337abafe85f", "d2149ecb23779f255774559bc6b09a40da0b67f764a7fc61"),
|
|
new NtlmKnownResponse("0dbc57d708580cb0", "ra%Z)ZE&},t", "ba09f4b51d59efba03e931788c5914dc06fc578b9e85d090", "812298742928dcd6711b4c932108897528e45dbca9463a40"),
|
|
new NtlmKnownResponse("89dfd5090d601936", "ra%Z)ZE&},t", "ce82f6b3341f1cdd425bdf44953bf83452fd0efa25360264", "e99a1428fd64d46cb9e771bba3aaf315f5fb8c8780cd37a6"),
|
|
new NtlmKnownResponse("4f9c3bc7b0ba63d4", "$gdqN", "4b21b010c976506aecd6da792faa4e6efd7083e5b953de5f", "bbec0eb714b01e4146dcc538215d2ebe011d0f209b2da2d3"),
|
|
new NtlmKnownResponse("7928722e5955a64b", "$gdqN", "01e1b5ef4bfcf849521454e5b5d2942b181951c33efe3473", "2f763a9ee207803bad0011081a3d25332b044822b2cf00a1"),
|
|
new NtlmKnownResponse("dec3fb19f56af673", "$gdqN", "ff555fc0302beb74f637ccbe6e17e8ce65a3861552512d6c", "c369bf569205073d7aecdb02df6cf3977e9dda975f5bbce0"),
|
|
new NtlmKnownResponse("f48d2345b489b2eb", "$gdqN", "40a4e20fa00d72a301f856a658ef0ff642b2d89a633567ef", "f9c74c29211779384b699da935e5570bdb4ae6d4457767df"),
|
|
new NtlmKnownResponse("b565ac97df07c48f", "THdY7]>;", "41fa9c2d34c75683135620def7a5e3c3aeb7320d21bd6148", "ebe810de662ab1a09d5b25f4f671ec1be89710b874c3185f"),
|
|
new NtlmKnownResponse("df2dffc0c489e4a6", "THdY7]>;", "8a1e615c5b0d0a1de96094b5fa165c7461aa496f0cc25267", "00620bb34513503d449999a55e587b84ce87fd9a7b972549"),
|
|
new NtlmKnownResponse("c8d323961e1fef75", "THdY7]>;", "6a99cd29b2bfdbf78d594aafca00a612408de2fd0de1065c", "c8b651d08eb804c63ebd18d26f5b70522b6a0f38936771ee"),
|
|
new NtlmKnownResponse("869bcae8ebaf9d1a", "THdY7]>;", "5efdb0fcac134c8a7db9a5716c923d6414439975052947ab", "259fee21ae873b1b5316a8bac744ebc361a9b08e20287d5e"),
|
|
new NtlmKnownResponse("d55cb32bcd058815", "k7!;p'QCb", "151a0d8ee9d3c57f4e150caf18f0b9fcfff69d67e5ea5cf8", "d09d2158bf15e85bde1cc2972bb3439cdff15cab1c460d5c"),
|
|
new NtlmKnownResponse("d36d84bb2d967536", "k7!;p'QCb", "96d5a808abdfe8cc19c9e8623af7f11208d176d5190c0fa9", "5f541049ac3a839126da2321d0109d301e3a4513fdb98689"),
|
|
new NtlmKnownResponse("10433cdba4308fe4", "k7!;p'QCb", "d6b4d9da498c332f3be34e4ad3d6559ad606c4d39573258f", "1d2020a20240db913bf8d7722ca208eb08c11dcbd1e1158e"),
|
|
new NtlmKnownResponse("17f649df5cecbe9d", "k7!;p'QCb", "b25e6ab956567240b86916024c7d5b7a047ef706f1549245", "e2ca8cd6f3d54aabc961552da96b9c575fb60de8a72ccde7"),
|
|
new NtlmKnownResponse("cec55fd6db3096ed", "F;VgA", "b18e9cc93374068a20957c5c8b72be0fd8fa5a0f1c16caf2", "9e12c04257e01bd265bd587967ce27b77e7ac6426b0be1f0"),
|
|
new NtlmKnownResponse("9fa98f13cad14983", "F;VgA", "a5578f61bdfc14b353754c81cff647364605146c9756d3ea", "64e796cbceb4fc5514370b4aa7b40cfc8c9ffb63acb78cca"),
|
|
new NtlmKnownResponse("11a5cdce13d6bba8", "F;VgA", "fd7bc9bcf082d0afdd7894fe530bf59e2911a2297095f7e3", "49f4ed6fe661adaab06b235bb119da63e9f0d2c15d4e0703"),
|
|
new NtlmKnownResponse("b8e6aee684c54ce2", "F;VgA", "f5234fdcdf683d4a11b0379a36dec631566b7a0ec22dec60", "dffd5accafddebcb55dffd0662a57eef238aadea67ef96c8"),
|
|
new NtlmKnownResponse("4e2956a0504822ed", "bKKMk?B", "451ada0c60f1e1b397463a1784349ad93b46071e6d38acce", "7519f4aac3c0ad7bf26d9a482712efddceeb2fec4ac3d049"),
|
|
new NtlmKnownResponse("54b356a4c5f447f7", "bKKMk?B", "cabbf9c6952bf1fad2b6347c371613cd0be114a6d8677f1c", "606b433725d633065d1cb883cef1f88c3e95af1a75bd5b28"),
|
|
new NtlmKnownResponse("0ab71df482af69da", "bKKMk?B", "913b755617905bd71d0fa0db6e6ed6c77bc601cb4ac420b1", "488537a0be68c967ba7641c84675789253f3fec251f09b78"),
|
|
new NtlmKnownResponse("5dac9f155cd20743", "bKKMk?B", "68a4c94dab62058e1eb00f473a379130ae9339dcc9cd3a1e", "90e0baad7052eb4a4e8be6afed9a0e3fb38ea2d6c366c546"),
|
|
new NtlmKnownResponse("70c4c091d16ea71c", "HOvO)@", "0756c8011d61e60e483ada98e28668833e60e0f530acc1dd", "5f95ae24a78bad77dda82ae72e25d09feb8a958f3941de0f"),
|
|
new NtlmKnownResponse("7f6d7d6cc5b060db", "HOvO)@", "2402df78a95018e8db8fc9dae83acb8a7add3846aec0cd32", "e021108fe5b8cc86d1244b2f5dc8a98b4a7bd82a1c11f0a8"),
|
|
new NtlmKnownResponse("9ec07c43a3a80c91", "HOvO)@", "993145b5aa6197cbc03b0652d41b43b4f03f2a446cb846a5", "5690852bdf3e17719632dcaee3002b408e531232061e8681"),
|
|
new NtlmKnownResponse("82ab135456745d77", "HOvO)@", "a3c8bd4330a288d41e4da989145855192fde833ce7e1fea4", "a7d72d0c0c604a0062fcd646bd1fd47cedb9bfa0e6c95ffb"),
|
|
|
|
};
|
|
}
|
|
|
|
[ConsoleCategory(Action="gen", HelpMessage="Generates a bunch of random hashes and passwords, and computes and displays the NT and LM responses.")]
|
|
internal class GenerateCommand
|
|
{
|
|
[InputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdin;
|
|
|
|
[OutputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdout;
|
|
|
|
[LongParameter("count", Mandatory=false, Default=8, HelpMessage="The number of hashes to generate.")]
|
|
public long IterationCount;
|
|
|
|
reflective internal GenerateCommand();
|
|
|
|
internal int AppMain()
|
|
{
|
|
StringBuilder buf = new StringBuilder();
|
|
Random rand = new Random();
|
|
|
|
Console.WriteLine("NtlmKnownResponse[] KnownResponses = {");
|
|
|
|
for (int i = 0; i < this.IterationCount; i++)
|
|
{
|
|
buf.Length = 0;
|
|
|
|
int pwlength = 4 + rand.Next(8);
|
|
for (int j = 0; j < pwlength; j++) {
|
|
char c = (char)(rand.Next(32, 126));
|
|
if (c == '\\' || c == '"')
|
|
c = '?';
|
|
buf.Append(c);
|
|
}
|
|
|
|
string! password = buf.ToString();
|
|
|
|
// Use the same password with 4 different challenges.
|
|
|
|
for (int j = 0; j < 4; j++) {
|
|
byte[]! challenge = new byte[8];
|
|
rand.NextBytes(challenge);
|
|
|
|
byte[]! lm_response = NtlmSupplicant.ComputeLmResponse(challenge, password);
|
|
byte[]! nt_response = NtlmSupplicant.ComputeNtResponse(challenge, password);
|
|
|
|
string! line = String.Format("new NtlmKnownResponse(\"{0}\", \"{1}\", \"{2}\", \"{3}\"),",
|
|
Util.ByteArrayToString(challenge),
|
|
EscapeString(password),
|
|
Util.ByteArrayToString(lm_response),
|
|
Util.ByteArrayToString(nt_response));
|
|
|
|
Console.WriteLine(line);
|
|
DebugStub.WriteLine(line);
|
|
}
|
|
}
|
|
Console.WriteLine("};");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static string! EscapeString(string! str)
|
|
{
|
|
return str.Replace(@"\", @"\\");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
struct NtlmKnownResponse
|
|
{
|
|
public NtlmKnownResponse(string! challenge, string! password, string! lm_response, string! nt_response)
|
|
{
|
|
this.Challenge = challenge;
|
|
this.Password = password;
|
|
this.NtResponse = nt_response;
|
|
this.LmResponse = lm_response;
|
|
}
|
|
|
|
public string! Challenge;
|
|
public string! Password;
|
|
public string! NtResponse;
|
|
public string! LmResponse;
|
|
}
|
|
|
|
|
|
|
|
class Util
|
|
{
|
|
public static byte[]! HexStringToByteArray(string! str)
|
|
{
|
|
if ((str.Length % 2) != 0)
|
|
throw new Exception("Input string cannot be odd in length.");
|
|
|
|
byte[]! result = new byte[str.Length / 2];
|
|
for (int i = 0; i < result.Length; i++)
|
|
{
|
|
byte high = CharToHex(str[i * 2]);
|
|
byte low = CharToHex(str[i * 2 + 1]);
|
|
result[i] = (byte)((high << 4) | low);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static byte CharToHex(char c)
|
|
{
|
|
if (c >= '0' && c <= '9')
|
|
return (byte)(c - '0');
|
|
if (c >= 'a' && c <= 'f')
|
|
return (byte)(c - 'a' + 10);
|
|
if (c >= 'A' && c <= 'F')
|
|
return (byte)(c - 'A' + 10);
|
|
throw new ArgumentException("Invalid hex char");
|
|
}
|
|
|
|
public static string! ByteArrayToString(byte[]! buffer)
|
|
{
|
|
return ByteArrayToString(buffer, 0, buffer.Length);
|
|
}
|
|
|
|
const string HexDigits = "0123456789abcdef";
|
|
|
|
public static string! ByteArrayToString(byte[]! buffer, int index, int length)
|
|
{
|
|
StringBuilder sb = new StringBuilder(length * 2);
|
|
for (int i = 0; i < length; i++) {
|
|
byte b = buffer[index + i];
|
|
sb.Append(HexDigits[b >> 4]);
|
|
sb.Append(HexDigits[b & 0xf]);
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
|
|
public static void ShowException(Exception! chain)
|
|
{
|
|
Exception current = chain;
|
|
while (current != null)
|
|
{
|
|
Console.WriteLine("{0}: {1}", current.GetType().FullName, current.Message);
|
|
current = current.InnerException;
|
|
}
|
|
}
|
|
|
|
public static int CompareArraySpans(byte[]! array1, byte[]! array2)
|
|
{
|
|
return CompareArraySpans(array1, 0, array2, 0, array1.Length);
|
|
}
|
|
|
|
public static int CompareArraySpans(byte[]! array1, int offset1, byte[]! array2, int offset2, int length)
|
|
{
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
byte element1 = array1[offset1 + i];
|
|
byte element2 = array2[offset2 + i];
|
|
if (element1 < element2)
|
|
return -1;
|
|
if (element1 > element2)
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
|
|
[ConsoleCategory(Action="remote", HelpMessage="Attempts to connect to a remote Windows host (running NtlmTestServer.exe) and authenticates using NTLM.")]
|
|
internal class RemoteAuthTestCommand
|
|
{
|
|
reflective internal RemoteAuthTestCommand();
|
|
|
|
[InputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdin;
|
|
|
|
[OutputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdout;
|
|
|
|
[StringParameter("server", Mandatory=true, HelpMessage="The remote server name or IP address.")]
|
|
public string ServerName;
|
|
|
|
[StringParameter("user", Mandatory=true, HelpMessage="The [domain\\]username to use.")]
|
|
public string UserName;
|
|
|
|
[StringParameter("password", Mandatory=true, HelpMessage="The password to use.", Default="Z@n%zilNaga")]
|
|
public string Password;
|
|
|
|
[LongParameter("count", Mandatory=false, Default=1, HelpMessage="The number of iterations to perform.")]
|
|
public long IterationCount;
|
|
|
|
[StringParameter("hostdomain", Mandatory=false, Default="WORKGROUP", HelpMessage="Specifies the domain name of the client computer. Default is WORKGROUP.")]
|
|
public string ClientMachineDomainName;
|
|
|
|
[StringParameter("workstation", Mandatory=false, Default="SINGULARITY", HelpMessage="Specifies the name of the local client machine. Default is SINGULARITY.")]
|
|
public string Workstation;
|
|
|
|
internal int AppMain()
|
|
{
|
|
// Split the username into domain\username, if the user has specified domain name.
|
|
string! username = (!)this.UserName;
|
|
string! domain;
|
|
int index = username.IndexOf('\\');
|
|
if (index != -1) {
|
|
domain = username.Substring(0, index);
|
|
username = username.Substring(index + 1);
|
|
}
|
|
else {
|
|
domain = ".";
|
|
}
|
|
|
|
string! password = (!)this.Password;
|
|
|
|
// First, connect to BVT test server.
|
|
// The sole purpose of the server is to test NTLMSSP messages.
|
|
|
|
|
|
try {
|
|
// Bartok is failing on Dns.GetLocalHostAddresses
|
|
//
|
|
//IPHostEntry! he = (!)Dns.GetHostByName(this.ServerName);
|
|
//IPAddress[]! addresses = (!)he.AddressList;
|
|
//
|
|
IPv4 addr = IPv4.Parse(this.ServerName);
|
|
IPAddress[]! addresses = { new IPAddress(addr) };
|
|
|
|
if (addresses.Length == 0) {
|
|
Console.WriteLine("Resolution failed; no address.");
|
|
return -1;
|
|
}
|
|
|
|
using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
|
|
{
|
|
// -XXX- Work around a bug in NetStack regarding quick reuse of TCP tuples
|
|
Random rand = new Random();
|
|
IPEndPoint local = new IPEndPoint(IPAddress.Any, 1000 + rand.Next(1000));
|
|
socket.Bind(local);
|
|
|
|
bool connected = false;
|
|
|
|
foreach (IPAddress address_null in addresses)
|
|
{
|
|
IPAddress! address = (!)address_null;
|
|
Console.WriteLine("Connecting: " + address.ToString());
|
|
IPEndPoint ep = new IPEndPoint(address, NtlmUnitTestProtocol.TcpPort);
|
|
|
|
try {
|
|
socket.Connect(ep);
|
|
Console.WriteLine("Connected.");
|
|
connected = true;
|
|
}
|
|
catch (Exception ex) {
|
|
Console.WriteLine("Connection failed.");
|
|
Util.ShowException(ex);
|
|
connected = false;
|
|
}
|
|
|
|
if (connected)
|
|
break;
|
|
}
|
|
|
|
if (!connected) {
|
|
Console.WriteLine("Failed to connect to server.");
|
|
return -1;
|
|
}
|
|
|
|
|
|
for (int iteration = 0; iteration < IterationCount; iteration++) {
|
|
if (IterationCount > 1)
|
|
Console.WriteLine("[Iteration {0}/{1}]", iteration, IterationCount.ToString());
|
|
|
|
try {
|
|
|
|
Console.WriteLine(" Sending NEGOTIATE");
|
|
|
|
byte[]! negotiate = NtlmSupplicant.GetNegotiate(
|
|
NtlmNegotiateFlags.None,
|
|
(!)this.ClientMachineDomainName,
|
|
(!)this.Workstation);
|
|
|
|
NtlmUnitTestProtocol.SendMessage(socket, TestMessageType.Negotiate, negotiate);
|
|
|
|
Console.WriteLine(" Waiting for CHALLENGE");
|
|
|
|
byte[]! challenge = NtlmUnitTestProtocol.ReceiveExpectedMessage(socket, TestMessageType.Challenge);
|
|
|
|
byte[]! response = NtlmSupplicant.GetResponse(
|
|
challenge,
|
|
domain,
|
|
username,
|
|
this.Workstation,
|
|
password);
|
|
|
|
Console.WriteLine(" Sending RESPONSE");
|
|
|
|
NtlmUnitTestProtocol.SendMessage(socket, TestMessageType.Response, response);
|
|
|
|
Console.WriteLine(" Waiting RESULT");
|
|
|
|
byte[]! resultBuffer = NtlmUnitTestProtocol.ReceiveExpectedMessage(socket, TestMessageType.Result);
|
|
|
|
Console.WriteLine(" Received RESULT:");
|
|
ref ResultMessage result = ref resultBuffer[0];
|
|
string! resulttext = Encoding.Unicode.GetString(resultBuffer, sizeof(ResultMessage), resultBuffer.Length - sizeof(ResultMessage));
|
|
|
|
if (result.Succeeded != 0) {
|
|
Console.WriteLine(" Succeeded = TRUE");
|
|
}
|
|
else {
|
|
Console.WriteLine(" Succeeded = FALSE");
|
|
}
|
|
Console.WriteLine(" Message: " + resulttext);
|
|
|
|
}
|
|
catch (Exception ex) {
|
|
Console.WriteLine("Exception occurred during test.");
|
|
Util.ShowException(ex);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
catch (Exception ex) {
|
|
Console.WriteLine("Exception occurred during test.");
|
|
Util.ShowException(ex);
|
|
return -1;
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
[ConsoleCategory(Action="remote-cm", HelpMessage="Attempts to connect to a remote Windows host (running NtlmTestServer.exe) and authenticates using NTLM via the Credentials Manager service.")]
|
|
internal class RemoteAuthTestCredMgrCommand
|
|
{
|
|
reflective internal RemoteAuthTestCredMgrCommand();
|
|
|
|
[InputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdin;
|
|
|
|
[OutputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdout;
|
|
|
|
[StringParameter("server", Mandatory=false, HelpMessage="The remote server name or IP address.")]
|
|
public string ServerName;
|
|
|
|
[StringParameter("credentials", Mandatory=true, HelpMessage="The credentials to use, such as [domain\\]user.")]
|
|
public string CredentialsName;
|
|
|
|
[StringParameter("tag", Mandatory=false, HelpMessage="The credentials tag, if necessary.")]
|
|
public string CredentialsTag;
|
|
|
|
[LongParameter("count", Mandatory=false, Default=1, HelpMessage="The number of iterations to perform.")]
|
|
public long IterationCount;
|
|
|
|
internal int AppMain()
|
|
{
|
|
// First, connect to BVT test server.
|
|
// The sole purpose of the server is to test NTLMSSP messages.
|
|
|
|
string! credentialsName = (!)this.CredentialsName;
|
|
string! credentialsTag = this.CredentialsTag != null ? this.CredentialsTag : "";
|
|
|
|
try {
|
|
// Bartok is failing on Dns.GetLocalHostAddresses
|
|
//
|
|
//IPHostEntry! he = (!)Dns.GetHostByName(this.ServerName);
|
|
//IPAddress[]! addresses = (!)he.AddressList;
|
|
//
|
|
IPv4 addr = IPv4.Parse(this.ServerName);
|
|
IPAddress[]! addresses = { new IPAddress(addr) };
|
|
|
|
if (addresses.Length == 0) {
|
|
Console.WriteLine("Resolution failed; no address.");
|
|
return -1;
|
|
}
|
|
|
|
using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
|
|
{
|
|
// -XXX- Work around a bug in NetStack regarding quick reuse of TCP tuples
|
|
Random rand = new Random();
|
|
IPEndPoint local = new IPEndPoint(IPAddress.Any, 1000 + rand.Next(1000));
|
|
socket.Bind(local);
|
|
|
|
bool connected = false;
|
|
|
|
foreach (IPAddress address_null in addresses)
|
|
{
|
|
IPAddress! address = (!)address_null;
|
|
Console.WriteLine("Connecting: " + address.ToString());
|
|
IPEndPoint ep = new IPEndPoint(address, NtlmUnitTestProtocol.TcpPort);
|
|
|
|
try {
|
|
socket.Connect(ep);
|
|
Console.WriteLine("Connected.");
|
|
connected = true;
|
|
}
|
|
catch (Exception ex) {
|
|
Console.WriteLine("Connection failed.");
|
|
Util.ShowException(ex);
|
|
connected = false;
|
|
}
|
|
|
|
if (connected)
|
|
break;
|
|
}
|
|
|
|
if (!connected) {
|
|
Console.WriteLine("Failed to connect to server.");
|
|
return -1;
|
|
}
|
|
|
|
|
|
for (int iteration = 0; iteration < IterationCount; iteration++) {
|
|
if (IterationCount > 1)
|
|
Console.WriteLine("[Iteration {0}/{1}]", iteration, IterationCount.ToString());
|
|
|
|
try {
|
|
|
|
Console.WriteLine("Acquiring security context");
|
|
GssSupplicantContract.Imp! supplicant;
|
|
GssSupplicantContract.Exp! supplicant_exp;
|
|
GssSupplicantContract.NewChannel(out supplicant, out supplicant_exp);
|
|
|
|
CredentialsManager.CreateSupplicant(
|
|
AuthenticationProtocolNames.Ntlm,
|
|
credentialsName,
|
|
credentialsTag,
|
|
supplicant_exp);
|
|
try {
|
|
|
|
Console.WriteLine(" Getting NEGOTIATE from security context");
|
|
byte[]! negotiate;
|
|
switch receive {
|
|
case supplicant.FirstToken(token):
|
|
// Console.WriteLine(" Received first token from security context");
|
|
negotiate = Bitter.ToByteArray(token);
|
|
delete token;
|
|
break;
|
|
|
|
case supplicant.NeedFirstToken():
|
|
throw new Exception("Received unexpected token from security context (NeedFirstToken)");
|
|
}
|
|
|
|
Console.WriteLine(" Sending NEGOTIATE");
|
|
|
|
NtlmUnitTestProtocol.SendMessage(socket, TestMessageType.Negotiate, negotiate);
|
|
|
|
Console.WriteLine(" Waiting for CHALLENGE");
|
|
|
|
byte[]! challenge = NtlmUnitTestProtocol.ReceiveExpectedMessage(socket, TestMessageType.Challenge);
|
|
|
|
Console.WriteLine(" Sending challenge to security context");
|
|
supplicant.SendAcceptToken(Bitter.FromByteArray(challenge));
|
|
|
|
Console.WriteLine(" Waiting for response from security context");
|
|
|
|
byte[]! response;
|
|
switch receive {
|
|
case supplicant.CompleteWithToken(token):
|
|
// Console.WriteLine(" Received token from security context, and authentication is complete");
|
|
response = Bitter.ToByteArray(token);
|
|
delete token;
|
|
break;
|
|
|
|
case supplicant.Complete():
|
|
throw new Exception("Security context returned unexpected message (Complete)");
|
|
|
|
case supplicant.ContinueNeeded(token):
|
|
delete token;
|
|
throw new Exception("Security context returned unexpected message(ContinueNeeded)");
|
|
|
|
case supplicant.AuthenticationFailed(error):
|
|
throw new Exception("Security context returned unexpected message (AuthenticationFailed): " + CredentialsManager.GssErrorCodeToString(error));
|
|
}
|
|
|
|
Console.WriteLine(" Sending RESPONSE");
|
|
|
|
NtlmUnitTestProtocol.SendMessage(socket, TestMessageType.Response, response);
|
|
|
|
Console.WriteLine(" Waiting RESULT");
|
|
|
|
byte[]! resultBuffer = NtlmUnitTestProtocol.ReceiveExpectedMessage(socket, TestMessageType.Result);
|
|
|
|
Console.WriteLine(" Received RESULT:");
|
|
ref ResultMessage result = ref resultBuffer[0];
|
|
string! resulttext = Encoding.Unicode.GetString(resultBuffer, sizeof(ResultMessage), resultBuffer.Length - sizeof(ResultMessage));
|
|
|
|
if (result.Succeeded != 0) {
|
|
Console.WriteLine(" Succeeded = TRUE");
|
|
}
|
|
else {
|
|
Console.WriteLine(" Succeeded = FALSE");
|
|
}
|
|
Console.WriteLine(" Message: " + resulttext);
|
|
|
|
}
|
|
finally {
|
|
delete supplicant;
|
|
}
|
|
|
|
}
|
|
catch (Exception ex) {
|
|
Console.WriteLine("Exception occurred during test.");
|
|
Util.ShowException(ex);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
catch (Exception ex) {
|
|
Console.WriteLine("Exception occurred during test.");
|
|
Util.ShowException(ex);
|
|
return -1;
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
class NtlmUnitTestProtocol
|
|
{
|
|
public const int TcpPort = 720;
|
|
|
|
public static void SendMessage(Socket! socket, TestMessageType type, byte[]! payload)
|
|
{
|
|
byte[]! headerBuffer = new byte[sizeof(TestMessageHeader)];
|
|
ref TestMessageHeader header = ref headerBuffer[0];
|
|
header.TotalLength = (uint)(sizeof(TestMessageHeader) + payload.Length);
|
|
header.MessageType = (uint)type;
|
|
|
|
socket.Send(headerBuffer);
|
|
|
|
socket.Send(payload);
|
|
}
|
|
|
|
public static byte[]! ReceiveExpectedMessage(Socket! socket, TestMessageType type)
|
|
{
|
|
TestMessageType actualType;
|
|
byte[]! msg = ReceiveMessage(socket, out actualType);
|
|
if (actualType != type) {
|
|
Console.WriteLine("Received message, but its type is not the expected type!");
|
|
Console.WriteLine("Received type {0}, wanted type {1}", actualType, type);
|
|
throw new Exception("Invalid message received.");
|
|
}
|
|
return msg;
|
|
}
|
|
|
|
public static byte[]! ReceiveMessage(Socket! socket, out TestMessageType type)
|
|
{
|
|
byte[]! headerBuffer = new byte[sizeof(TestMessageHeader)];
|
|
|
|
int length = socket.Receive(headerBuffer, sizeof(TestMessageHeader), SocketFlags.None);
|
|
if (length == 0) {
|
|
throw new Exception("Server has closed socket.");
|
|
}
|
|
|
|
if (length < sizeof(TestMessageHeader)) {
|
|
throw new Exception("Received short data from server.");
|
|
}
|
|
ref TestMessageHeader header = ref headerBuffer[0];
|
|
|
|
if (header.TotalLength < sizeof(TestMessageHeader)) {
|
|
throw new Exception("Received invalid header from server (length is too short)");
|
|
}
|
|
|
|
if (header.TotalLength > 0x10000) {
|
|
throw new Exception("Received excessively large message from server.");
|
|
}
|
|
|
|
int bodyLength = (int)(header.TotalLength - sizeof(TestMessageHeader));
|
|
byte[]! body = new byte[bodyLength];
|
|
|
|
length = socket.Receive(body, bodyLength, SocketFlags.None);
|
|
if (length == 0)
|
|
throw new Exception("Server has closed socket.");
|
|
|
|
if (length < bodyLength)
|
|
throw new Exception("Received short data (payload) from server.");
|
|
|
|
type = (TestMessageType)header.MessageType;
|
|
return body;
|
|
}
|
|
}
|
|
|
|
enum TestMessageType
|
|
{
|
|
Negotiate = 1,
|
|
Challenge = 2,
|
|
Response = 3,
|
|
Result = 4,
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
pointerfree struct ResultMessage
|
|
{
|
|
public int Succeeded;
|
|
// unicode string of error follows, no nul terminator
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
pointerfree struct TestMessageHeader
|
|
{
|
|
public uint TotalLength;
|
|
public uint MessageType;
|
|
}
|
|
|