diff --git a/MailKit/MailKit.csproj b/MailKit/MailKit.csproj
index 3563c5a320..a934a7eea4 100644
--- a/MailKit/MailKit.csproj
+++ b/MailKit/MailKit.csproj
@@ -138,14 +138,17 @@
-
-
-
+
+
-
+
+
+
+
+
diff --git a/MailKit/Security/Ntlm/BitConverterLE.cs b/MailKit/Security/Ntlm/BitConverterLE.cs
index 4db415bb1c..331ea9747a 100644
--- a/MailKit/Security/Ntlm/BitConverterLE.cs
+++ b/MailKit/Security/Ntlm/BitConverterLE.cs
@@ -26,14 +26,9 @@
using System;
-namespace MailKit.Security.Ntlm
-{
- sealed class BitConverterLE
+namespace MailKit.Security.Ntlm {
+ static class BitConverterLE
{
- BitConverterLE ()
- {
- }
-
unsafe static byte[] GetULongBytes (byte *bytes)
{
if (BitConverter.IsLittleEndian)
@@ -73,7 +68,7 @@ unsafe static void UIntFromBytes (byte *dst, byte[] src, int startIndex)
}
}
- unsafe internal static short ToInt16 (byte[] value, int startIndex)
+ public unsafe static short ToInt16 (byte[] value, int startIndex)
{
short ret;
@@ -82,7 +77,7 @@ unsafe internal static short ToInt16 (byte[] value, int startIndex)
return ret;
}
- unsafe internal static int ToInt32 (byte[] value, int startIndex)
+ public unsafe static int ToInt32 (byte[] value, int startIndex)
{
int ret;
@@ -91,7 +86,7 @@ unsafe internal static int ToInt32 (byte[] value, int startIndex)
return ret;
}
- unsafe internal static ushort ToUInt16 (byte[] value, int startIndex)
+ public unsafe static ushort ToUInt16 (byte[] value, int startIndex)
{
ushort ret;
@@ -100,7 +95,7 @@ unsafe internal static ushort ToUInt16 (byte[] value, int startIndex)
return ret;
}
- unsafe internal static uint ToUInt32 (byte[] value, int startIndex)
+ public unsafe static uint ToUInt32 (byte[] value, int startIndex)
{
uint ret;
diff --git a/MailKit/Security/Ntlm/ChallengeResponse2.cs b/MailKit/Security/Ntlm/ChallengeResponse2.cs
deleted file mode 100644
index 6beae42d2e..0000000000
--- a/MailKit/Security/Ntlm/ChallengeResponse2.cs
+++ /dev/null
@@ -1,285 +0,0 @@
-//
-// Mono.Security.Protocol.Ntlm.ChallengeResponse
-// Implements Challenge Response for NTLM v1 and NTLM v2 Session
-//
-// Authors: Sebastien Pouliot
-// Martin Baulig
-// Jeffrey Stedfast
-//
-// Copyright (c) 2003 Motus Technologies Inc. (http://www.motus.com)
-// Copyright (c) 2004 Novell (http://www.novell.com)
-// Copyright (c) 2012 Xamarin, Inc. (http://www.xamarin.com)
-//
-// References
-// a. NTLM Authentication Scheme for HTTP, Ronald Tschalär
-// http://www.innovation.ch/java/ntlm.html
-// b. The NTLM Authentication Protocol, Copyright © 2003 Eric Glass
-// http://davenport.sourceforge.net/ntlm.html
-//
-// 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.Text;
-using System.Security.Cryptography;
-
-namespace MailKit.Security.Ntlm {
- static class ChallengeResponse2
- {
- static readonly byte[] Magic = { 0x4B, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 };
-
- // This is the pre-encrypted magic value with a null DES key (0xAAD3B435B51404EE)
- // Ref: http://packetstormsecurity.nl/Crackers/NT/l0phtcrack/l0phtcrack2.5-readme.html
- static readonly byte[] NullEncMagic = { 0xAA, 0xD3, 0xB4, 0x35, 0xB5, 0x14, 0x04, 0xEE };
-
- static byte[] ComputeLM (string password, byte[] challenge)
- {
- var buffer = new byte[21];
-
- // create Lan Manager password
- using (var des = DES.Create ()) {
- des.Mode = CipherMode.ECB;
-
- // Note: In .NET DES cannot accept a weak key
- // this can happen for a null password
- if (string.IsNullOrEmpty (password)) {
- Buffer.BlockCopy (NullEncMagic, 0, buffer, 0, 8);
- } else {
- des.Key = PasswordToKey (password, 0);
- using (var ct = des.CreateEncryptor ())
- ct.TransformBlock (Magic, 0, 8, buffer, 0);
- }
-
- // and if a password has less than 8 characters
- if (password == null || password.Length < 8) {
- Buffer.BlockCopy (NullEncMagic, 0, buffer, 8, 8);
- } else {
- des.Key = PasswordToKey (password, 7);
- using (var ct = des.CreateEncryptor ())
- ct.TransformBlock (Magic, 0, 8, buffer, 8);
- }
- }
-
- return GetResponse (challenge, buffer);
- }
-
- static byte[] ComputeNtlmPassword (string password)
- {
- var buffer = new byte[21];
-
- // create NT password
- using (var md4 = new MD4 ()) {
- var data = password == null ? new byte[0] : Encoding.Unicode.GetBytes (password);
- var hash = md4.ComputeHash (data);
- Buffer.BlockCopy (hash, 0, buffer, 0, 16);
-
- // clean up
- Array.Clear (data, 0, data.Length);
- Array.Clear (hash, 0, hash.Length);
- }
-
- return buffer;
- }
-
- static byte[] ComputeNtlm (string password, byte[] challenge)
- {
- var buffer = ComputeNtlmPassword (password);
- return GetResponse (challenge, buffer);
- }
-
- static void ComputeNtlmV2Session (string password, byte[] challenge, out byte[] lm, out byte[] ntlm)
- {
- var nonce = new byte[8];
-
- using (var rng = RandomNumberGenerator.Create ())
- rng.GetBytes (nonce);
-
- var sessionNonce = new byte[challenge.Length + 8];
- challenge.CopyTo (sessionNonce, 0);
- nonce.CopyTo (sessionNonce, challenge.Length);
-
- lm = new byte[24];
- nonce.CopyTo (lm, 0);
-
- using (var md5 = MD5.Create ()) {
- var hash = md5.ComputeHash (sessionNonce);
- var newChallenge = new byte[8];
-
- Array.Copy (hash, newChallenge, 8);
-
- ntlm = ComputeNtlm (password, newChallenge);
-
- // clean up
- Array.Clear (newChallenge, 0, newChallenge.Length);
- Array.Clear (hash, 0, hash.Length);
- }
-
- // clean up
- Array.Clear (sessionNonce, 0, sessionNonce.Length);
- Array.Clear (nonce, 0, nonce.Length);
- }
-
- static byte[] ComputeNtlmV2 (Type2Message type2, string username, string password, string domain)
- {
- var ntlm_hash = ComputeNtlmPassword (password);
-
- var ubytes = Encoding.Unicode.GetBytes (username.ToUpperInvariant ());
- var tbytes = Encoding.Unicode.GetBytes (domain);
-
- var bytes = new byte[ubytes.Length + tbytes.Length];
- ubytes.CopyTo (bytes, 0);
- Array.Copy (tbytes, 0, bytes, ubytes.Length, tbytes.Length);
-
- byte[] ntlm_v2_hash;
-
- using (var md5 = new HMACMD5 (ntlm_hash))
- ntlm_v2_hash = md5.ComputeHash (bytes);
-
- Array.Clear (ntlm_hash, 0, ntlm_hash.Length);
-
- using (var md5 = new HMACMD5 (ntlm_v2_hash)) {
- var timestamp = DateTime.UtcNow.Ticks - 504911232000000000;
- var nonce = new byte[8];
-
- if (type2.TargetInfo?.Timestamp != 0)
- timestamp = type2.TargetInfo.Timestamp;
-
- using (var rng = RandomNumberGenerator.Create ())
- rng.GetBytes (nonce);
-
- var targetInfo = type2.EncodedTargetInfo;
- var blob = new byte[28 + targetInfo.Length];
- blob[0] = 0x01;
- blob[1] = 0x01;
-
- Buffer.BlockCopy (BitConverterLE.GetBytes (timestamp), 0, blob, 8, 8);
-
- Buffer.BlockCopy (nonce, 0, blob, 16, 8);
- Buffer.BlockCopy (targetInfo, 0, blob, 28, targetInfo.Length);
-
- var challenge = type2.Nonce;
-
- var hashInput = new byte[challenge.Length + blob.Length];
- challenge.CopyTo (hashInput, 0);
- blob.CopyTo (hashInput, challenge.Length);
-
- var blobHash = md5.ComputeHash (hashInput);
-
- var response = new byte[blob.Length + blobHash.Length];
- blobHash.CopyTo (response, 0);
- blob.CopyTo (response, blobHash.Length);
-
- Array.Clear (ntlm_v2_hash, 0, ntlm_v2_hash.Length);
- Array.Clear (hashInput, 0, hashInput.Length);
- Array.Clear (blobHash, 0, blobHash.Length);
- Array.Clear (nonce, 0, nonce.Length);
- Array.Clear (blob, 0, blob.Length);
-
- return response;
- }
- }
-
- public static void Compute (Type2Message type2, NtlmAuthLevel level, string username, string password, string domain, out byte[] lm, out byte[] ntlm)
- {
- lm = null;
-
- switch (level) {
- case NtlmAuthLevel.LM_and_NTLM:
- lm = ComputeLM (password, type2.Nonce);
- ntlm = ComputeNtlm (password, type2.Nonce);
- break;
- case NtlmAuthLevel.LM_and_NTLM_and_try_NTLMv2_Session:
- if ((type2.Flags & NtlmFlags.NegotiateNtlm2Key) == 0)
- goto case NtlmAuthLevel.LM_and_NTLM;
- ComputeNtlmV2Session (password, type2.Nonce, out lm, out ntlm);
- break;
- case NtlmAuthLevel.NTLM_only:
- if ((type2.Flags & NtlmFlags.NegotiateNtlm2Key) != 0)
- ComputeNtlmV2Session (password, type2.Nonce, out lm, out ntlm);
- else
- ntlm = ComputeNtlm (password, type2.Nonce);
- break;
- case NtlmAuthLevel.NTLMv2_only:
- ntlm = ComputeNtlmV2 (type2, username, password, domain);
- if (type2.TargetInfo.Timestamp != 0)
- lm = new byte[24];
- break;
- default:
- throw new InvalidOperationException ();
- }
- }
-
- static byte[] GetResponse (byte[] challenge, byte[] pwd)
- {
- var response = new byte[24];
-
- using (var des = DES.Create ()) {
- des.Mode = CipherMode.ECB;
- des.Key = PrepareDESKey (pwd, 0);
-
- using (var ct = des.CreateEncryptor ())
- ct.TransformBlock (challenge, 0, 8, response, 0);
-
- des.Key = PrepareDESKey (pwd, 7);
-
- using (var ct = des.CreateEncryptor ())
- ct.TransformBlock (challenge, 0, 8, response, 8);
-
- des.Key = PrepareDESKey (pwd, 14);
-
- using (var ct = des.CreateEncryptor ())
- ct.TransformBlock (challenge, 0, 8, response, 16);
- }
-
- return response;
- }
-
- static byte[] PrepareDESKey (byte[] key56bits, int position)
- {
- // convert to 8 bytes
- var key = new byte[8];
-
- key[0] = key56bits [position];
- key[1] = (byte) ((key56bits[position] << 7) | (key56bits[position + 1] >> 1));
- key[2] = (byte) ((key56bits[position + 1] << 6) | (key56bits[position + 2] >> 2));
- key[3] = (byte) ((key56bits[position + 2] << 5) | (key56bits[position + 3] >> 3));
- key[4] = (byte) ((key56bits[position + 3] << 4) | (key56bits[position + 4] >> 4));
- key[5] = (byte) ((key56bits[position + 4] << 3) | (key56bits[position + 5] >> 5));
- key[6] = (byte) ((key56bits[position + 5] << 2) | (key56bits[position + 6] >> 6));
- key[7] = (byte) (key56bits[position + 6] << 1);
-
- return key;
- }
-
- static byte[] PasswordToKey (string password, int position)
- {
- int len = Math.Min (password.Length - position, 7);
- var key7 = new byte[7];
-
- Encoding.ASCII.GetBytes (password.ToUpper (), position, len, key7, 0);
- var key8 = PrepareDESKey (key7, 0);
-
- // cleanup intermediate key material
- Array.Clear (key7, 0, key7.Length);
-
- return key8;
- }
- }
-}
diff --git a/MailKit/Security/Ntlm/MessageBase.cs b/MailKit/Security/Ntlm/MessageBase.cs
deleted file mode 100644
index a9c41ac3cc..0000000000
--- a/MailKit/Security/Ntlm/MessageBase.cs
+++ /dev/null
@@ -1,103 +0,0 @@
-//
-// Mono.Security.Protocol.Ntlm.MessageBase
-// abstract class for all NTLM messages
-//
-// Author:
-// Sebastien Pouliot
-//
-// Copyright (C) 2003 Motus Technologies Inc. (http://www.motus.com)
-// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
-//
-// References
-// a. NTLM Authentication Scheme for HTTP, Ronald Tschalär
-// http://www.innovation.ch/java/ntlm.html
-// b. The NTLM Authentication Protocol, Copyright © 2003 Eric Glass
-// http://davenport.sourceforge.net/ntlm.html
-//
-// 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.Globalization;
-
-namespace MailKit.Security.Ntlm {
- abstract class MessageBase
- {
- static readonly byte[] header = { 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00 };
-
- protected MessageBase (int type)
- {
- Type = type;
- }
-
- public NtlmFlags Flags {
- get; set;
- }
-
- public Version OSVersion {
- get; protected set;
- }
-
- public int Type {
- get; private set;
- }
-
- protected byte[] PrepareMessage (int size)
- {
- var message = new byte[size];
-
- Buffer.BlockCopy (header, 0, message, 0, 8);
-
- message[ 8] = (byte) Type;
- message[ 9] = (byte)(Type >> 8);
- message[10] = (byte)(Type >> 16);
- message[11] = (byte)(Type >> 24);
-
- return message;
- }
-
- bool CheckHeader (byte[] message, int startIndex)
- {
- for (int i = 0; i < header.Length; i++) {
- if (message[startIndex + i] != header[i])
- return false;
- }
-
- return BitConverterLE.ToUInt32 (message, startIndex + 8) == Type;
- }
-
- protected void ValidateArguments (byte[] message, int startIndex, int length)
- {
- if (message == null)
- throw new ArgumentNullException (nameof (message));
-
- if (startIndex < 0 || startIndex > message.Length)
- throw new ArgumentOutOfRangeException (nameof (startIndex));
-
- if (length < 12 || length > (message.Length - startIndex))
- throw new ArgumentOutOfRangeException (nameof (length));
-
- if (!CheckHeader (message, startIndex))
- throw new ArgumentException (string.Format (CultureInfo.InvariantCulture, "Invalid Type{0} message.", Type), nameof (message));
- }
-
- public abstract byte[] Encode ();
- }
-}
diff --git a/MailKit/Security/Ntlm/NtlmAuthLevel.cs b/MailKit/Security/Ntlm/NtlmAttribute.cs
similarity index 55%
rename from MailKit/Security/Ntlm/NtlmAuthLevel.cs
rename to MailKit/Security/Ntlm/NtlmAttribute.cs
index 584d1646d1..640b4f46a0 100644
--- a/MailKit/Security/Ntlm/NtlmAuthLevel.cs
+++ b/MailKit/Security/Ntlm/NtlmAttribute.cs
@@ -1,10 +1,9 @@
+//
+// NtlmAttribute.cs
//
-// NtlmAuthLevel.cs
+// Author: Jeffrey Stedfast
//
-// Author:
-// Martin Baulig
-//
-// Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com)
+// Copyright (c) 2013-2021 .NET Foundation and Contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -23,29 +22,24 @@
// 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.
+//
-namespace MailKit.Security.Ntlm {
- /*
- * On Windows, this is controlled by a registry setting
- * (http://msdn.microsoft.com/en-us/library/ms814176.aspx)
- *
- * This can be configured by setting the static
- * Type3Message.DefaultAuthLevel property, the default value
- * is LM_and_NTLM_and_try_NTLMv2_Session.
- */
- enum NtlmAuthLevel {
- /* Use LM and NTLM, never use NTLMv2 session security. */
- LM_and_NTLM,
-
- /* Use NTLMv2 session security if the server supports it,
- * otherwise fall back to LM and NTLM. */
- LM_and_NTLM_and_try_NTLMv2_Session,
-
- /* Use NTLMv2 session security if the server supports it,
- * otherwise fall back to NTLM. Never use LM. */
- NTLM_only,
+// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4
- /* Use NTLMv2 only. */
- NTLMv2_only,
+namespace MailKit.Security.Ntlm
+{
+ enum NtlmAttribute : short
+ {
+ EOL = 0,
+ ServerName = 1,
+ DomainName = 2,
+ DnsServerName = 3,
+ DnsDomainName = 4,
+ DnsTreeName = 5,
+ Flags = 6,
+ Timestamp = 7,
+ SingleHost = 8,
+ TargetName = 9,
+ ChannelBinding = 10
}
}
diff --git a/MailKit/Security/Ntlm/NtlmAttributeValuePair.cs b/MailKit/Security/Ntlm/NtlmAttributeValuePair.cs
new file mode 100644
index 0000000000..ad01801d8a
--- /dev/null
+++ b/MailKit/Security/Ntlm/NtlmAttributeValuePair.cs
@@ -0,0 +1,241 @@
+//
+// NtlmAttributeValuePair.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2021 .NET Foundation and Contributors
+//
+// 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.
+//
+
+// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4
+
+namespace MailKit.Security.Ntlm {
+ ///
+ /// An abstract NTLM attribute and value pair.
+ ///
+ ///
+ /// An abstract NTLM attribute and value pair.
+ ///
+ abstract class NtlmAttributeValuePair
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Creates a new NTLM attribute and value pair.
+ ///
+ /// The NTLM attribute.
+ protected NtlmAttributeValuePair (NtlmAttribute attr)
+ {
+ Attribute = attr;
+ }
+
+ ///
+ /// Get the NTLM attribute that this pair represents.
+ ///
+ ///
+ /// Gets the NTLM attribute that this pair represents.
+ ///
+ /// The NTLM attribute.
+ public NtlmAttribute Attribute {
+ get; private set;
+ }
+ }
+
+ ///
+ /// An NTLM attribute and value pair consisting of a string value.
+ ///
+ ///
+ /// An NTLM attribute and value pair consisting of a string value.
+ ///
+ sealed class NtlmAttributeStringValuePair : NtlmAttributeValuePair
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Creates a new NTLM attribute and value pair consisting of a string value.
+ ///
+ /// The NTLM attribute.
+ /// The NTLM attribute value.
+ public NtlmAttributeStringValuePair (NtlmAttribute attr, string value) : base (attr)
+ {
+ Value = value;
+ }
+
+ ///
+ /// Get or set the value of the attribute.
+ ///
+ ///
+ /// Gets or sets the value of the attribute.
+ ///
+ /// The attribute value.
+ public string Value {
+ get; set;
+ }
+ }
+
+ ///
+ /// An NTLM attribute and value pair consisting of a flags value.
+ ///
+ ///
+ /// An NTLM attribute and value pair consisting of a flags value.
+ ///
+ sealed class NtlmAttributeFlagsValuePair : NtlmAttributeValuePair
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Creates a new NTLM attribute and value pair consisting of a flags value.
+ ///
+ /// The NTLM attribute.
+ /// The NTLM attribute value.
+ /// The size of the encoded flags value.
+ internal NtlmAttributeFlagsValuePair (NtlmAttribute attr, int value, short size) : base (attr)
+ {
+ Value = value;
+ Size = size;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Creates a new NTLM attribute and value pair consisting of a flags value.
+ ///
+ /// The NTLM attribute.
+ /// The NTLM attribute value.
+ public NtlmAttributeFlagsValuePair (NtlmAttribute attr, int value) : this (attr, value, 4)
+ {
+ }
+
+ ///
+ /// Get or set the size of the encoded flags value.
+ ///
+ ///
+ /// Gets or sets the size of the encoded flags value.
+ ///
+ public short Size {
+ get; internal set;
+ }
+
+ ///
+ /// Get or set the value of the attribute.
+ ///
+ ///
+ /// Gets or sets the value of the attribute.
+ ///
+ /// The attribute value.
+ public int Value {
+ get; set;
+ }
+ }
+
+ ///
+ /// An NTLM attribute and value pair consisting of a timestamp value.
+ ///
+ ///
+ /// An NTLM attribute and value pair consisting of a timestamp value.
+ ///
+ sealed class NtlmAttributeTimestampValuePair : NtlmAttributeValuePair
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Creates a new NTLM attribute and value pair consisting of a timestamp value.
+ ///
+ /// The NTLM attribute.
+ /// The NTLM attribute value.
+ /// The size of the encoded flags value.
+ internal NtlmAttributeTimestampValuePair (NtlmAttribute attr, long value, short size) : base (attr)
+ {
+ Value = value;
+ Size = size;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Creates a new NTLM attribute and value pair consisting of a timestamp value.
+ ///
+ /// The NTLM attribute.
+ /// The NTLM attribute value.
+ public NtlmAttributeTimestampValuePair (NtlmAttribute attr, long value) : this (attr, value, 8)
+ {
+ }
+
+ ///
+ /// Get or set the size of the encoded timestamp value.
+ ///
+ ///
+ /// Gets or sets the size of the encoded timestamp value.
+ ///
+ public short Size {
+ get; internal set;
+ }
+
+ ///
+ /// Get or set the value of the attribute.
+ ///
+ ///
+ /// Gets or sets the value of the attribute.
+ ///
+ /// The attribute value.
+ public long Value {
+ get; set;
+ }
+ }
+
+ ///
+ /// An NTLM attribute and value pair consisting of a byte array value.
+ ///
+ ///
+ /// An NTLM attribute and value pair consisting of a byte array value.
+ ///
+ sealed class NtlmAttributeByteArrayValuePair : NtlmAttributeValuePair
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Creates a new NTLM attribute and value pair consisting of a byte array value.
+ ///
+ /// The NTLM attribute.
+ /// The NTLM attribute value.
+ public NtlmAttributeByteArrayValuePair (NtlmAttribute attr, byte[] value) : base (attr)
+ {
+ Value = value;
+ }
+
+ ///
+ /// Get or set the value of the attribute.
+ ///
+ ///
+ /// Gets or sets the value of the attribute.
+ ///
+ /// The attribute value.
+ public byte[] Value {
+ get; set;
+ }
+ }
+}
diff --git a/MailKit/Security/Ntlm/NtlmFlags.cs b/MailKit/Security/Ntlm/NtlmFlags.cs
index b85121ec90..a0be760258 100644
--- a/MailKit/Security/Ntlm/NtlmFlags.cs
+++ b/MailKit/Security/Ntlm/NtlmFlags.cs
@@ -24,6 +24,8 @@
// THE SOFTWARE.
//
+// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4
+
using System;
namespace MailKit.Security.Ntlm {
@@ -148,13 +150,18 @@ enum NtlmFlags {
R6 = TargetTypeShare,
///
- /// Indicates that the NTLM2 signing and sealing scheme should be used for
- /// protecting authenticated communications. Note that this refers to a
- /// particular session security scheme, and is not related to the use of
- /// NTLMv2 authentication. This flag can, however, have an effect on the
- /// response calculations.
+ /// If set, requests usage of the NTLM v2 session security. NTLM v2 session
+ /// security is a misnomer because it is not NTLM v2. It is NTLM v1 using the
+ /// extended session security that is also in NTLM v2. NTLMSSP_NEGOTIATE_LM_KEY
+ /// and NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY are mutually exclusive. If
+ /// both NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY and NTLMSSP_NEGOTIATE_LM_KEY
+ /// are requested, NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY alone MUST be
+ /// returned to the client. NTLM v2 authentication session key generation MUST
+ /// be supported by both the client and the DC in order to be used, and extended
+ /// session security signing and sealing requires support from the client and
+ /// the server in order to be used.
///
- NegotiateNtlm2Key = 0x00080000,
+ NegotiateExtendedSessionSecurity = 0x00080000,
///
/// This flag's usage has not been identified.
diff --git a/MailKit/Security/Ntlm/NtlmMessageBase.cs b/MailKit/Security/Ntlm/NtlmMessageBase.cs
new file mode 100644
index 0000000000..74176bf33e
--- /dev/null
+++ b/MailKit/Security/Ntlm/NtlmMessageBase.cs
@@ -0,0 +1,95 @@
+//
+// NtlmMessageBase.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2021 .NET Foundation and Contributors
+//
+// 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.
+//
+
+// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4
+
+using System;
+using System.Globalization;
+
+namespace MailKit.Security.Ntlm {
+ abstract class NtlmMessageBase
+ {
+ static readonly byte[] Signature = { (byte) 'N', (byte) 'T', (byte) 'L', (byte) 'M', (byte) 'S', (byte) 'S', (byte) 'P', 0x00 };
+
+ protected NtlmMessageBase (int type)
+ {
+ Type = type;
+ }
+
+ public NtlmFlags Flags {
+ get; protected set;
+ }
+
+ public Version OSVersion {
+ get; protected set;
+ }
+
+ public int Type {
+ get; private set;
+ }
+
+ protected byte[] PrepareMessage (int size)
+ {
+ var message = new byte[size];
+
+ Buffer.BlockCopy (Signature, 0, message, 0, 8);
+
+ message[ 8] = (byte) Type;
+ message[ 9] = (byte)(Type >> 8);
+ message[10] = (byte)(Type >> 16);
+ message[11] = (byte)(Type >> 24);
+
+ return message;
+ }
+
+ bool CheckSignature (byte[] message, int startIndex)
+ {
+ for (int i = 0; i < Signature.Length; i++) {
+ if (message[startIndex + i] != Signature[i])
+ return false;
+ }
+
+ return BitConverterLE.ToUInt32 (message, startIndex + 8) == Type;
+ }
+
+ protected void ValidateArguments (byte[] message, int startIndex, int length)
+ {
+ if (message == null)
+ throw new ArgumentNullException (nameof (message));
+
+ if (startIndex < 0 || startIndex > message.Length)
+ throw new ArgumentOutOfRangeException (nameof (startIndex));
+
+ if (length < 12 || length > (message.Length - startIndex))
+ throw new ArgumentOutOfRangeException (nameof (length));
+
+ if (!CheckSignature (message, startIndex))
+ throw new ArgumentException (string.Format (CultureInfo.InvariantCulture, "Invalid Type{0} message.", Type), nameof (message));
+ }
+
+ public abstract byte[] Encode ();
+ }
+}
diff --git a/MailKit/Security/Ntlm/NtlmSingleHostData.cs b/MailKit/Security/Ntlm/NtlmSingleHostData.cs
new file mode 100644
index 0000000000..43bb25125a
--- /dev/null
+++ b/MailKit/Security/Ntlm/NtlmSingleHostData.cs
@@ -0,0 +1,184 @@
+//
+// NtlmSingleHostData.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2021 .NET Foundation and Contributors
+//
+// 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.
+//
+
+// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4
+
+using System;
+
+namespace MailKit.Security.Ntlm {
+ ///
+ /// An NTLM SingleHostData structure.
+ ///
+ ///
+ /// An NTLM SingleHostData structure.
+ ///
+ class NtlmSingleHostData
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Creates a new .
+ ///
+ /// The raw target info buffer to decode.
+ /// The starting index of the single host data structure.
+ /// The length of the single host data structure.
+ public NtlmSingleHostData (byte[] buffer, int startIndex, int length)
+ {
+ Decode (buffer, startIndex, length);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Creates a new .
+ ///
+ /// The 8-byte platform-specific blob.
+ /// The 256-bit randomly generated machine id.
+ ///
+ /// is null.
+ /// -or-
+ /// is null.
+ ///
+ ///
+ /// is not 8 bytes.
+ /// -or-
+ /// is not 32 bytes.
+ ///
+ public NtlmSingleHostData (byte[] customData, byte[] machineId)
+ {
+ if (customData == null)
+ throw new ArgumentNullException (nameof (customData));
+
+ if (customData.Length != 8)
+ throw new ArgumentException (nameof (customData));
+
+ if (machineId == null)
+ throw new ArgumentNullException (nameof (machineId));
+
+ if (machineId.Length != 32)
+ throw new ArgumentException (nameof (machineId));
+
+ CustomData = customData;
+ MachineId = machineId;
+ Size = 48;
+ }
+
+ ///
+ /// Get or set an 8-byte platform-specific blob.
+ ///
+ ///
+ /// Gets or sets an 8-byte platform-specific blob.
+ ///
+ public byte[] CustomData {
+ get; private set;
+ }
+
+ ///
+ /// Get the 256-bit randomly generated machine ID.
+ ///
+ ///
+ /// Gets the 256-bit randomly generated machine ID.
+ ///
+ /// The 256-bit randomly generated machine ID.
+ public byte[] MachineId {
+ get; private set;
+ }
+
+ ///
+ /// Get the size of the SingleHostData structure.
+ ///
+ ///
+ /// Gets the size of the SingleHostData structure.
+ ///
+ /// The size of the SingleHostData structure.
+ public int Size {
+ get; private set;
+ }
+
+ void Decode (byte[] buffer, int startIndex, int length)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException (nameof (buffer));
+
+ if (startIndex < 0 || startIndex > buffer.Length)
+ throw new ArgumentOutOfRangeException (nameof (startIndex));
+
+ if (length < 48 || length > (buffer.Length - startIndex))
+ throw new ArgumentOutOfRangeException (nameof (length));
+
+ int index = startIndex;
+
+ // Size (4 bytes): A 32-bit unsigned integer that defines the length, in bytes, of the Value field in the AV_PAIR (section 2.2.2.1) structure.
+ Size = BitConverterLE.ToInt32 (buffer, index);
+ index += 4;
+
+ // Z4 (4 bytes): A 32-bit integer value containing 0x00000000.
+ index += 4;
+
+ // CustomData (8 bytes): An 8-byte platform-specific blob containing info only relevant when the client and the server are on the same host.
+ CustomData = new byte[8];
+ Buffer.BlockCopy (buffer, index, CustomData, 0, 8);
+ index += 8;
+
+ // MachineID (32 bytes): A 256-bit random number created at computer startup to identify the calling machine.
+ MachineId = new byte[32];
+ Buffer.BlockCopy (buffer, index, MachineId, 0, 32);
+ }
+
+ ///
+ /// Encode the SingleHostData structure.
+ ///
+ ///
+ /// Encodes the SingleHostData structure.
+ ///
+ /// The encoded SingleHostData structure.
+ public byte[] Encode ()
+ {
+ var buffer = new byte[Size];
+ int index = 0;
+
+ // Size (4 bytes): A 32-bit unsigned integer that defines the length, in bytes, of the Value field in the AV_PAIR (section 2.2.2.1) structure.
+ buffer[index++] = (byte) (Size);
+ buffer[index++] = (byte) (Size >> 8);
+ buffer[index++] = (byte) (Size >> 16);
+ buffer[index++] = (byte) (Size >> 24);
+
+ // Z4 (4 bytes): A 32-bit integer value containing 0x00000000.
+ index += 4;
+
+ // CustomData (8 bytes): An 8-byte platform-specific blob containing info only relevant when the client and the server are on the same host.
+ Buffer.BlockCopy (CustomData, 0, buffer, index, 8);
+ index += 8;
+
+ // MachineID (32 bytes): A 256-bit random number created at computer startup to identify the calling machine.
+ Buffer.BlockCopy (MachineId, 0, buffer, index, 32);
+
+ return buffer;
+ }
+ }
+}
diff --git a/MailKit/Security/Ntlm/NtlmTargetInfo.cs b/MailKit/Security/Ntlm/NtlmTargetInfo.cs
new file mode 100644
index 0000000000..fa1956c20a
--- /dev/null
+++ b/MailKit/Security/Ntlm/NtlmTargetInfo.cs
@@ -0,0 +1,534 @@
+//
+// NtlmTargetInfo.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2021 .NET Foundation and Contributors
+//
+// 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.
+//
+
+// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4
+
+using System;
+using System.Text;
+using System.Collections.Generic;
+
+namespace MailKit.Security.Ntlm {
+ ///
+ /// An NTLM TargetInfo structure.
+ ///
+ ///
+ /// An NTLM TargetInfo structure.
+ ///
+ class NtlmTargetInfo
+ {
+ readonly List attributes = new List ();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Creates a new .
+ ///
+ /// The raw target info buffer to decode.
+ /// The starting index of the target info structure.
+ /// The length of the target info structure.
+ /// true if the target info strings are unicode; otherwise, false.
+ public NtlmTargetInfo (byte[] buffer, int startIndex, int length, bool unicode)
+ {
+ Decode (buffer, startIndex, length, unicode);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Creates a new .
+ ///
+ public NtlmTargetInfo ()
+ {
+ }
+
+ ///
+ /// Copy the attribute value pairs to another TargetInfo.
+ ///
+ ///
+ /// Copies the attribute value pairs to another TargetInfo.
+ ///
+ public void CopyTo (NtlmTargetInfo targetInfo)
+ {
+ targetInfo.attributes.Clear ();
+
+ foreach (var attribute in attributes) {
+ if (attribute is NtlmAttributeTimestampValuePair timestamp)
+ targetInfo.attributes.Add (new NtlmAttributeTimestampValuePair (timestamp.Attribute, timestamp.Value, timestamp.Size));
+ else if (attribute is NtlmAttributeFlagsValuePair flags)
+ targetInfo.attributes.Add (new NtlmAttributeFlagsValuePair (flags.Attribute, flags.Value, flags.Size));
+ else if (attribute is NtlmAttributeByteArrayValuePair array)
+ targetInfo.attributes.Add (new NtlmAttributeByteArrayValuePair (array.Attribute, array.Value));
+ else if (attribute is NtlmAttributeStringValuePair str)
+ targetInfo.attributes.Add (new NtlmAttributeStringValuePair (str.Attribute, str.Value));
+ }
+ }
+
+ NtlmAttributeValuePair GetAvPair (NtlmAttribute attr)
+ {
+ for (int i = 0; i < attributes.Count; i++) {
+ if (attributes[i].Attribute == attr)
+ return attributes[i];
+ }
+
+ return null;
+ }
+
+ string GetAvPairString (NtlmAttribute attr)
+ {
+ return ((NtlmAttributeStringValuePair) GetAvPair (attr))?.Value;
+ }
+
+ void SetAvPairString (NtlmAttribute attr, string value)
+ {
+ var pair = (NtlmAttributeStringValuePair) GetAvPair (attr);
+
+ if (pair == null) {
+ if (value != null)
+ attributes.Add (new NtlmAttributeStringValuePair (attr, value));
+ } else if (value != null) {
+ pair.Value = value;
+ } else {
+ attributes.Remove (pair);
+ }
+ }
+
+ byte[] GetAvPairByteArray (NtlmAttribute attr)
+ {
+ return ((NtlmAttributeByteArrayValuePair) GetAvPair (attr))?.Value;
+ }
+
+ void SetAvPairByteArray (NtlmAttribute attr, byte[] value)
+ {
+ var pair = (NtlmAttributeByteArrayValuePair) GetAvPair (attr);
+
+ if (pair == null) {
+ if (value != null)
+ attributes.Add (new NtlmAttributeByteArrayValuePair (attr, value));
+ } else if (value != null) {
+ pair.Value = value;
+ } else {
+ attributes.Remove (pair);
+ }
+ }
+
+ ///
+ /// Get or set the server's NetBIOS computer name.
+ ///
+ ///
+ /// Gets or sets the server's NetBIOS computer name.
+ ///
+ /// The server's NetBIOS computer name if available; otherwise, null.
+ public string ServerName {
+ get { return GetAvPairString (NtlmAttribute.ServerName); }
+ set { SetAvPairString (NtlmAttribute.ServerName, value); }
+ }
+
+ ///
+ /// Get or set the server's NetBIOS domain name.
+ ///
+ ///
+ /// Gets or sets the server's NetBIOS domain name.
+ ///
+ /// The server's NetBIOS domain name if available; otherwise, null.
+ public string DomainName {
+ get { return GetAvPairString (NtlmAttribute.DomainName); }
+ set { SetAvPairString (NtlmAttribute.DomainName, value); }
+ }
+
+ ///
+ /// Get or set the fully qualified domain name (FQDN) of the server.
+ ///
+ ///
+ /// Gets or sets the fully qualified domain name (FQDN) of the server.
+ ///
+ /// The fully qualified domain name (FQDN) of the server if available; otherwise, null.
+ public string DnsServerName {
+ get { return GetAvPairString (NtlmAttribute.DnsServerName); }
+ set { SetAvPairString (NtlmAttribute.DnsServerName, value); }
+ }
+
+ ///
+ /// Get or set the fully qualified domain name (FQDN) of the domain.
+ ///
+ ///
+ /// Gets or sets the fully qualified domain name (FQDN) of the domain.
+ ///
+ /// The fully qualified domain name (FQDN) of the domain if available; otherwise, null.
+ public string DnsDomainName {
+ get { return GetAvPairString (NtlmAttribute.DnsDomainName); }
+ set { SetAvPairString (NtlmAttribute.DnsDomainName, value); }
+ }
+
+ ///
+ /// Get or set the fully qualified domain name (FQDN) of the forest.
+ ///
+ ///
+ /// Gets or sets the fully qualified domain name (FQDN) of the forest.
+ ///
+ /// The fully qualified domain name (FQDN) of the forest if available; otherwise, null.
+ public string DnsTreeName {
+ get { return GetAvPairString (NtlmAttribute.DnsTreeName); }
+ set { SetAvPairString (NtlmAttribute.DnsTreeName, value); }
+ }
+
+ ///
+ /// Get or set a 32-bit value indicating server or client configuration.
+ ///
+ ///
+ /// Gets or sets a 32-bit value indicating server or client configuration.
+ /// 0x00000001: Indicates to the client that the account authentication is constrained.
+ /// 0x00000002: Indicates that the client is providing message integrity in the MIC field (section 2.2.1.3) in the AUTHENTICATE_MESSAGE.
+ /// 0x00000004: Indicates that the client is providing a target SPN generated from an untrusted source.
+ ///
+ /// The 32-bit flags value if available; otherwise, null.
+ public int? Flags {
+ get { return ((NtlmAttributeFlagsValuePair) GetAvPair (NtlmAttribute.Flags))?.Value; }
+ set {
+ var pair = (NtlmAttributeFlagsValuePair) GetAvPair (NtlmAttribute.Flags);
+
+ if (pair == null) {
+ if (value != null)
+ attributes.Add (new NtlmAttributeFlagsValuePair (NtlmAttribute.Flags, value.Value));
+ } else if (value != null) {
+ pair.Size = Math.Max (pair.Size, (short) (value.Value > short.MaxValue ? 4 : 2));
+ pair.Value = value.Value;
+ } else {
+ attributes.Remove (pair);
+ }
+ }
+ }
+
+ ///
+ /// Get or set a timestamp that contains the server local time.
+ ///
+ ///
+ /// Gets or sets a timestamp that contains the server local time.
+ /// A FILETIME structure ([MS-DTYP] section 2.3.3) in little-endian byte order that contains
+ /// the server local time. This structure is always sent in the CHALLENGE_MESSAGE.
+ ///
+ /// The local time of the server, if available; otherwise null.
+ public long? Timestamp {
+ get { return ((NtlmAttributeTimestampValuePair) GetAvPair (NtlmAttribute.Timestamp))?.Value; }
+ set {
+ var pair = (NtlmAttributeTimestampValuePair) GetAvPair (NtlmAttribute.Timestamp);
+
+ if (pair == null) {
+ if (value != null)
+ attributes.Add (new NtlmAttributeTimestampValuePair (NtlmAttribute.Timestamp, value.Value));
+ } else if (value != null) {
+ pair.Size = Math.Max (pair.Size, (short) (value.Value > int.MaxValue ? 8 : 4));
+ pair.Value = value.Value;
+ } else {
+ attributes.Remove (pair);
+ }
+ }
+ }
+
+ ///
+ /// Get or set the single host data structure.
+ ///
+ ///
+ /// Gets or sets the single host data structure.
+ /// The Value field contains a platform-specific blob, as well as a MachineID created at computer startup to identify the calling machine.
+ ///
+ /// The single host data structure, if available; otherwise, null.
+ public byte[] SingleHost {
+ get { return GetAvPairByteArray (NtlmAttribute.SingleHost); }
+ set { SetAvPairByteArray (NtlmAttribute.SingleHost, value); }
+ }
+
+ ///
+ /// Get or set the Service Principal Name (SPN) of the server.
+ ///
+ ///
+ /// Gets or sets the Service Principal Name (SPN) of the server.
+ ///
+ /// The Service Principal Name (SPN) of the server, if available; otherwise, null.
+ public string TargetName {
+ get { return GetAvPairString (NtlmAttribute.TargetName); }
+ set { SetAvPairString (NtlmAttribute.TargetName, value); }
+ }
+
+ ///
+ /// Get or set the channel binding hash.
+ ///
+ ///
+ /// Gets or sets the channel binding hash.
+ ///
+ /// An MD5 hash of the channel binding data, if available; otherwise null.
+ public byte[] ChannelBinding {
+ get { return GetAvPairByteArray (NtlmAttribute.ChannelBinding); }
+ set { SetAvPairByteArray (NtlmAttribute.ChannelBinding, value); }
+ }
+
+ static byte[] DecodeByteArray (byte[] buffer, ref int index)
+ {
+ var length = BitConverterLE.ToInt16 (buffer, index);
+ var value = new byte[length];
+
+ Buffer.BlockCopy (buffer, index + 2, value, 0, length);
+
+ index += 2 + length;
+
+ return value;
+ }
+
+ static string DecodeString (byte[] buffer, ref int index, bool unicode)
+ {
+ var encoding = unicode ? Encoding.Unicode : Encoding.UTF8;
+ var length = BitConverterLE.ToInt16 (buffer, index);
+ var value = encoding.GetString (buffer, index + 2, length);
+
+ index += 2 + length;
+
+ return value;
+ }
+
+ static int DecodeFlags (byte[] buffer, ref int index, out short size)
+ {
+ size = BitConverterLE.ToInt16 (buffer, index);
+ int flags;
+
+ index += 2;
+
+ switch (size) {
+ case 4: flags = BitConverterLE.ToInt32 (buffer, index); break;
+ case 2: flags = BitConverterLE.ToInt16 (buffer, index); break;
+ default: flags = 0; break;
+ }
+
+ index += size;
+
+ return flags;
+ }
+
+ static long DecodeTimestamp (byte[] buffer, ref int index, out short size)
+ {
+ size = BitConverterLE.ToInt16 (buffer, index);
+ long value;
+
+ index += 2;
+
+ switch (size) {
+ case 8:
+ long lo = BitConverterLE.ToUInt32 (buffer, index);
+ long hi = BitConverterLE.ToUInt32 (buffer, index + 4);
+ value = (hi << 32) | lo;
+ break;
+ case 4: value = BitConverterLE.ToUInt32 (buffer, index); break;
+ case 2: value = BitConverterLE.ToUInt16 (buffer, index); break;
+ default: value = 0; break;
+ }
+
+ index += size;
+
+ return value;
+ }
+
+ void Decode (byte[] buffer, int startIndex, int length, bool unicode)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException (nameof (buffer));
+
+ if (startIndex < 0 || startIndex > buffer.Length)
+ throw new ArgumentOutOfRangeException (nameof (startIndex));
+
+ if (length < 12 || length > (buffer.Length - startIndex))
+ throw new ArgumentOutOfRangeException (nameof (length));
+
+ int index = startIndex;
+
+ do {
+ var attr = (NtlmAttribute) BitConverterLE.ToInt16 (buffer, index);
+ short size;
+
+ index += 2;
+
+ switch (attr) {
+ case NtlmAttribute.EOL:
+ index = startIndex + length;
+ break;
+ case NtlmAttribute.ServerName:
+ case NtlmAttribute.DomainName:
+ case NtlmAttribute.DnsServerName:
+ case NtlmAttribute.DnsDomainName:
+ case NtlmAttribute.DnsTreeName:
+ case NtlmAttribute.TargetName:
+ attributes.Add (new NtlmAttributeStringValuePair (attr, DecodeString (buffer, ref index, unicode)));
+ break;
+ case NtlmAttribute.Flags:
+ attributes.Add (new NtlmAttributeFlagsValuePair (attr, DecodeFlags (buffer, ref index, out size), size));
+ break;
+ case NtlmAttribute.Timestamp:
+ attributes.Add (new NtlmAttributeTimestampValuePair (attr, DecodeTimestamp (buffer, ref index, out size), size));
+ break;
+ default:
+ attributes.Add (new NtlmAttributeByteArrayValuePair (attr, DecodeByteArray (buffer, ref index)));
+ break;
+ }
+ } while (index < startIndex + length);
+ }
+
+ int CalculateSize (bool unicode)
+ {
+ var encoding = unicode ? Encoding.Unicode : Encoding.UTF8;
+ int length = 4;
+
+ foreach (var attribute in attributes) {
+ switch (attribute.Attribute) {
+ case NtlmAttribute.ServerName:
+ case NtlmAttribute.DomainName:
+ case NtlmAttribute.DnsServerName:
+ case NtlmAttribute.DnsDomainName:
+ case NtlmAttribute.DnsTreeName:
+ case NtlmAttribute.TargetName:
+ var str = (NtlmAttributeStringValuePair) attribute;
+ length += 4 + encoding.GetByteCount (str.Value);
+ break;
+ case NtlmAttribute.Flags:
+ var flags = (NtlmAttributeFlagsValuePair) attribute;
+ length += 4 + flags.Size;
+ break;
+ case NtlmAttribute.Timestamp:
+ var timestamp = (NtlmAttributeTimestampValuePair) attribute;
+ length += 4 + timestamp.Size;
+ break;
+ default:
+ var channelBinding = (NtlmAttributeByteArrayValuePair) attribute;
+ length += 4 + channelBinding.Value.Length;
+ break;
+ }
+ }
+
+ return length;
+ }
+
+ static void EncodeInt16 (byte[] buf, ref int index, short value)
+ {
+ buf[index++] = (byte) (value);
+ buf[index++] = (byte) (value >> 8);
+ }
+
+ static void EncodeInt32 (byte[] buf, ref int index, int value)
+ {
+ buf[index++] = (byte) (value);
+ buf[index++] = (byte) (value >> 8);
+ buf[index++] = (byte) (value >> 16);
+ buf[index++] = (byte) (value >> 24);
+ }
+
+ static void EncodeTypeAndLength (byte[] buf, ref int index, NtlmAttribute attr, short length)
+ {
+ EncodeInt16 (buf, ref index, (short) attr);
+ EncodeInt16 (buf, ref index, length);
+ }
+
+ static void EncodeByteArray (byte[] buf, ref int index, NtlmAttribute attr, byte[] value)
+ {
+ EncodeTypeAndLength (buf, ref index, attr, (short) value.Length);
+ Buffer.BlockCopy (value, 0, buf, index, value.Length);
+ index += value.Length;
+ }
+
+ static void EncodeString (byte[] buf, ref int index, NtlmAttribute attr, string value, bool unicode)
+ {
+ var encoding = unicode ? Encoding.Unicode : Encoding.UTF8;
+ int length = encoding.GetByteCount (value);
+
+ EncodeTypeAndLength (buf, ref index, attr, (short) length);
+ encoding.GetBytes (value, 0, value.Length, buf, index);
+ index += length;
+ }
+
+ static void EncodeTimestamp (byte[] buf, ref int index, NtlmAttribute attr, long value, short size)
+ {
+ EncodeTypeAndLength (buf, ref index, attr, size);
+
+ switch (size) {
+ case 2: EncodeInt16 (buf, ref index, (short) (value & 0xffff)); break;
+ case 4: EncodeInt32 (buf, ref index, (int) (value & 0xffffffff)); break;
+ default:
+ EncodeInt32 (buf, ref index, (int) (value & 0xffffffff));
+ EncodeInt32 (buf, ref index, (int) (value >> 32));
+ break;
+ }
+ }
+
+ static void EncodeFlags (byte[] buf, ref int index, NtlmAttribute attr, int value, short size)
+ {
+ EncodeTypeAndLength (buf, ref index, attr, size);
+
+ switch (size) {
+ case 2: EncodeInt16 (buf, ref index, (short) value); break;
+ default: EncodeInt32 (buf, ref index, value); break;
+ }
+ }
+
+ ///
+ /// Encode the TargetInfo structure.
+ ///
+ ///
+ /// Encodes the TargetInfo structure.
+ ///
+ /// true if the strings should be encoded in Unicode; otherwise, false.
+ /// The encoded TargetInfo.
+ public byte[] Encode (bool unicode)
+ {
+ var buf = new byte[CalculateSize (unicode)];
+ int index = 0;
+
+ foreach (var attribute in attributes) {
+ switch (attribute.Attribute) {
+ case NtlmAttribute.ServerName:
+ case NtlmAttribute.DomainName:
+ case NtlmAttribute.DnsServerName:
+ case NtlmAttribute.DnsDomainName:
+ case NtlmAttribute.DnsTreeName:
+ case NtlmAttribute.TargetName:
+ var str = (NtlmAttributeStringValuePair) attribute;
+ EncodeString (buf, ref index, str.Attribute, str.Value, unicode);
+ break;
+ case NtlmAttribute.Flags:
+ var flags = (NtlmAttributeFlagsValuePair) attribute;
+ EncodeFlags (buf, ref index, flags.Attribute, flags.Value, flags.Size);
+ break;
+ case NtlmAttribute.Timestamp:
+ var timestamp = (NtlmAttributeTimestampValuePair) attribute;
+ EncodeTimestamp (buf, ref index, timestamp.Attribute, timestamp.Value, timestamp.Size);
+ break;
+ default:
+ var generic = (NtlmAttributeByteArrayValuePair) attribute;
+ EncodeByteArray (buf, ref index, generic.Attribute, generic.Value);
+ break;
+ }
+ }
+
+ return buf;
+ }
+ }
+}
diff --git a/MailKit/Security/Ntlm/NtlmUtils.cs b/MailKit/Security/Ntlm/NtlmUtils.cs
new file mode 100644
index 0000000000..917f8d7197
--- /dev/null
+++ b/MailKit/Security/Ntlm/NtlmUtils.cs
@@ -0,0 +1,242 @@
+//
+// NtlmUtils.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2021 .NET Foundation and Contributors
+//
+// 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.
+//
+
+// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4
+
+using System;
+using System.Text;
+using System.Security.Cryptography;
+
+using SSCMD5 = System.Security.Cryptography.MD5;
+
+namespace MailKit.Security.Ntlm {
+ static class NtlmUtils
+ {
+ internal static readonly byte[] ClientSealMagic = Encoding.ASCII.GetBytes ("session key to client-to-server sealing key magic constant");
+ static readonly byte[] ServerSealMagic = Encoding.ASCII.GetBytes ("session key to server-to-client sealing key magic constant");
+ static readonly byte[] ClientSignMagic = Encoding.ASCII.GetBytes ("session key to client-to-server signing key magic constant");
+ static readonly byte[] ServerSignMagic = Encoding.ASCII.GetBytes ("session key to server-to-client signing key magic constant");
+ static readonly byte[] SealKeySuffix40 = new byte[] { 0xe5, 0x38, 0xb0 };
+ static readonly byte[] SealKeySuffix56 = new byte[] { 0xa0 };
+ static readonly byte[] Responserversion = new byte[] { 1 };
+ static readonly byte[] HiResponserversion = new byte[] { 1 };
+ static readonly byte[] Z24 = new byte[24];
+ static readonly byte[] Z6 = new byte[6];
+ static readonly byte[] Z4 = new byte[4];
+ static readonly byte[] Z1 = new byte[1];
+
+ public static byte[] ConcatenationOf (params string[] values)
+ {
+ var concatenatedValue = string.Concat (values);
+
+ return Encoding.Unicode.GetBytes (concatenatedValue);
+ }
+
+ public static byte[] ConcatenationOf (params byte[][] values)
+ {
+ int index = 0, length = 0;
+
+ for (int i = 0; i < values.Length; i++)
+ length += values[i].Length;
+
+ var concatenated = new byte[length];
+ for (int i = 0; i < values.Length; i++) {
+ length = values[i].Length;
+ Buffer.BlockCopy (values[i], 0, concatenated, index, length);
+ index += length;
+ }
+
+ return concatenated;
+ }
+
+ static byte[] MD4 (byte[] buffer)
+ {
+ using (var md4 = new MD4 ()) {
+ var hash = md4.ComputeHash (buffer);
+ Array.Clear (buffer, 0, buffer.Length);
+ return hash;
+ }
+ }
+
+ static byte[] MD4 (string password)
+ {
+ return MD4 (Encoding.Unicode.GetBytes (password));
+ }
+
+ public static byte[] MD5 (byte[] buffer)
+ {
+ using (var md5 = SSCMD5.Create ()) {
+ var hash = md5.ComputeHash (buffer);
+ Array.Clear (buffer, 0, buffer.Length);
+ return hash;
+ }
+ }
+
+ public static byte[] HMACMD5 (byte[] key, params byte[][] values)
+ {
+ using (var md5 = new HMACMD5 (key)) {
+ int i;
+
+ for (i = 0; i < values.Length - 1; i++)
+ md5.TransformBlock (values[i], 0, values[i].Length, null, 0);
+
+ md5.TransformFinalBlock (values[i], 0, values[i].Length);
+
+ return md5.Hash;
+ }
+ }
+
+ public static byte[] NONCE (int size)
+ {
+ var nonce = new byte[size];
+
+ using (var rng = RandomNumberGenerator.Create ())
+ rng.GetBytes (nonce);
+
+ return nonce;
+ }
+
+ public static byte[] RC4K (byte[] key, byte[] message)
+ {
+ try {
+ using (var rc4 = new RC4 ()) {
+ rc4.Key = key;
+
+ return rc4.TransformFinalBlock (message, 0, message.Length);
+ }
+ } finally {
+ Array.Clear (key, 0, key.Length);
+ }
+ }
+
+ public static byte[] SEALKEY (NtlmFlags flags, byte[] exportedSessionKey, bool client = true)
+ {
+ if ((flags & NtlmFlags.NegotiateExtendedSessionSecurity) != 0) {
+ byte[] subkey;
+
+ if ((flags & NtlmFlags.Negotiate128) != 0) {
+ subkey = exportedSessionKey;
+ } else if ((flags & NtlmFlags.Negotiate56) != 0) {
+ subkey = new byte[7];
+ Buffer.BlockCopy (exportedSessionKey, 0, subkey, 0, subkey.Length);
+ } else {
+ subkey = new byte[5];
+ Buffer.BlockCopy (exportedSessionKey, 0, subkey, 0, subkey.Length);
+ }
+
+ var magic = client ? ClientSealMagic : ServerSealMagic;
+ var sealKey = MD5 (ConcatenationOf (subkey, magic));
+
+ if (subkey != exportedSessionKey)
+ Array.Clear (subkey, 0, subkey.Length);
+
+ return sealKey;
+ } else if ((flags & NtlmFlags.NegotiateLanManagerKey) != 0) {
+ byte[] suffix;
+ int length;
+
+ if ((flags & NtlmFlags.Negotiate56) != 0) {
+ suffix = SealKeySuffix56;
+ length = 7;
+ } else {
+ suffix = SealKeySuffix40;
+ length = 5;
+ }
+
+ var sealKey = new byte[length + suffix.Length];
+ Buffer.BlockCopy (exportedSessionKey, 0, sealKey, 0, length);
+ Buffer.BlockCopy (suffix, 0, sealKey, length, suffix.Length);
+
+ return sealKey;
+ } else {
+ return exportedSessionKey;
+ }
+ }
+
+ public static byte[] SIGNKEY (NtlmFlags flags, byte[] exportedSessionKey, bool client = true)
+ {
+ if ((flags & NtlmFlags.NegotiateExtendedSessionSecurity) != 0) {
+ var magic = client ? ClientSignMagic : ServerSignMagic;
+ return MD5 (ConcatenationOf (exportedSessionKey, magic));
+ } else {
+ return null;
+ }
+ }
+
+ static byte[] NTOWFv2 (string domain, string userName, string password)
+ {
+ var hash = MD4 (password);
+ byte[] responseKey;
+
+ using (var md5 = new HMACMD5 (hash)) {
+ var userDom = ConcatenationOf (userName.ToUpperInvariant (), domain);
+ responseKey = md5.ComputeHash (userDom);
+ }
+
+ Array.Clear (hash, 0, hash.Length);
+
+ return responseKey;
+ }
+
+ public static void ComputeNtlmV2 (Type2Message type2, string domain, string userName, string password, byte[] targetInfo, byte[] clientChallenge, long? time, out byte[] ntChallengeResponse, out byte[] lmChallengeResponse, out byte[] sessionBaseKey)
+ {
+ if (userName.Length == 0 && password.Length == 0) {
+ // Special case for anonymous authentication
+ ntChallengeResponse = null;
+ lmChallengeResponse = Z1;
+ sessionBaseKey = null;
+ return;
+ }
+
+ var timestamp = (time ?? DateTime.UtcNow.Ticks) - 504911232000000000;
+ var responseKey = NTOWFv2 (domain, userName, password);
+
+ // Note: If NTLM v2 authentication is used, the client SHOULD send the timestamp in the CHALLENGE_MESSAGE.
+ if (type2.TargetInfo?.Timestamp != null)
+ timestamp = type2.TargetInfo.Timestamp.Value;
+
+ var temp = ConcatenationOf (Responserversion, HiResponserversion, Z6, BitConverterLE.GetBytes (timestamp), clientChallenge, Z4, targetInfo, Z4);
+ var proof = HMACMD5 (responseKey, type2.ServerChallenge, temp);
+
+ sessionBaseKey = HMACMD5 (responseKey, proof);
+
+ ntChallengeResponse = ConcatenationOf (proof, temp);
+ Array.Clear (proof, 0, proof.Length);
+ Array.Clear (temp, 0, temp.Length);
+
+ var hash = HMACMD5 (responseKey, type2.ServerChallenge, clientChallenge);
+ Array.Clear (responseKey, 0, responseKey.Length);
+
+ // Note: If NTLM v2 authentication is used and the CHALLENGE_MESSAGE TargetInfo field (section 2.2.1.2) has an
+ // MsvAvTimestamp present, the client SHOULD NOT send the LmChallengeResponse and SHOULD send Z(24) instead.
+ if (type2.TargetInfo?.Timestamp == null)
+ lmChallengeResponse = ConcatenationOf (hash, clientChallenge);
+ else
+ lmChallengeResponse = Z24;
+ Array.Clear (hash, 0, hash.Length);
+ }
+ }
+}
diff --git a/MailKit/Security/Ntlm/RC4.cs b/MailKit/Security/Ntlm/RC4.cs
new file mode 100644
index 0000000000..5ab1132935
--- /dev/null
+++ b/MailKit/Security/Ntlm/RC4.cs
@@ -0,0 +1,205 @@
+//
+// 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.Security.Cryptography;
+
+namespace MailKit.Security.Ntlm {
+ // References:
+ // a. Usenet 1994 - RC4 Algorithm revealed
+ // http://www.qrst.de/html/dsds/rc4.htm
+
+ class RC4 : SymmetricAlgorithm, ICryptoTransform
+ {
+ byte[] key, state;
+ byte x, y;
+ bool disposed;
+
+ public RC4 () : base ()
+ {
+ state = new byte[256];
+ KeySizeValue = 64;
+ }
+
+ ~RC4 ()
+ {
+ Dispose (false);
+ }
+
+ public bool CanReuseTransform {
+ get { return false; }
+ }
+
+ public bool CanTransformMultipleBlocks {
+ get { return true; }
+ }
+
+ public int InputBlockSize {
+ get { return 1; }
+ }
+
+ public int OutputBlockSize {
+ get { return 1; }
+ }
+
+ public override byte[] Key {
+ get {
+ if (key == null)
+ throw new InvalidOperationException ();
+
+ return (byte[]) key.Clone ();
+ }
+ set {
+ if (value == null)
+ throw new ArgumentNullException (nameof (value));
+
+ if (value.Length == 0)
+ throw new ArgumentException ("Invalid key length.", nameof (value));
+
+ KeySizeValue = value.Length << 3;
+ key = (byte[]) value.Clone ();
+ KeySetup (key);
+ }
+ }
+
+ public override ICryptoTransform CreateEncryptor (byte[] rgbKey, byte[] rgvIV)
+ {
+ return new RC4 { Key = rgbKey };
+ }
+
+ public override ICryptoTransform CreateDecryptor (byte[] rgbKey, byte[] rgvIV)
+ {
+ return new RC4 { Key = rgbKey };
+ }
+
+ public override void GenerateIV ()
+ {
+ // not used for a stream cipher
+ IV = new byte[0];
+ }
+
+ public override void GenerateKey ()
+ {
+ key = new byte[KeySizeValue >> 3];
+ RandomNumberGenerator.Create ().GetBytes (key);
+ KeySetup (key);
+ }
+
+ void KeySetup (byte[] key)
+ {
+ byte index1 = 0;
+ byte index2 = 0;
+
+ for (int counter = 0; counter < 256; counter++)
+ state[counter] = (byte) counter;
+
+ x = y = 0;
+
+ for (int counter = 0; counter < 256; counter++) {
+ index2 = (byte) (key[index1] + state[counter] + index2);
+ // swap byte
+ byte tmp = state[counter];
+ state[counter] = state[index2];
+ state[index2] = tmp;
+ index1 = (byte) ((index1 + 1) % key.Length);
+ }
+ }
+
+ void CheckInput (byte[] inputBuffer, int inputOffset, int inputCount)
+ {
+ if (inputBuffer == null)
+ throw new ArgumentNullException (nameof (inputBuffer));
+
+ if (inputOffset < 0 || inputOffset > inputBuffer.Length)
+ throw new ArgumentOutOfRangeException (nameof (inputOffset));
+
+ if (inputCount < 0 || inputOffset > inputBuffer.Length - inputCount)
+ throw new ArgumentOutOfRangeException (nameof (inputCount));
+ }
+
+ public int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
+ {
+ CheckInput (inputBuffer, inputOffset, inputCount);
+
+ // check output parameters
+ if (outputBuffer == null)
+ throw new ArgumentNullException (nameof (outputBuffer));
+
+ if (outputOffset < 0 || outputOffset > outputBuffer.Length - inputCount)
+ throw new ArgumentOutOfRangeException (nameof (outputOffset));
+
+ return InternalTransformBlock (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
+ }
+
+ int InternalTransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
+ {
+ byte xorIndex;
+
+ for (int counter = 0; counter < inputCount; counter++) {
+ x = (byte) (x + 1);
+ y = (byte) (state[x] + y);
+
+ // swap byte
+ byte tmp = state[x];
+ state[x] = state[y];
+ state[y] = tmp;
+
+ xorIndex = (byte) (state[x] + state[y]);
+ outputBuffer[outputOffset + counter] = (byte) (inputBuffer[inputOffset + counter] ^ state[xorIndex]);
+ }
+ return inputCount;
+ }
+
+ public byte[] TransformFinalBlock (byte[] inputBuffer, int inputOffset, int inputCount)
+ {
+ CheckInput (inputBuffer, inputOffset, inputCount);
+
+ var output = new byte[inputCount];
+ InternalTransformBlock (inputBuffer, inputOffset, inputCount, output, 0);
+ return output;
+ }
+
+ protected override void Dispose (bool disposing)
+ {
+ if (disposed)
+ return;
+
+ x = y = 0;
+
+ if (key != null)
+ Array.Clear (key, 0, key.Length);
+
+ Array.Clear (state, 0, state.Length);
+
+ if (disposing) {
+ state = null;
+ key = null;
+ }
+
+ disposed = true;
+ }
+ }
+}
diff --git a/MailKit/Security/Ntlm/TargetInfo.cs b/MailKit/Security/Ntlm/TargetInfo.cs
deleted file mode 100644
index dbb2f9a8f4..0000000000
--- a/MailKit/Security/Ntlm/TargetInfo.cs
+++ /dev/null
@@ -1,288 +0,0 @@
-//
-// TargetInfo.cs
-//
-// Author: Jeffrey Stedfast
-//
-// Copyright (c) 2013-2021 .NET Foundation and Contributors
-//
-// 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.Text;
-
-namespace MailKit.Security.Ntlm {
- class TargetInfo
- {
- public TargetInfo (byte[] buffer, int startIndex, int length, bool unicode)
- {
- Decode (buffer, startIndex, length, unicode);
- }
-
- public TargetInfo ()
- {
- }
-
- public int? Flags {
- get; set;
- }
-
- public byte[] ChannelBinding {
- get; set;
- }
-
- public string DomainName {
- get; set;
- }
-
- public string ServerName {
- get; set;
- }
-
- public string DnsDomainName {
- get; set;
- }
-
- public string DnsServerName {
- get; set;
- }
-
- public string DnsTreeName {
- get; set;
- }
-
- public string TargetName {
- get; set;
- }
-
- public long Timestamp {
- get; set;
- }
-
- static byte[] DecodeByteArray (byte[] buffer, ref int index)
- {
- var length = BitConverterLE.ToInt16 (buffer, index);
- var value = new byte[length];
-
- Buffer.BlockCopy (buffer, index + 2, value, 0, length);
-
- index += 2 + length;
-
- return value;
- }
-
- static string DecodeString (byte[] buffer, ref int index, bool unicode)
- {
- var encoding = unicode ? Encoding.Unicode : Encoding.UTF8;
- var length = BitConverterLE.ToInt16 (buffer, index);
- var value = encoding.GetString (buffer, index + 2, length);
-
- index += 2 + length;
-
- return value;
- }
-
- static int DecodeFlags (byte[] buffer, ref int index)
- {
- short nbytes = BitConverterLE.ToInt16 (buffer, index);
- int flags;
-
- index += 2;
-
- switch (nbytes) {
- case 4: flags = BitConverterLE.ToInt32 (buffer, index); break;
- case 2: flags = BitConverterLE.ToInt16 (buffer, index); break;
- default: flags = 0; break;
- }
-
- index += nbytes;
-
- return flags;
- }
-
- static long DecodeTimestamp (byte[] buffer, ref int index)
- {
- short nbytes = BitConverterLE.ToInt16 (buffer, index);
- long lo, hi;
-
- index += 2;
-
- switch (nbytes) {
- case 8:
- lo = BitConverterLE.ToUInt32 (buffer, index);
- index += 4;
- hi = BitConverterLE.ToUInt32 (buffer, index);
- index += 4;
- return (hi << 32) | lo;
- case 4:
- lo = BitConverterLE.ToUInt32 (buffer, index);
- index += 4;
- return lo;
- case 2:
- lo = BitConverterLE.ToUInt16 (buffer, index);
- index += 2;
- return lo;
- default:
- index += nbytes;
- return 0;
- }
- }
-
- void Decode (byte[] buffer, int startIndex, int length, bool unicode)
- {
- int index = startIndex;
-
- do {
- var type = BitConverterLE.ToInt16 (buffer, index);
-
- index += 2;
-
- switch (type) {
- case 0: index = startIndex + length; break; // a 'type' of 0 terminates the TargetInfo
- case 1: ServerName = DecodeString (buffer, ref index, unicode); break;
- case 2: DomainName = DecodeString (buffer, ref index, unicode); break;
- case 3: DnsServerName = DecodeString (buffer, ref index, unicode); break;
- case 4: DnsDomainName = DecodeString (buffer, ref index, unicode); break;
- case 5: DnsTreeName = DecodeString (buffer, ref index, unicode); break;
- case 6: Flags = DecodeFlags (buffer, ref index); break;
- case 7: Timestamp = DecodeTimestamp (buffer, ref index); break;
- case 9: TargetName = DecodeString (buffer, ref index, unicode); break;
- case 10: ChannelBinding = DecodeByteArray (buffer, ref index); break;
- default: index += 2 + BitConverterLE.ToInt16 (buffer, index); break;
- }
- } while (index < startIndex + length);
- }
-
- int CalculateSize (bool unicode)
- {
- var encoding = unicode ? Encoding.Unicode : Encoding.UTF8;
- int length = 4;
-
- if (!string.IsNullOrEmpty (DomainName))
- length += 4 + encoding.GetByteCount (DomainName);
-
- if (!string.IsNullOrEmpty (ServerName))
- length += 4 + encoding.GetByteCount (ServerName);
-
- if (!string.IsNullOrEmpty (DnsDomainName))
- length += 4 + encoding.GetByteCount (DnsDomainName);
-
- if (!string.IsNullOrEmpty (DnsServerName))
- length += 4 + encoding.GetByteCount (DnsServerName);
-
- if (!string.IsNullOrEmpty (DnsTreeName))
- length += 4 + encoding.GetByteCount (DnsTreeName);
-
- if (Flags.HasValue)
- length += 8;
-
- if (Timestamp != 0)
- length += 12;
-
- if (!string.IsNullOrEmpty (TargetName))
- length += 4 + encoding.GetByteCount (TargetName);
-
- if (ChannelBinding != null && ChannelBinding.Length > 0)
- length += 4 + ChannelBinding.Length;
-
- return length;
- }
-
- static void EncodeTypeAndLength (byte[] buf, ref int index, short type, short length)
- {
- buf[index++] = (byte) (type);
- buf[index++] = (byte) (type >> 8);
- buf[index++] = (byte) (length);
- buf[index++] = (byte) (length >> 8);
- }
-
- static void EncodeByteArray (byte[] buf, ref int index, short type, byte[] value)
- {
- EncodeTypeAndLength (buf, ref index, type, (short) value.Length);
- Buffer.BlockCopy (value, 0, buf, index, value.Length);
- index += value.Length;
- }
-
- static void EncodeString (byte[] buf, ref int index, short type, string value, bool unicode)
- {
- var encoding = unicode ? Encoding.Unicode : Encoding.UTF8;
- int length = encoding.GetByteCount (value);
-
- EncodeTypeAndLength (buf, ref index, type, (short) length);
- encoding.GetBytes (value, 0, value.Length, buf, index);
- index += length;
- }
-
- static void EncodeInt32 (byte[] buf, ref int index, int value)
- {
- buf[index++] = (byte) (value);
- buf[index++] = (byte) (value >> 8);
- buf[index++] = (byte) (value >> 16);
- buf[index++] = (byte) (value >> 24);
- }
-
- static void EncodeTimestamp (byte[] buf, ref int index, short type, long value)
- {
- EncodeTypeAndLength (buf, ref index, type, 8);
- EncodeInt32 (buf, ref index, (int) (value & 0xffffffff));
- EncodeInt32 (buf, ref index, (int) (value >> 32));
- }
-
- static void EncodeFlags (byte[] buf, ref int index, short type, int value)
- {
- EncodeTypeAndLength (buf, ref index, type, 4);
- EncodeInt32 (buf, ref index, value);
- }
-
- public byte[] Encode (bool unicode)
- {
- var buf = new byte[CalculateSize (unicode)];
- int index = 0;
-
- if (!string.IsNullOrEmpty (DomainName))
- EncodeString (buf, ref index, 2, DomainName, unicode);
-
- if (!string.IsNullOrEmpty (ServerName))
- EncodeString (buf, ref index, 1, ServerName, unicode);
-
- if (!string.IsNullOrEmpty (DnsDomainName))
- EncodeString (buf, ref index, 4, DnsDomainName, unicode);
-
- if (!string.IsNullOrEmpty (DnsServerName))
- EncodeString (buf, ref index, 3, DnsServerName, unicode);
-
- if (!string.IsNullOrEmpty (DnsTreeName))
- EncodeString (buf, ref index, 5, DnsTreeName, unicode);
-
- if (Flags.HasValue)
- EncodeFlags (buf, ref index, 6, Flags.Value);
-
- if (Timestamp != 0)
- EncodeTimestamp (buf, ref index, 7, Timestamp);
-
- if (!string.IsNullOrEmpty (TargetName))
- EncodeString (buf, ref index, 9, TargetName, unicode);
-
- if (ChannelBinding != null && ChannelBinding.Length > 0)
- EncodeByteArray (buf, ref index, 10, ChannelBinding);
-
- return buf;
- }
- }
-}
diff --git a/MailKit/Security/Ntlm/Type1Message.cs b/MailKit/Security/Ntlm/Type1Message.cs
index 27af57fb2d..debacce6b0 100644
--- a/MailKit/Security/Ntlm/Type1Message.cs
+++ b/MailKit/Security/Ntlm/Type1Message.cs
@@ -1,92 +1,89 @@
//
-// Mono.Security.Protocol.Ntlm.Type1Message - Negotiation
+// Type1Message.cs
//
-// Authors: Sebastien Pouliot
-// Jeffrey Stedfast
+// Author: Jeffrey Stedfast
//
-// Copyright (c) 2003 Motus Technologies Inc. (http://www.motus.com)
-// Copyright (c) 2004 Novell, Inc (http://www.novell.com)
// Copyright (c) 2013-2021 .NET Foundation and Contributors
//
-// References
-// a. NTLM Authentication Scheme for HTTP, Ronald Tschalär
-// http://www.innovation.ch/java/ntlm.html
-// b. The NTLM Authentication Protocol, Copyright © 2003 Eric Glass
-// http://davenport.sourceforge.net/ntlm.html
+// 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:
//
-// 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.
+// 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.
+//
+
+// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4
using System;
using System.Text;
namespace MailKit.Security.Ntlm {
- class Type1Message : MessageBase
+ class Type1Message : NtlmMessageBase
{
- internal static readonly NtlmFlags DefaultFlags = NtlmFlags.NegotiateNtlm | NtlmFlags.NegotiateOem | NtlmFlags.NegotiateUnicode | NtlmFlags.RequestTarget;
+ // System.Net.Mail seems to default to: NtlmFlags.Negotiate56 | NtlmFlags.NegotiateUnicode | NtlmFlags.NegotiateOem | NtlmFlags.RequestTarget | NtlmFlags.NegotiateNtlm | NtlmFlags.NegotiateAlwaysSign | NtlmFlags.NegotiateExtendedSessionSecurity | NtlmFlags.NegotiateVersion | NtlmFlags.Negotiate128
+ internal static readonly NtlmFlags DefaultFlags = NtlmFlags.Negotiate56 | NtlmFlags.NegotiateUnicode | NtlmFlags.NegotiateOem | NtlmFlags.RequestTarget | NtlmFlags.NegotiateNtlm | NtlmFlags.NegotiateAlwaysSign | NtlmFlags.NegotiateExtendedSessionSecurity | NtlmFlags.Negotiate128;
- string workstation;
- string domain;
+ byte[] cached;
- public Type1Message (string workstation, string domainName, Version osVersion) : base (1)
+ public Type1Message (NtlmFlags flags, string domain, string workstation, Version osVersion = null) : base (1)
{
- Flags = DefaultFlags;
- Workstation = workstation;
- OSVersion = osVersion;
- Domain = domainName;
+ Flags = flags & ~(NtlmFlags.NegotiateDomainSupplied | NtlmFlags.NegotiateWorkstationSupplied | NtlmFlags.NegotiateVersion);
- if (osVersion != null)
+ // Note: If the NTLMSSP_NEGOTIATE_VERSION flag is set by the client application, the Version field
+ // MUST be set to the current version (section 2.2.2.10), the DomainName field MUST be set to
+ // a zero-length string, and the Workstation field MUST be set to a zero-length string.
+ if (osVersion != null) {
Flags |= NtlmFlags.NegotiateVersion;
+ Workstation = string.Empty;
+ Domain = string.Empty;
+ OSVersion = osVersion;
+ } else {
+ if (!string.IsNullOrEmpty (workstation)) {
+ Flags |= NtlmFlags.NegotiateWorkstationSupplied;
+ Workstation = workstation.ToUpperInvariant ();
+ } else {
+ Workstation = string.Empty;
+ }
+
+ if (!string.IsNullOrEmpty (domain)) {
+ Flags |= NtlmFlags.NegotiateDomainSupplied;
+ Domain = domain.ToUpperInvariant ();
+ } else {
+ Domain = string.Empty;
+ }
+ }
+ }
+
+ public Type1Message (string domain = null, string workstation = null, Version osVersion = null) : this (DefaultFlags, domain, workstation, osVersion)
+ {
}
public Type1Message (byte[] message, int startIndex, int length) : base (1)
{
Decode (message, startIndex, length);
+
+ cached = new byte[length];
+ Buffer.BlockCopy (message, startIndex, cached, 0, length);
}
public string Domain {
- get { return domain; }
- set {
- if (string.IsNullOrEmpty (value)) {
- Flags &= ~NtlmFlags.NegotiateDomainSupplied;
- value = string.Empty;
- } else {
- Flags |= NtlmFlags.NegotiateDomainSupplied;
- }
-
- domain = value;
- }
+ get; private set;
}
public string Workstation {
- get { return workstation; }
- set {
- if (string.IsNullOrEmpty (value)) {
- Flags &= ~NtlmFlags.NegotiateWorkstationSupplied;
- value = string.Empty;
- } else {
- Flags |= NtlmFlags.NegotiateWorkstationSupplied;
- }
-
- workstation = value;
- }
+ get; private set;
}
void Decode (byte[] message, int startIndex, int length)
@@ -98,12 +95,12 @@ void Decode (byte[] message, int startIndex, int length)
// decode the domain
var domainLength = BitConverterLE.ToUInt16 (message, startIndex + 16);
var domainOffset = BitConverterLE.ToUInt16 (message, startIndex + 20);
- domain = Encoding.UTF8.GetString (message, startIndex + domainOffset, domainLength);
+ Domain = Encoding.UTF8.GetString (message, startIndex + domainOffset, domainLength);
// decode the workstation/host
var workstationLength = BitConverterLE.ToUInt16 (message, startIndex + 24);
var workstationOffset = BitConverterLE.ToUInt16 (message, startIndex + 28);
- workstation = Encoding.UTF8.GetString (message, startIndex + workstationOffset, workstationLength);
+ Workstation = Encoding.UTF8.GetString (message, startIndex + workstationOffset, workstationLength);
if ((Flags & NtlmFlags.NegotiateVersion) != 0 && length >= 40) {
// decode the OS Version
@@ -117,17 +114,17 @@ void Decode (byte[] message, int startIndex, int length)
public override byte[] Encode ()
{
- bool negotiateVersion;
- int versionLength = 0;
-
- if (negotiateVersion = (Flags & NtlmFlags.NegotiateVersion) != 0)
- versionLength = 8;
+ if (cached != null)
+ return cached;
+ var negotiateVersion = (Flags & NtlmFlags.NegotiateVersion) != 0;
+ var workstation = Encoding.UTF8.GetBytes (Workstation);
+ var domain = Encoding.UTF8.GetBytes (Domain);
+ int versionLength = negotiateVersion ? 8 : 0;
int workstationOffset = 32 + versionLength;
int domainOffset = workstationOffset + workstation.Length;
var message = PrepareMessage (32 + domain.Length + workstation.Length + versionLength);
- byte[] buffer;
message[12] = (byte) Flags;
message[13] = (byte)((uint) Flags >> 8);
@@ -159,11 +156,10 @@ public override byte[] Encode ()
message[39] = 0x0f;
}
- buffer = Encoding.UTF8.GetBytes (workstation.ToUpperInvariant ());
- Buffer.BlockCopy (buffer, 0, message, workstationOffset, buffer.Length);
+ Buffer.BlockCopy (workstation, 0, message, workstationOffset, workstation.Length);
+ Buffer.BlockCopy (domain, 0, message, domainOffset, domain.Length);
- buffer = Encoding.UTF8.GetBytes (domain.ToUpperInvariant ());
- Buffer.BlockCopy (buffer, 0, message, domainOffset, buffer.Length);
+ cached = message;
return message;
}
diff --git a/MailKit/Security/Ntlm/Type2Message.cs b/MailKit/Security/Ntlm/Type2Message.cs
index eeaecc72f2..a270ce077d 100644
--- a/MailKit/Security/Ntlm/Type2Message.cs
+++ b/MailKit/Security/Ntlm/Type2Message.cs
@@ -1,85 +1,78 @@
//
-// Mono.Security.Protocol.Ntlm.Type2Message - Challenge
+// Type2Message.cs
//
-// Authors: Sebastien Pouliot
-// Jeffrey Stedfast
+// Author: Jeffrey Stedfast
//
-// Copyright (c) 2003 Motus Technologies Inc. (http://www.motus.com)
-// Copyright (c) 2004 Novell, Inc (http://www.novell.com)
// Copyright (c) 2013-2021 .NET Foundation and Contributors
//
-// References
-// a. NTLM Authentication Scheme for HTTP, Ronald Tschalär
-// http://www.innovation.ch/java/ntlm.html
-// b. The NTLM Authentication Protocol, Copyright © 2003 Eric Glass
-// http://davenport.sourceforge.net/ntlm.html
+// 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:
//
-// 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.
+// 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.
+//
+
+// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4
using System;
using System.Text;
-using System.Security.Cryptography;
namespace MailKit.Security.Ntlm {
- class Type2Message : MessageBase
+ class Type2Message : NtlmMessageBase
{
- byte[] targetInfo;
- byte[] nonce;
+ static readonly NtlmFlags DefaultFlags = NtlmFlags.NegotiateNtlm | NtlmFlags.NegotiateUnicode /*| NtlmFlags.NegotiateAlwaysSign*/;
+ byte[] serverChallenge;
+ byte[] cached;
- public Type2Message () : base (2)
+ public Type2Message (NtlmFlags flags, Version osVersion = null) : base (2)
{
- Flags = NtlmFlags.NegotiateNtlm | NtlmFlags.NegotiateUnicode /*| NtlmFlags.NegotiateAlwaysSign*/;
- nonce = new byte[8];
-
- using (var rng = RandomNumberGenerator.Create ())
- rng.GetBytes (nonce);
+ serverChallenge = NtlmUtils.NONCE (8);
+ OSVersion = osVersion;
+ Flags = flags;
}
- public Type2Message (Version osVersion) : this ()
+ public Type2Message (Version osVersion = null) : this (DefaultFlags, osVersion)
{
- OSVersion = osVersion;
}
public Type2Message (byte[] message, int startIndex, int length) : base (2)
{
- nonce = new byte[8];
+ serverChallenge = new byte[8];
Decode (message, startIndex, length);
+
+ cached = new byte[length];
+ Buffer.BlockCopy (message, startIndex, cached, 0, length);
}
~Type2Message ()
{
- if (nonce != null)
- Array.Clear (nonce, 0, nonce.Length);
+ if (serverChallenge != null)
+ Array.Clear (serverChallenge, 0, serverChallenge.Length);
}
- public byte[] Nonce {
- get { return (byte[]) nonce.Clone (); }
+ public byte[] ServerChallenge {
+ get { return serverChallenge; }
set {
if (value == null)
throw new ArgumentNullException (nameof (value));
if (value.Length != 8)
- throw new ArgumentException ("Invalid Nonce Length (should be 8 bytes).", nameof (value));
+ throw new ArgumentException ("Invalid nonce length (should be 8 bytes).", nameof (value));
- nonce = (byte[]) value.Clone ();
+ Array.Clear (serverChallenge, 0, serverChallenge.Length);
+ serverChallenge = value;
}
}
@@ -87,17 +80,13 @@ public Type2Message (byte[] message, int startIndex, int length) : base (2)
get; set;
}
- public TargetInfo TargetInfo {
+ public NtlmTargetInfo TargetInfo {
get; set;
}
- public byte[] EncodedTargetInfo {
- get {
- if (targetInfo != null)
- return (byte[]) targetInfo.Clone ();
-
- return new byte[0];
- }
+ public byte[] GetEncodedTargetInfo ()
+ {
+ return TargetInfo?.Encode ((Flags & NtlmFlags.NegotiateUnicode) != 0);
}
void Decode (byte[] message, int startIndex, int length)
@@ -106,7 +95,7 @@ void Decode (byte[] message, int startIndex, int length)
Flags = (NtlmFlags) BitConverterLE.ToUInt32 (message, startIndex + 20);
- Buffer.BlockCopy (message, startIndex + 24, nonce, 0, 8);
+ Buffer.BlockCopy (message, startIndex + 24, serverChallenge, 0, 8);
var targetNameLength = BitConverterLE.ToUInt16 (message, startIndex + 12);
var targetNameOffset = BitConverterLE.ToUInt16 (message, startIndex + 16);
@@ -131,29 +120,23 @@ void Decode (byte[] message, int startIndex, int length)
var targetInfoLength = BitConverterLE.ToUInt16 (message, startIndex + 40);
var targetInfoOffset = BitConverterLE.ToUInt16 (message, startIndex + 44);
- if (targetInfoLength > 0 && targetInfoOffset < length && targetInfoLength <= (length - targetInfoOffset)) {
- TargetInfo = new TargetInfo (message, startIndex + targetInfoOffset, targetInfoLength, (Flags & NtlmFlags.NegotiateOem) == 0);
-
- targetInfo = new byte[targetInfoLength];
- Buffer.BlockCopy (message, startIndex + targetInfoOffset, targetInfo, 0, targetInfoLength);
- }
+ if (targetInfoLength > 0 && targetInfoOffset < length && targetInfoLength <= (length - targetInfoOffset))
+ TargetInfo = new NtlmTargetInfo (message, startIndex + targetInfoOffset, targetInfoLength, (Flags & NtlmFlags.NegotiateUnicode) != 0);
}
}
public override byte[] Encode ()
{
+ if (cached != null)
+ return cached;
+
+ var targetInfo = GetEncodedTargetInfo ();
int targetNameOffset = 40;
int targetInfoOffset = 48;
byte[] targetName = null;
bool negotiateVersion;
int size = 40;
- if (negotiateVersion = (Flags & NtlmFlags.NegotiateVersion) != 0) {
- targetNameOffset += 16;
- targetInfoOffset += 16;
- size += 16;
- }
-
if (TargetName != null) {
var encoding = (Flags & NtlmFlags.NegotiateUnicode) != 0 ? Encoding.Unicode : Encoding.UTF8;
@@ -162,27 +145,21 @@ public override byte[] Encode ()
size += targetName.Length;
}
- if (TargetInfo != null || targetInfo != null) {
- if (targetInfo == null)
- targetInfo = TargetInfo.Encode ((Flags & NtlmFlags.NegotiateUnicode) != 0);
- size += targetInfo.Length + 8;
+ if (targetInfo != null) {
+ size += 8 + targetInfo.Length;
targetNameOffset += 8;
}
- var message = PrepareMessage (size);
-
- // message length
- message[16] = (byte) size;
- message[17] = (byte)(size >> 8);
-
- // flags
- message[20] = (byte) Flags;
- message[21] = (byte)((uint) Flags >> 8);
- message[22] = (byte)((uint) Flags >> 16);
- message[23] = (byte)((uint) Flags >> 24);
+ if (negotiateVersion = (Flags & NtlmFlags.NegotiateVersion) != 0) {
+ targetNameOffset += 8;
+ targetInfoOffset += 8;
+ size += 8;
+ }
- Buffer.BlockCopy (nonce, 0, message, 24, nonce.Length);
+ // 12 bytes
+ var message = PrepareMessage (size);
+ // TargetName (8 bytes)
if (targetName != null) {
message[12] = (byte) targetName.Length;
message[13] = (byte)(targetName.Length >> 8);
@@ -190,10 +167,25 @@ public override byte[] Encode ()
message[15] = (byte)(targetName.Length >> 8);
message[16] = (byte) targetNameOffset;
message[17] = (byte)(targetNameOffset >> 8);
+ //message[18] = (byte) (targetNameOffset >> 16);
+ //message[19] = (byte) (targetNameOffset >> 24);
+ // TargetName Payload
Buffer.BlockCopy (targetName, 0, message, targetNameOffset, targetName.Length);
}
+ // NegotiateFlags (4 bytes)
+ message[20] = (byte) Flags;
+ message[21] = (byte) ((uint) Flags >> 8);
+ message[22] = (byte) ((uint) Flags >> 16);
+ message[23] = (byte) ((uint) Flags >> 24);
+
+ // ServerChallenge (8 bytes)
+ Buffer.BlockCopy (serverChallenge, 0, message, 24, serverChallenge.Length);
+
+ // Reserved (8 bytes)
+
+ // TargetInfo (8 bytes)
if (targetInfo != null) {
message[40] = (byte) targetInfo.Length;
message[41] = (byte)(targetInfo.Length >> 8);
@@ -202,6 +194,7 @@ public override byte[] Encode ()
message[44] = (byte) targetInfoOffset;
message[45] = (byte)(targetInfoOffset >> 8);
+ // TargetInfo Payload
Buffer.BlockCopy (targetInfo, 0, message, targetInfoOffset, targetInfo.Length);
}
@@ -216,6 +209,8 @@ public override byte[] Encode ()
message[55] = 0x0f;
}
+ cached = message;
+
return message;
}
}
diff --git a/MailKit/Security/Ntlm/Type3Message.cs b/MailKit/Security/Ntlm/Type3Message.cs
index 1a8fff0879..909e4794b6 100644
--- a/MailKit/Security/Ntlm/Type3Message.cs
+++ b/MailKit/Security/Ntlm/Type3Message.cs
@@ -1,133 +1,190 @@
//
-// Mono.Security.Protocol.Ntlm.Type3Message - Authentication
+// Type3Message.cs
//
-// Authors: Sebastien Pouliot
-// Jeffrey Stedfast
+// Author: Jeffrey Stedfast
//
-// Copyright (c) 2003 Motus Technologies Inc. (http://www.motus.com)
-// Copyright (c) 2004 Novell, Inc (http://www.novell.com)
// Copyright (c) 2013-2021 .NET Foundation and Contributors
//
-// References
-// a. NTLM Authentication Scheme for HTTP, Ronald Tschalär
-// http://www.innovation.ch/java/ntlm.html
-// b. The NTLM Authentication Protocol, Copyright © 2003 Eric Glass
-// http://davenport.sourceforge.net/ntlm.html
+// 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:
//
-// 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.
+// 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.
+//
+
+// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4
using System;
using System.Text;
namespace MailKit.Security.Ntlm {
- class Type3Message : MessageBase
+ class Type3Message : NtlmMessageBase
{
+ static readonly byte[] Z16 = new byte[16];
+
+ readonly Type1Message type1;
readonly Type2Message type2;
- readonly byte[] challenge;
+ byte[] clientChallenge;
- public Type3Message (byte[] message, int startIndex, int length) : base (3)
+ public Type3Message (Type1Message type1, Type2Message type2, string userName, string password, string workstation) : base (3)
{
- Decode (message, startIndex, length);
- type2 = null;
- }
+ if (type1 == null)
+ throw new ArgumentNullException (nameof (type1));
- public Type3Message (Type2Message type2, Version osVersion, NtlmAuthLevel level, string userName, string password, string workstation) : base (3)
- {
+ if (type2 == null)
+ throw new ArgumentNullException (nameof (type2));
+
+ if (userName == null)
+ throw new ArgumentNullException (nameof (userName));
+
+ if (password == null)
+ throw new ArgumentNullException (nameof (password));
+
+ clientChallenge = NtlmUtils.NONCE (8);
+ this.type1 = type1;
this.type2 = type2;
- challenge = type2.Nonce;
- Domain = type2.TargetName;
- OSVersion = osVersion;
- Username = userName;
- Password = password;
- Level = level;
- Workstation = workstation;
- Flags = 0;
+ if ((type2.Flags & NtlmFlags.TargetTypeDomain) != 0) {
+ // The server is domain-joined, so the TargetName will be the domain.
+ Domain = type2.TargetName;
+ } else {
+ // The server is not domain-joined, so the TargetName will be the machine name of the server.
+ Domain = type2.TargetInfo?.DomainName;
- if (osVersion != null)
- Flags |= NtlmFlags.NegotiateVersion;
+ // TODO: throw if TargetInfo is null?
+ }
- if ((type2.Flags & NtlmFlags.NegotiateUnicode) != 0)
- Flags |= NtlmFlags.NegotiateUnicode;
- else
- Flags |= NtlmFlags.NegotiateOem;
+ Workstation = workstation;
+ UserName = userName;
+ Password = password;
- if ((type2.Flags & NtlmFlags.NegotiateNtlm) != 0)
- Flags |= NtlmFlags.NegotiateNtlm;
+ // Use only the features supported by both the client and server.
+ Flags = type1.Flags & type2.Flags;
- if ((type2.Flags & NtlmFlags.NegotiateNtlm2Key) != 0)
- Flags |= NtlmFlags.NegotiateNtlm2Key;
+ // If the client and server both support NEGOTIATE_UNICODE, disable NEGOTIATE_OEM.
+ if ((Flags & NtlmFlags.NegotiateUnicode) != 0)
+ Flags &= ~NtlmFlags.NegotiateOem;
+ // TODO: throw if Unicode && Oem are both unset?
- if ((type2.Flags & NtlmFlags.NegotiateTargetInfo) != 0)
- Flags |= NtlmFlags.NegotiateTargetInfo;
+ // If the client and server both support NEGOTIATE_EXTENDED_SESSIONSECURITY, disable NEGOTIATE_LM_KEY.
+ if ((Flags & NtlmFlags.NegotiateExtendedSessionSecurity) != 0)
+ Flags &= ~NtlmFlags.NegotiateLanManagerKey;
- if ((type2.Flags & NtlmFlags.RequestTarget) != 0)
+ // Disable NEGOTIATE_KEY_EXCHANGE if neither NEGOTIATE_SIGN nor NEGOTIATE_SEAL are also present.
+ if ((Flags & NtlmFlags.NegotiateKeyExchange) != 0 && (Flags & (NtlmFlags.NegotiateSign | NtlmFlags.NegotiateSeal)) == 0)
+ Flags &= ~NtlmFlags.NegotiateKeyExchange;
+
+ // If we had RequestTarget in our initial NEGOTIATE_MESSAGE, include it again in this message(?)
+ if ((type1.Flags & NtlmFlags.RequestTarget) != 0)
Flags |= NtlmFlags.RequestTarget;
+
+ // If NEGOTIATE_VERSION is set, grab the OSVersion from our original negotiate message.
+ if ((Flags & NtlmFlags.NegotiateVersion) != 0)
+ OSVersion = type1.OSVersion ?? OSVersion;
+ }
+
+ public Type3Message (byte[] message, int startIndex, int length) : base (3)
+ {
+ Decode (message, startIndex, length);
+ type2 = null;
}
~Type3Message ()
{
- if (challenge != null)
- Array.Clear (challenge, 0, challenge.Length);
+ if (clientChallenge != null)
+ Array.Clear (clientChallenge, 0, clientChallenge.Length);
+
+ if (LmChallengeResponse != null)
+ Array.Clear (LmChallengeResponse, 0, LmChallengeResponse.Length);
+
+ if (NtChallengeResponse != null)
+ Array.Clear (NtChallengeResponse, 0, NtChallengeResponse.Length);
+
+ if (ExportedSessionKey != null)
+ Array.Clear (ExportedSessionKey, 0, ExportedSessionKey.Length);
- if (LM != null)
- Array.Clear (LM, 0, LM.Length);
+ if (EncryptedRandomSessionKey != null)
+ Array.Clear (EncryptedRandomSessionKey, 0, EncryptedRandomSessionKey.Length);
+ }
+
+ ///
+ /// This is only used for unit testing purposes.
+ ///
+ internal byte[] ClientChallenge {
+ get { return clientChallenge; }
+ set {
+ if (value == null)
+ return;
+
+ if (value.Length != 8)
+ throw new ArgumentException ("Invalid nonce length (should be 8 bytes).", nameof (value));
- if (NT != null)
- Array.Clear (NT, 0, NT.Length);
+ Array.Clear (clientChallenge, 0, clientChallenge.Length);
+ clientChallenge = value;
+ }
}
- public NtlmAuthLevel Level {
+ ///
+ /// This is only used for unit testing purposes.
+ ///
+ internal long? Timestamp {
get; set;
}
public string Domain {
- get; set;
+ get; private set;
}
public string Workstation {
- get; set;
+ get; private set;
}
public string Password {
- get; set;
+ get; private set;
}
- public string Username {
- get; set;
+ public string UserName {
+ get; private set;
}
- public byte[] LM {
+ public byte[] Mic {
get; private set;
}
- public byte[] NT {
- get; set;
+ public byte[] LmChallengeResponse {
+ get; private set;
+ }
+
+ public byte[] NtChallengeResponse {
+ get; private set;
+ }
+
+ public byte[] ExportedSessionKey {
+ get; private set;
+ }
+
+ public byte[] EncryptedRandomSessionKey {
+ get; private set;
}
void Decode (byte[] message, int startIndex, int length)
{
- ValidateArguments (message, startIndex, length);
+ int payloadOffset = length;
+ int micOffset = 64;
- Password = null;
+ ValidateArguments (message, startIndex, length);
if (message.Length >= 64)
Flags = (NtlmFlags) BitConverterLE.ToUInt32 (message, startIndex + 60);
@@ -136,29 +193,36 @@ void Decode (byte[] message, int startIndex, int length)
int lmLength = BitConverterLE.ToUInt16 (message, startIndex + 12);
int lmOffset = BitConverterLE.ToUInt16 (message, startIndex + 16);
- LM = new byte[lmLength];
- Buffer.BlockCopy (message, startIndex + lmOffset, LM, 0, lmLength);
+ LmChallengeResponse = new byte[lmLength];
+ Buffer.BlockCopy (message, startIndex + lmOffset, LmChallengeResponse, 0, lmLength);
+ payloadOffset = Math.Min (payloadOffset, lmOffset);
int ntLength = BitConverterLE.ToUInt16 (message, startIndex + 20);
int ntOffset = BitConverterLE.ToUInt16 (message, startIndex + 24);
- NT = new byte[ntLength];
- Buffer.BlockCopy (message, startIndex + ntOffset, NT, 0, ntLength);
+ NtChallengeResponse = new byte[ntLength];
+ Buffer.BlockCopy (message, startIndex + ntOffset, NtChallengeResponse, 0, ntLength);
+ payloadOffset = Math.Min (payloadOffset, ntOffset);
int domainLength = BitConverterLE.ToUInt16 (message, startIndex + 28);
int domainOffset = BitConverterLE.ToUInt16 (message, startIndex + 32);
Domain = DecodeString (message, startIndex + domainOffset, domainLength);
+ payloadOffset = Math.Min (payloadOffset, domainOffset);
int userLength = BitConverterLE.ToUInt16 (message, startIndex + 36);
int userOffset = BitConverterLE.ToUInt16 (message, startIndex + 40);
- Username = DecodeString (message, startIndex + userOffset, userLength);
+ UserName = DecodeString (message, startIndex + userOffset, userLength);
+ payloadOffset = Math.Min (payloadOffset, userOffset);
- int hostLength = BitConverterLE.ToUInt16 (message, startIndex + 44);
- int hostOffset = BitConverterLE.ToUInt16 (message, startIndex + 48);
- Workstation = DecodeString (message, startIndex + hostOffset, hostLength);
+ int workstationLength = BitConverterLE.ToUInt16 (message, startIndex + 44);
+ int workstationOffset = BitConverterLE.ToUInt16 (message, startIndex + 48);
+ Workstation = DecodeString (message, startIndex + workstationOffset, workstationLength);
+ payloadOffset = Math.Min (payloadOffset, workstationOffset);
- // Session key. We don't use it yet.
- //int skeyLength = BitConverterLE.ToUInt16 (message, startIndex + 52);
- //int skeyOffset = BitConverterLE.ToUInt16 (message, startIndex + 56);
+ int skeyLength = BitConverterLE.ToUInt16 (message, startIndex + 52);
+ int skeyOffset = BitConverterLE.ToUInt16 (message, startIndex + 56);
+ EncryptedRandomSessionKey = new byte[skeyLength];
+ Buffer.BlockCopy (message, startIndex + skeyOffset, EncryptedRandomSessionKey, 0, skeyLength);
+ payloadOffset = Math.Min (payloadOffset, skeyOffset);
// OSVersion
if ((Flags & NtlmFlags.NegotiateVersion) != 0 && length >= 72) {
@@ -168,6 +232,13 @@ void Decode (byte[] message, int startIndex, int length)
int build = BitConverterLE.ToUInt16 (message, startIndex + 66);
OSVersion = new Version (major, minor, build);
+ micOffset += 8;
+ }
+
+ // MIC
+ if (micOffset + 16 <= payloadOffset) {
+ Mic = new byte[16];
+ Buffer.BlockCopy (message, startIndex + micOffset, Mic, 0, Mic.Length);
}
}
@@ -188,26 +259,94 @@ byte[] EncodeString (string text)
return encoding.GetBytes (text);
}
+ public void ComputeNtlmV2 (string targetName, bool unverifiedTargetName, byte[] channelBinding)
+ {
+ var targetInfo = new NtlmTargetInfo ();
+ int avFlags = 0;
+
+ // If the CHALLENGE_MESSAGE contains a TargetInfo field
+ if (type2.TargetInfo != null) {
+ type2.TargetInfo.CopyTo (targetInfo);
+
+ if (targetInfo.Flags.HasValue)
+ avFlags = targetInfo.Flags.Value;
+
+ // If the CHALLENGE_MESSAGE TargetInfo field (section 2.2.1.2) has an MsvAvTimestamp present, the client SHOULD provide a MIC.
+ if (type2.TargetInfo?.Timestamp != null) {
+ // If there is an AV_PAIR structure (section 2.2.2.1) with the AvId field set to MsvAvFlags, then in the Value field, set bit 0x2 to 1.
+ // Else add an AV_PAIR structure and set the AvId field to MsvAvFlags and the Value field bit 0x2 to 1.
+ targetInfo.Flags = avFlags |= 0x2;
+ }
+
+ // If ClientSuppliedTargetName (section 3.1.1.2) is not NULL
+ if (targetName != null) {
+ // If UnverifiedTargetName (section 3.1.1.2) is TRUE, then in AvId field = MsvAvFlags set 0x00000004 bit.
+ if (unverifiedTargetName)
+ targetInfo.Flags = avFlags |= 0x4;
+
+ // Add an AV_PAIR structure and set the AvId field to MsvAvTargetName and the Value field to ClientSuppliedTargetName without
+ // terminating NULL.
+ targetInfo.TargetName = targetName;
+ } else {
+ // Else add an AV_PAIR structure and set the AvId field to MsvAvTargetName and the Value field to an empty string without terminating NULL.
+ targetInfo.TargetName = string.Empty;
+ }
+
+ // The client SHOULD send the channel binding AV_PAIR:
+ // If the ClientChannelBindingsUnhashed (section 3.1.1.2) is not NULL
+ if (channelBinding != null) {
+ // Add an AV_PAIR structure and set the AvId field to MsvAvChannelBindings and the Value field to MD5_HASH(ClientChannelBindingsUnhashed).
+ targetInfo.ChannelBinding = NtlmUtils.MD5 (channelBinding);
+ } else {
+ // Else add an AV_PAIR structure and set the AvId field to MsvAvChannelBindings and the Value field to Z(16).
+ targetInfo.ChannelBinding = Z16;
+ }
+ }
+
+ var encodedTargetInfo = targetInfo.Encode ((Flags & NtlmFlags.NegotiateUnicode) != 0);
+
+ // Note: For NTLMv2, the sessionBaseKey is the same as the keyExchangeKey.
+ NtlmUtils.ComputeNtlmV2 (type2, Domain, UserName, Password, encodedTargetInfo, clientChallenge, Timestamp, out var ntChallengeResponse, out var lmChallengeResponse, out var keyExchangeKey);
+
+ NtChallengeResponse = ntChallengeResponse;
+ LmChallengeResponse = lmChallengeResponse;
+
+ if ((Flags & NtlmFlags.NegotiateKeyExchange) != 0 && (Flags & (NtlmFlags.NegotiateSign | NtlmFlags.NegotiateSeal)) != 0) {
+ ExportedSessionKey = NtlmUtils.NONCE (16);
+ EncryptedRandomSessionKey = NtlmUtils.RC4K (keyExchangeKey, ExportedSessionKey);
+ } else {
+ ExportedSessionKey = keyExchangeKey;
+ EncryptedRandomSessionKey = null;
+ }
+
+ // If the CHALLENGE_MESSAGE TargetInfo field (section 2.2.1.2) has an MsvAvTimestamp present, the client SHOULD provide a MIC.
+ if ((avFlags & 0x2) != 0)
+ Mic = NtlmUtils.HMACMD5 (ExportedSessionKey, NtlmUtils.ConcatenationOf (type1.Encode (), type2.Encode (), Encode ()));
+ }
+
public override byte[] Encode ()
{
var target = EncodeString (Domain);
- var user = EncodeString (Username);
+ var user = EncodeString (UserName);
var workstation = EncodeString (Workstation);
- var payloadOffset = 64;
+ int payloadOffset = 64, micOffset = -1;
bool negotiateVersion;
- byte[] lm, ntlm;
-
- ChallengeResponse2.Compute (type2, Level, Username, Password, Domain, out lm, out ntlm);
if (negotiateVersion = ((type2.Flags & NtlmFlags.NegotiateVersion) != 0 && OSVersion != null))
payloadOffset += 8;
- var lmResponseLength = lm != null ? lm.Length : 0;
- var ntResponseLength = ntlm != null ? ntlm.Length : 0;
+ if (Mic != null) {
+ micOffset = payloadOffset;
+ payloadOffset += Mic.Length;
+ }
- var message = PrepareMessage (payloadOffset + target.Length + user.Length + workstation.Length + lmResponseLength + ntResponseLength);
+ var lmResponseLength = LmChallengeResponse != null ? LmChallengeResponse.Length : 0;
+ var ntResponseLength = NtChallengeResponse != null ? NtChallengeResponse.Length : 0;
+ int skeyLength = EncryptedRandomSessionKey != null ? EncryptedRandomSessionKey.Length : 0;
- // LM response
+ var message = PrepareMessage (payloadOffset + target.Length + user.Length + workstation.Length + lmResponseLength + ntResponseLength + skeyLength);
+
+ // LmChallengeResponse
short lmResponseOffset = (short) (payloadOffset + target.Length + user.Length + workstation.Length);
message[12] = (byte) lmResponseLength;
message[13] = (byte) 0x00;
@@ -215,8 +354,10 @@ public override byte[] Encode ()
message[15] = message[13];
message[16] = (byte) lmResponseOffset;
message[17] = (byte) (lmResponseOffset >> 8);
+ //message[18] = (byte) (lmResponseOffset >> 16);
+ //message[19] = (byte) (lmResponseOffset >> 24);
- // NT response
+ // NtChallengeResponse
short ntResponseOffset = (short) (lmResponseOffset + lmResponseLength);
message[20] = (byte) ntResponseLength;
message[21] = (byte) (ntResponseLength >> 8);
@@ -224,8 +365,10 @@ public override byte[] Encode ()
message[23] = message[21];
message[24] = (byte) ntResponseOffset;
message[25] = (byte) (ntResponseOffset >> 8);
+ //message[26] = (byte) (ntResponseOffset >> 16);
+ //message[27] = (byte) (ntResponseOffset >> 24);
- // target
+ // Target
short domainLength = (short) target.Length;
short domainOffset = (short) payloadOffset;
message[28] = (byte) domainLength;
@@ -234,8 +377,10 @@ public override byte[] Encode ()
message[31] = message[29];
message[32] = (byte) domainOffset;
message[33] = (byte) (domainOffset >> 8);
+ //message[34] = (byte) (domainOffset >> 16);
+ //message[35] = (byte) (domainOffset >> 24);
- // username
+ // UserName
short userLength = (short) user.Length;
short userOffset = (short) (domainOffset + domainLength);
message[36] = (byte) userLength;
@@ -244,8 +389,10 @@ public override byte[] Encode ()
message[39] = message[37];
message[40] = (byte) userOffset;
message[41] = (byte) (userOffset >> 8);
+ //message[42] = (byte) (userOffset >> 16);
+ //message[43] = (byte) (userOffset >> 24);
- // host
+ // Workstation
short workstationLength = (short) workstation.Length;
short workstationOffset = (short) (userOffset + userLength);
message[44] = (byte) workstationLength;
@@ -254,11 +401,19 @@ public override byte[] Encode ()
message[47] = message[45];
message[48] = (byte) workstationOffset;
message[49] = (byte) (workstationOffset >> 8);
-
- // message length
- short messageLength = (short) message.Length;
- message[56] = (byte) messageLength;
- message[57] = (byte) (messageLength >> 8);
+ //message[50] = (byte) (workstationOffset >> 16);
+ //message[51] = (byte) (workstationOffset >> 24);
+
+ // EncryptedRandomSessionKey
+ short skeyOffset = (short) (ntResponseOffset + ntResponseLength);
+ message[52] = (byte) skeyLength;
+ message[53] = (byte) (skeyLength >> 8);
+ message[54] = message[52];
+ message[55] = message[53];
+ message[56] = (byte) skeyOffset;
+ message[57] = (byte) (skeyOffset >> 8);
+ //message[58] = (byte) (skeyOffset >> 16);
+ //message[59] = (byte) (skeyOffset >> 24);
// options flags
message[60] = (byte) Flags;
@@ -277,19 +432,21 @@ public override byte[] Encode ()
message[71] = 0x0f;
}
+ if (Mic != null)
+ Buffer.BlockCopy (Mic, 0, message, micOffset, Mic.Length);
+
Buffer.BlockCopy (target, 0, message, domainOffset, target.Length);
Buffer.BlockCopy (user, 0, message, userOffset, user.Length);
Buffer.BlockCopy (workstation, 0, message, workstationOffset, workstation.Length);
- if (lm != null) {
- Buffer.BlockCopy (lm, 0, message, lmResponseOffset, lm.Length);
- Array.Clear (lm, 0, lm.Length);
- }
+ if (LmChallengeResponse != null)
+ Buffer.BlockCopy (LmChallengeResponse, 0, message, lmResponseOffset, LmChallengeResponse.Length);
- if (ntlm != null) {
- Buffer.BlockCopy (ntlm, 0, message, ntResponseOffset, ntlm.Length);
- Array.Clear (ntlm, 0, ntlm.Length);
- }
+ if (NtChallengeResponse != null)
+ Buffer.BlockCopy (NtChallengeResponse, 0, message, ntResponseOffset, NtChallengeResponse.Length);
+
+ if ((Flags & NtlmFlags.NegotiateKeyExchange) != 0 && EncryptedRandomSessionKey != null)
+ Buffer.BlockCopy (EncryptedRandomSessionKey, 0, message, skeyOffset, EncryptedRandomSessionKey.Length);
return message;
}
diff --git a/MailKit/Security/SaslMechanism.cs b/MailKit/Security/SaslMechanism.cs
index 9a5507af73..ca503c4fa4 100644
--- a/MailKit/Security/SaslMechanism.cs
+++ b/MailKit/Security/SaslMechanism.cs
@@ -243,6 +243,8 @@ protected byte[] GetChannelBindingToken (ChannelBindingKind kind)
}
}
+ channelBinding.Close ();
+
return token;
}
diff --git a/MailKit/Security/SaslMechanismNtlm.cs b/MailKit/Security/SaslMechanismNtlm.cs
index 095cac55c7..ba1266c1a8 100644
--- a/MailKit/Security/SaslMechanismNtlm.cs
+++ b/MailKit/Security/SaslMechanismNtlm.cs
@@ -24,8 +24,11 @@
// THE SOFTWARE.
//
+// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4
+
using System;
using System.Net;
+using System.Security.Authentication.ExtendedProtection;
using MailKit.Security.Ntlm;
@@ -39,10 +42,11 @@ namespace MailKit.Security {
public class SaslMechanismNtlm : SaslMechanism
{
enum LoginState {
- Initial,
+ Negotiate,
Challenge
}
+ Type1Message type1;
LoginState state;
///
@@ -57,7 +61,9 @@ enum LoginState {
///
public SaslMechanismNtlm (NetworkCredential credentials) : base (credentials)
{
- Level = NtlmAuthLevel.NTLMv2_only;
+ if (Environment.OSVersion.Platform == PlatformID.Win32NT)
+ OSVersion = Environment.OSVersion.Version;
+ Workstation = Environment.MachineName;
}
///
@@ -75,7 +81,23 @@ public SaslMechanismNtlm (NetworkCredential credentials) : base (credentials)
///
public SaslMechanismNtlm (string userName, string password) : base (userName, password)
{
- Level = NtlmAuthLevel.NTLMv2_only;
+ if (Environment.OSVersion.Platform == PlatformID.Win32NT)
+ OSVersion = Environment.OSVersion.Version;
+ Workstation = Environment.MachineName;
+ }
+
+ ///
+ /// This is only used for unit testing purposes.
+ ///
+ internal byte[] Nonce {
+ get; set;
+ }
+
+ ///
+ /// This is only used for unit testing purposes.
+ ///
+ internal long? Timestamp {
+ get; set;
}
///
@@ -89,6 +111,17 @@ public SaslMechanismNtlm (string userName, string password) : base (userName, pa
get { return "NTLM"; }
}
+ ///
+ /// Get whether or not the SASL mechanism supports channel binding.
+ ///
+ ///
+ /// Gets whether or not the SASL mechanism supports channel binding.
+ ///
+ /// true if the SASL mechanism supports channel binding; otherwise, false.
+ public override bool SupportsChannelBinding {
+ get { return true; }
+ }
+
///
/// Get whether or not the mechanism supports an initial response (SASL-IR).
///
@@ -102,16 +135,26 @@ public SaslMechanismNtlm (string userName, string password) : base (userName, pa
get { return true; }
}
- internal NtlmAuthLevel Level {
+ ///
+ /// Get or set a value indicating whether or not the NTLM SASL mechanism should allow channel-binding.
+ ///
+ ///
+ /// Gets or sets a value indicating whether or not the NTLM SASL mechanism should allow channel-binding.
+ /// In the future, this option will disappear as channel-binding will become the default. For now,
+ /// it is only an option because this feature has not been thoroughly tested.
+ ///
+ /// true if the NTLM SASL mechanism should allow channel-binding; otherwise, false.
+ public bool AllowChannelBinding {
get; set;
}
///
- /// Get or set the Windows OS version to use in the NTLM negotiation (used for debuigging purposes).
+ /// Get or set the Windows OS version to use in the NTLM negotiation (used for debugging purposes).
///
///
- /// Gets or sets the Windows OS version to use in the NTLM negotiation (used for debuigging purposes).
+ /// Gets or sets the Windows OS version to use in the NTLM negotiation (used for debugging purposes).
///
+ /// The Windows OS version.
public Version OSVersion {
get; set;
}
@@ -127,6 +170,29 @@ public SaslMechanismNtlm (string userName, string password) : base (userName, pa
get; set;
}
+ ///
+ /// Get or set the service principal name (SPN) of the service that the client wishes to authenticate with.
+ ///
+ ///
+ /// Get or set the service principal name (SPN) of the service that the client wishes to authenticate with.
+ /// This value is optional.
+ ///
+ /// The service principal name (SPN) of the service that the client wishes to authenticate with.
+ public string ServicePrincipalName {
+ get; set;
+ }
+
+ ///
+ /// Get or set a value indicating that the caller generated the target's SPN from an untrusted source.
+ ///
+ ///
+ /// Gets or sets a value indicating that the caller generated the target's SPN from an untrusted source.
+ ///
+ /// true if the is unverified; otherwise, false.
+ public bool IsUnverifiedServicePrincipalName {
+ get; set;
+ }
+
///
/// Parse the server's challenge token and return the next challenge response.
///
@@ -147,22 +213,28 @@ protected override byte[] Challenge (byte[] token, int startIndex, int length)
string userName = Credentials.UserName;
string domain = Credentials.Domain;
- MessageBase message = null;
+ NtlmMessageBase message = null;
if (string.IsNullOrEmpty (domain)) {
- int index = userName.IndexOf ('\\');
- if (index == -1)
- index = userName.IndexOf ('/');
+ int index;
+
+ if ((index = userName.LastIndexOf ('@')) != -1) {
+ userName = userName.Substring (0, index);
+ domain = userName.Substring (index + 1);
+ } else {
+ if ((index = userName.IndexOf ('\\')) == -1)
+ index = userName.IndexOf ('/');
- if (index >= 0) {
- domain = userName.Substring (0, index);
- userName = userName.Substring (index + 1);
+ if (index >= 0) {
+ domain = userName.Substring (0, index);
+ userName = userName.Substring (index + 1);
+ }
}
}
switch (state) {
- case LoginState.Initial:
- message = new Type1Message (Workstation, domain, OSVersion);
+ case LoginState.Negotiate:
+ message = type1 = new Type1Message (domain, Workstation, OSVersion);
state = LoginState.Challenge;
break;
case LoginState.Challenge:
@@ -175,11 +247,24 @@ protected override byte[] Challenge (byte[] token, int startIndex, int length)
return message?.Encode ();
}
- MessageBase GetChallengeResponse (string userName, string password, byte[] token, int startIndex, int length)
+ NtlmMessageBase GetChallengeResponse (string userName, string password, byte[] token, int startIndex, int length)
{
var type2 = new Type2Message (token, startIndex, length);
+ var type3 = new Type3Message (type1, type2, userName, password, Workstation) {
+ ClientChallenge = Nonce,
+ Timestamp = Timestamp
+ };
+ byte[] channelBinding = null;
+
+ if (AllowChannelBinding && type2.TargetInfo != null) {
+ // Only bother with attempting to channel-bind if the CHALLENGE_MESSAGE's TargetInfo is not NULL.
+ channelBinding = GetChannelBindingToken (ChannelBindingKind.Endpoint);
+ }
+
+ type3.ComputeNtlmV2 (ServicePrincipalName, IsUnverifiedServicePrincipalName, channelBinding);
+ type1 = null;
- return new Type3Message (type2, OSVersion, Level, userName, password, Workstation);
+ return type3;
}
///
@@ -190,7 +275,8 @@ MessageBase GetChallengeResponse (string userName, string password, byte[] token
///
public override void Reset ()
{
- state = LoginState.Initial;
+ state = LoginState.Negotiate;
+ type1 = null;
base.Reset ();
}
}
diff --git a/UnitTests/Security/Ntlm/NtlmSingleHostDataTests.cs b/UnitTests/Security/Ntlm/NtlmSingleHostDataTests.cs
new file mode 100644
index 0000000000..8615dc7d88
--- /dev/null
+++ b/UnitTests/Security/Ntlm/NtlmSingleHostDataTests.cs
@@ -0,0 +1,54 @@
+//
+// NtlmSingleHostDataTests.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2021 .NET Foundation and Contributors
+//
+// 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 NUnit.Framework;
+
+using MailKit.Security.Ntlm;
+
+namespace UnitTests.Security.Ntlm {
+ [TestFixture]
+ public class NtlmSingleHostDataTests
+ {
+ [Test]
+ public void TestArgumentExceptions ()
+ {
+ var customData = new byte[8];
+ var machineId = new byte[32];
+ var buffer = new byte[48];
+
+ Assert.Throws (() => new NtlmSingleHostData (null, machineId));
+ Assert.Throws (() => new NtlmSingleHostData (machineId, machineId));
+ Assert.Throws (() => new NtlmSingleHostData (customData, null));
+ Assert.Throws (() => new NtlmSingleHostData (customData, customData));
+
+ Assert.Throws (() => new NtlmSingleHostData (null, 0, 48));
+ Assert.Throws (() => new NtlmSingleHostData (buffer, -1, 48));
+ Assert.Throws (() => new NtlmSingleHostData (buffer, 0, 25));
+ }
+ }
+}
diff --git a/UnitTests/Security/Ntlm/NtlmTargetInfoTests.cs b/UnitTests/Security/Ntlm/NtlmTargetInfoTests.cs
new file mode 100644
index 0000000000..9a56373f68
--- /dev/null
+++ b/UnitTests/Security/Ntlm/NtlmTargetInfoTests.cs
@@ -0,0 +1,297 @@
+//
+// NtlmTargetInfoTests.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2021 .NET Foundation and Contributors
+//
+// 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 NUnit.Framework;
+
+using MailKit.Security.Ntlm;
+
+namespace UnitTests.Security.Ntlm {
+ [TestFixture]
+ public class NtlmTargetInfoTests
+ {
+ [Test]
+ public void TestArgumentExceptions ()
+ {
+ var buffer = new byte[24];
+
+ Assert.Throws (() => new NtlmTargetInfo (null, 0, 24, true));
+ Assert.Throws (() => new NtlmTargetInfo (buffer, -1, 24, true));
+ Assert.Throws (() => new NtlmTargetInfo (buffer, 0, 25, true));
+ }
+
+#if false
+ static string ToCSharpByteArrayInitializer (string name, byte[] buffer)
+ {
+ var builder = new System.Text.StringBuilder ();
+ int index = 0;
+
+ builder.AppendLine ($"static readonly byte[] {name} = {{");
+ while (index < buffer.Length) {
+ builder.Append ('\t');
+ for (int i = 0; i < 16 && index < buffer.Length; i++, index++)
+ builder.AppendFormat ("0x{0}, ", buffer[index].ToString ("x2"));
+ builder.Length--;
+ if (index == buffer.Length)
+ builder.Length--;
+ builder.AppendLine ();
+ }
+ builder.AppendLine ($"}};");
+
+ return builder.ToString ();
+ }
+#endif
+
+ static void AssertDecode (byte[] buffer, bool unicode)
+ {
+ var channelBinding = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef };
+ var timestamp = DateTime.FromFileTimeUtc (132737136905945346);
+ var targetInfo = new NtlmTargetInfo (buffer, 0, buffer.Length, unicode);
+
+ //var buffer1 = targetInfo.Encode (true);
+ //var csharp = ToCSharpByteArrayInitializer ("NtlmTargetInfoUnorderedUnicode", buffer1);
+
+ Assert.AreEqual ("ServerName", targetInfo.ServerName);
+ Assert.AreEqual ("DomainName", targetInfo.DomainName);
+ Assert.AreEqual ("DnsServerName", targetInfo.DnsServerName);
+ Assert.AreEqual ("DnsDomainName", targetInfo.DnsDomainName);
+ Assert.AreEqual ("DnsTreeName", targetInfo.DnsTreeName);
+ Assert.AreEqual (2, targetInfo.Flags, "Flags");
+ Assert.AreEqual (timestamp.ToFileTimeUtc (), targetInfo.Timestamp, "Timestamp");
+ //Assert.AreEqual ("SingleHost", targetInfo.SingleHost);
+ Assert.AreEqual (16, targetInfo.ChannelBinding.Length, "ChannelBinding");
+
+ for (int i = 0; i < channelBinding.Length; i++)
+ Assert.AreEqual (channelBinding[i], targetInfo.ChannelBinding[i], $"ChannelBinding[{i}]");
+
+ // Verify that re-encoding the target info results in an exact replica of the input.
+ var encoded = targetInfo.Encode (unicode);
+
+ Assert.AreEqual (buffer.Length, encoded.Length, "Re-encoded lengths do not match");
+
+ for (int i = 0; i < buffer.Length; i++)
+ Assert.AreEqual (buffer[i], encoded[i], $"encoded[{i}]");
+ }
+
+ static readonly byte[] NtlmTargetInfoOrderedOem = {
+ 0x01, 0x00, 0x0a, 0x00, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x02, 0x00,
+ 0x0a, 0x00, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x03, 0x00, 0x0d, 0x00,
+ 0x44, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x04, 0x00, 0x0d,
+ 0x00, 0x44, 0x6e, 0x73, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x05, 0x00,
+ 0x0b, 0x00, 0x44, 0x6e, 0x73, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x06, 0x00, 0x04,
+ 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x08, 0x00, 0x02, 0x01, 0xc8, 0x05, 0xb9, 0x93, 0xd7,
+ 0x01, 0x08, 0x00, 0x0a, 0x00, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x09,
+ 0x00, 0x0a, 0x00, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x0a, 0x00, 0x10,
+ 0x00, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd,
+ 0xef, 0x00, 0x00, 0x00, 0x00
+ };
+
+ [Test]
+ public void TestDecodeOrderedOem ()
+ {
+ AssertDecode (NtlmTargetInfoOrderedOem, false);
+ }
+
+ static readonly byte[] NtlmTargetInfoOrderedUnicode = {
+ 0x01, 0x00, 0x14, 0x00, 0x53, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00,
+ 0x4e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x02, 0x00, 0x14, 0x00, 0x44, 0x00, 0x6f, 0x00,
+ 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x4e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00,
+ 0x03, 0x00, 0x1a, 0x00, 0x44, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x53, 0x00, 0x65, 0x00, 0x72, 0x00,
+ 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x4e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x04, 0x00,
+ 0x1a, 0x00, 0x44, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x44, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x4e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x05, 0x00, 0x16, 0x00,
+ 0x44, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x54, 0x00, 0x72, 0x00, 0x65, 0x00, 0x65, 0x00, 0x4e, 0x00,
+ 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x06, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00,
+ 0x08, 0x00, 0x02, 0x01, 0xc8, 0x05, 0xb9, 0x93, 0xd7, 0x01, 0x08, 0x00, 0x14, 0x00, 0x53, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x48, 0x00, 0x6f, 0x00, 0x73, 0x00,
+ 0x74, 0x00, 0x09, 0x00, 0x14, 0x00, 0x54, 0x00, 0x61, 0x00, 0x72, 0x00, 0x67, 0x00, 0x65, 0x00,
+ 0x74, 0x00, 0x4e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x01, 0x23,
+ 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x00, 0x00,
+ 0x00, 0x00
+ };
+
+ [Test]
+ public void TestDecodeOrderedUnicode ()
+ {
+ AssertDecode (NtlmTargetInfoOrderedUnicode, true);
+ }
+
+ static readonly byte[] NtlmTargetInfoUnorderedOem = {
+ 0x07, 0x00, 0x08, 0x00, 0x02, 0x01, 0xc8, 0x05, 0xb9, 0x93, 0xd7, 0x01, 0x09, 0x00, 0x0a, 0x00,
+ 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x08, 0x00, 0x0a, 0x00, 0x53, 0x69,
+ 0x6e, 0x67, 0x6c, 0x65, 0x48, 0x6f, 0x73, 0x74, 0x01, 0x00, 0x0a, 0x00, 0x53, 0x65, 0x72, 0x76,
+ 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x06, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00,
+ 0x0a, 0x00, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x05, 0x00, 0x0b, 0x00,
+ 0x44, 0x6e, 0x73, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x03, 0x00, 0x0d, 0x00, 0x44,
+ 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x04, 0x00, 0x0d, 0x00,
+ 0x44, 0x6e, 0x73, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x0a, 0x00, 0x10,
+ 0x00, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd,
+ 0xef, 0x00, 0x00, 0x00, 0x00
+ };
+
+ [Test]
+ public void TestDecodeUnorederedOem ()
+ {
+ AssertDecode (NtlmTargetInfoUnorderedOem, false);
+ }
+
+ static readonly byte[] NtlmTargetInfoUnorderedUnicode = {
+ 0x07, 0x00, 0x08, 0x00, 0x02, 0x01, 0xc8, 0x05, 0xb9, 0x93, 0xd7, 0x01, 0x09, 0x00, 0x14, 0x00,
+ 0x54, 0x00, 0x61, 0x00, 0x72, 0x00, 0x67, 0x00, 0x65, 0x00, 0x74, 0x00, 0x4e, 0x00, 0x61, 0x00,
+ 0x6d, 0x00, 0x65, 0x00, 0x08, 0x00, 0x14, 0x00, 0x53, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00,
+ 0x6c, 0x00, 0x65, 0x00, 0x48, 0x00, 0x6f, 0x00, 0x73, 0x00, 0x74, 0x00, 0x01, 0x00, 0x14, 0x00,
+ 0x53, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x4e, 0x00, 0x61, 0x00,
+ 0x6d, 0x00, 0x65, 0x00, 0x06, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x14, 0x00,
+ 0x44, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x4e, 0x00, 0x61, 0x00,
+ 0x6d, 0x00, 0x65, 0x00, 0x05, 0x00, 0x16, 0x00, 0x44, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x54, 0x00,
+ 0x72, 0x00, 0x65, 0x00, 0x65, 0x00, 0x4e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x03, 0x00,
+ 0x1a, 0x00, 0x44, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x53, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00,
+ 0x65, 0x00, 0x72, 0x00, 0x4e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x04, 0x00, 0x1a, 0x00,
+ 0x44, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x44, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00,
+ 0x6e, 0x00, 0x4e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x01, 0x23,
+ 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x00, 0x00,
+ 0x00, 0x00
+ };
+
+ [Test]
+ public void TestDecodeUnorderedUnocode ()
+ {
+ AssertDecode (NtlmTargetInfoUnorderedUnicode, true);
+ }
+
+ static NtlmSingleHostData GenerateSingleHostData ()
+ {
+ var customData = new byte[8];
+ var machineId = new byte[32];
+ var rng = new Random ();
+
+ rng.NextBytes (customData);
+ rng.NextBytes (machineId);
+
+ return new NtlmSingleHostData (customData, machineId);
+ }
+
+ static void AssertSingleHost (NtlmSingleHostData expected, byte[] actual, string prefix)
+ {
+ var singleHost = new NtlmSingleHostData (actual, 0, actual.Length);
+
+ Assert.AreEqual (expected.Size, singleHost.Size, $"{prefix}.Size");
+ for (int i = 0; i < 8; i++)
+ Assert.AreEqual (expected.CustomData[i], singleHost.CustomData[i], $"{prefix}.CustomData[{i}]");
+ for (int i = 0; i < 32; i++)
+ Assert.AreEqual (expected.MachineId[i], singleHost.MachineId[i], $"{prefix}.MachineId[{i}]");
+ }
+
+ [Test]
+ public void TestRemovingAttributes ()
+ {
+ var channelBinding = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef };
+ var timestamp = DateTime.FromFileTimeUtc (132737136905945346);
+ var singleHost = GenerateSingleHostData ();
+ var targetInfo = new NtlmTargetInfo {
+ ServerName = "ServerName",
+ DomainName = "DomainName",
+ SingleHost = singleHost.Encode (),
+ Flags = 2,
+ Timestamp = timestamp.ToFileTimeUtc (),
+ ChannelBinding = channelBinding
+ };
+
+ Assert.AreEqual ("ServerName", targetInfo.ServerName);
+ Assert.AreEqual ("DomainName", targetInfo.DomainName);
+ AssertSingleHost (singleHost, targetInfo.SingleHost, "SingleHost");
+ Assert.AreEqual (2, targetInfo.Flags, "Flags");
+ Assert.AreEqual (timestamp.ToFileTimeUtc (), targetInfo.Timestamp, "Timestamp");
+ Assert.AreEqual (16, targetInfo.ChannelBinding.Length, "ChannelBinding");
+
+ for (int i = 0; i < channelBinding.Length; i++)
+ Assert.AreEqual (channelBinding[i], targetInfo.ChannelBinding[i], $"ChannelBinding[{i}]");
+
+ targetInfo.SingleHost = null;
+ Assert.IsNull (targetInfo.SingleHost, "SingleHost remove attempt #1");
+ targetInfo.SingleHost = null;
+ Assert.IsNull (targetInfo.SingleHost, "SingleHost remove attempt #2");
+
+ targetInfo.Flags = null;
+ Assert.IsNull (targetInfo.Flags, "Flags remove attempt #1");
+ targetInfo.Flags = null;
+ Assert.IsNull (targetInfo.Flags, "Flags remove attempt #2");
+
+ targetInfo.Timestamp = null;
+ Assert.IsNull (targetInfo.Timestamp, "Timestamp remove attempt #1");
+ targetInfo.Timestamp = null;
+ Assert.IsNull (targetInfo.Timestamp, "Timestamp remove attempt #2");
+
+ targetInfo.ChannelBinding = null;
+ Assert.IsNull (targetInfo.ChannelBinding, "ChannelBinding remove attempt #1");
+ targetInfo.ChannelBinding = null;
+ Assert.IsNull (targetInfo.ChannelBinding, "ChannelBinding remove attempt #2");
+ }
+
+ [Test]
+ public void TestUpdatingAttributes ()
+ {
+ var channelBinding = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef };
+ var timestamp = DateTime.FromFileTimeUtc (132737136905945346);
+ var updatedSingleHost = GenerateSingleHostData ();
+ var singleHost = GenerateSingleHostData ();
+ var targetInfo = new NtlmTargetInfo {
+ ServerName = "ServerName",
+ DomainName = "DomainName",
+ SingleHost = singleHost.Encode (),
+ Flags = 2,
+ Timestamp = timestamp.ToFileTimeUtc (),
+ ChannelBinding = channelBinding
+ };
+
+ Assert.AreEqual ("ServerName", targetInfo.ServerName);
+ Assert.AreEqual ("DomainName", targetInfo.DomainName);
+ AssertSingleHost (singleHost, targetInfo.SingleHost, "SingleHost");
+ Assert.AreEqual (2, targetInfo.Flags, "Flags");
+ Assert.AreEqual (timestamp.ToFileTimeUtc (), targetInfo.Timestamp, "Timestamp");
+ Assert.AreEqual (16, targetInfo.ChannelBinding.Length, "ChannelBinding");
+
+ for (int i = 0; i < channelBinding.Length; i++)
+ Assert.AreEqual (channelBinding[i], targetInfo.ChannelBinding[i], $"ChannelBinding[{i}]");
+
+ targetInfo.SingleHost = updatedSingleHost.Encode ();
+ AssertSingleHost (updatedSingleHost, targetInfo.SingleHost, "Updated SingleHost");
+
+ targetInfo.Flags = 1;
+ Assert.AreEqual (1, targetInfo.Flags, "Updated Flags");
+
+ targetInfo.Timestamp = 123456789;
+ Assert.AreEqual (123456789, targetInfo.Timestamp, "Updated Timestamp");
+
+ targetInfo.ChannelBinding = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ Assert.AreEqual (20, targetInfo.ChannelBinding.Length, "Updated ChannelBinding");
+
+ for (int i = 0; i < channelBinding.Length; i++)
+ Assert.AreEqual (i, targetInfo.ChannelBinding[i], $"Updated ChannelBinding[{i}]");
+ }
+ }
+}
diff --git a/UnitTests/Security/Ntlm/RC4Tests.cs b/UnitTests/Security/Ntlm/RC4Tests.cs
new file mode 100644
index 0000000000..bcf15fef30
--- /dev/null
+++ b/UnitTests/Security/Ntlm/RC4Tests.cs
@@ -0,0 +1,170 @@
+//
+// RC4Tests.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2021 .NET Foundation and Contributors
+//
+// 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 NUnit.Framework;
+
+using MailKit.Security.Ntlm;
+
+namespace UnitTests.Security.Ntlm {
+ [TestFixture]
+ public class RC4Tests
+ {
+ [Test]
+ public void TestArgumentExceptions ()
+ {
+ using (var rc4 = new RC4 ()) {
+ var buffer = new byte[16];
+
+ Assert.AreEqual (1, rc4.InputBlockSize, "InputBlockSize");
+ Assert.AreEqual (1, rc4.OutputBlockSize, "OutputBlockSize");
+ Assert.IsFalse (rc4.CanReuseTransform, "CanReuseTransform");
+ Assert.IsTrue (rc4.CanTransformMultipleBlocks, "CanTransformMultipleBlocks");
+
+ Assert.Throws (() => { var x = rc4.Key; });
+ Assert.Throws (() => { rc4.Key = null; });
+ Assert.Throws (() => { rc4.Key = new byte[0]; });
+
+ rc4.GenerateIV ();
+ rc4.GenerateKey ();
+ rc4.CreateDecryptor ();
+ rc4.CreateEncryptor ();
+
+ // TransformBlock input buffer parameters
+ Assert.Throws (() => rc4.TransformBlock (null, 0, buffer.Length, buffer, 0));
+ Assert.Throws (() => rc4.TransformBlock (buffer, -1, buffer.Length, buffer, 0));
+ Assert.Throws (() => rc4.TransformBlock (buffer, 0, -1, buffer, 0));
+
+ // TransformBlock output buffer parameters
+ Assert.Throws (() => rc4.TransformBlock (buffer, 0, buffer.Length, null, 0));
+ Assert.Throws (() => rc4.TransformBlock (buffer, 0, buffer.Length, buffer, -1));
+
+ // TransformFinalBlock
+ Assert.Throws (() => rc4.TransformFinalBlock (null, 0, buffer.Length));
+ Assert.Throws (() => rc4.TransformFinalBlock (buffer, -1, buffer.Length));
+ Assert.Throws (() => rc4.TransformFinalBlock (buffer, 0, -1));
+ }
+ }
+
+ static void AssertEncrypt (byte[] key, byte[] input, byte[] expected)
+ {
+ using (var rc4 = new RC4 ()) {
+ var output = new byte[input.Length];
+
+ rc4.Key = key;
+
+ rc4.TransformBlock (input, 0, input.Length, output, 0);
+
+ for (int i = 0; i < output.Length; i++)
+ Assert.AreEqual (expected[i], output[i], $"output[{i}]");
+ }
+
+ using (var rc4 = new RC4 ()) {
+ rc4.Key = key;
+
+ var output = rc4.TransformFinalBlock (input, 0, input.Length);
+
+ for (int i = 0; i < output.Length; i++)
+ Assert.AreEqual (expected[i], output[i], $"output[{i}]");
+ }
+ }
+
+ [Test]
+ public void TestEncryptExample1 ()
+ {
+ var expected = new byte[] { 0x74, 0x94, 0xC2, 0xE7, 0x10, 0x4B, 0x08, 0x79 };
+ var key = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF };
+ var text = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+ AssertEncrypt (key, text, expected);
+ }
+
+ [Test]
+ public void TestEncryptExample2 ()
+ {
+ byte[] expected = new byte[] { 0xF1, 0x38, 0x29, 0xC9, 0xDE };
+ byte[] key = new byte[] { 0x61, 0x8A, 0x63, 0xD2, 0xFB };
+ byte[] text = new byte[] { 0xDC, 0xEE, 0x4C, 0xF9, 0x2C };
+
+ AssertEncrypt (key, text, expected);
+ }
+
+ [Test]
+ public void TestEncryptExample3 ()
+ {
+ byte[] expected = new byte[] {
+ 0x35, 0x81, 0x86, 0x99, 0x90, 0x01, 0xe6, 0xb5, 0xda, 0xf0, 0x5e, 0xce, 0xeb, 0x7e, 0xee, 0x21,
+ 0xe0, 0x68, 0x9c, 0x1f, 0x00, 0xee, 0xa8, 0x1f, 0x7d, 0xd2, 0xca, 0xae, 0xe1, 0xd2, 0x76, 0x3e,
+ 0x68, 0xaf, 0x0e, 0xad, 0x33, 0xd6, 0x6c, 0x26, 0x8b, 0xc9, 0x46, 0xc4, 0x84, 0xfb, 0xe9, 0x4c,
+ 0x5f, 0x5e, 0x0b, 0x86, 0xa5, 0x92, 0x79, 0xe4, 0xf8, 0x24, 0xe7, 0xa6, 0x40, 0xbd, 0x22, 0x32,
+ 0x10, 0xb0, 0xa6, 0x11, 0x60, 0xb7, 0xbc, 0xe9, 0x86, 0xea, 0x65, 0x68, 0x80, 0x03, 0x59, 0x6b,
+ 0x63, 0x0a, 0x6b, 0x90, 0xf8, 0xe0, 0xca, 0xf6, 0x91, 0x2a, 0x98, 0xeb, 0x87, 0x21, 0x76, 0xe8,
+ 0x3c, 0x20, 0x2c, 0xaa, 0x64, 0x16, 0x6d, 0x2c, 0xce, 0x57, 0xff, 0x1b, 0xca, 0x57, 0xb2, 0x13,
+ 0xf0, 0xed, 0x1a, 0xa7, 0x2f, 0xb8, 0xea, 0x52, 0xb0, 0xbe, 0x01, 0xcd, 0x1e, 0x41, 0x28, 0x67,
+ 0x72, 0x0b, 0x32, 0x6e, 0xb3, 0x89, 0xd0, 0x11, 0xbd, 0x70, 0xd8, 0xaf, 0x03, 0x5f, 0xb0, 0xd8,
+ 0x58, 0x9d, 0xbc, 0xe3, 0xc6, 0x66, 0xf5, 0xea, 0x8d, 0x4c, 0x79, 0x54, 0xc5, 0x0c, 0x3f, 0x34,
+ 0x0b, 0x04, 0x67, 0xf8, 0x1b, 0x42, 0x59, 0x61, 0xc1, 0x18, 0x43, 0x07, 0x4d, 0xf6, 0x20, 0xf2,
+ 0x08, 0x40, 0x4b, 0x39, 0x4c, 0xf9, 0xd3, 0x7f, 0xf5, 0x4b, 0x5f, 0x1a, 0xd8, 0xf6, 0xea, 0x7d,
+ 0xa3, 0xc5, 0x61, 0xdf, 0xa7, 0x28, 0x1f, 0x96, 0x44, 0x63, 0xd2, 0xcc, 0x35, 0xa4, 0xd1, 0xb0,
+ 0x34, 0x90, 0xde, 0xc5, 0x1b, 0x07, 0x11, 0xfb, 0xd6, 0xf5, 0x5f, 0x79, 0x23, 0x4d, 0x5b, 0x7c,
+ 0x76, 0x66, 0x22, 0xa6, 0x6d, 0xe9, 0x2b, 0xe9, 0x96, 0x46, 0x1d, 0x5e, 0x4d, 0xc8, 0x78, 0xef,
+ 0x9b, 0xca, 0x03, 0x05, 0x21, 0xe8, 0x35, 0x1e, 0x4b, 0xae, 0xd2, 0xfd, 0x04, 0xf9, 0x46, 0x73,
+ 0x68, 0xc4, 0xad, 0x6a, 0xc1, 0x86, 0xd0, 0x82, 0x45, 0xb2, 0x63, 0xa2, 0x66, 0x6d, 0x1f, 0x6c,
+ 0x54, 0x20, 0xf1, 0x59, 0x9d, 0xfd, 0x9f, 0x43, 0x89, 0x21, 0xc2, 0xf5, 0xa4, 0x63, 0x93, 0x8c,
+ 0xe0, 0x98, 0x22, 0x65, 0xee, 0xf7, 0x01, 0x79, 0xbc, 0x55, 0x3f, 0x33, 0x9e, 0xb1, 0xa4, 0xc1,
+ 0xaf, 0x5f, 0x6a, 0x54, 0x7f
+ };
+ byte[] key = new byte[] {
+ 0x29, 0x04, 0x19, 0x72, 0xFB, 0x42, 0xBA, 0x5F, 0xC7, 0x12, 0x77, 0x12, 0xF1, 0x38, 0x29, 0xC9
+ };
+ byte[] text = new byte[] {
+ 0x52, 0x75, 0x69, 0x73, 0x6c, 0x69, 0x6e, 0x6e, 0x75, 0x6e, 0x20, 0x6c, 0x61, 0x75, 0x6c, 0x75,
+ 0x20, 0x6b, 0x6f, 0x72, 0x76, 0x69, 0x73, 0x73, 0x73, 0x61, 0x6e, 0x69, 0x2c, 0x20, 0x74, 0xe4,
+ 0x68, 0x6b, 0xe4, 0x70, 0xe4, 0x69, 0x64, 0x65, 0x6e, 0x20, 0x70, 0xe4, 0xe4, 0x6c, 0x6c, 0xe4,
+ 0x20, 0x74, 0xe4, 0x79, 0x73, 0x69, 0x6b, 0x75, 0x75, 0x2e, 0x20, 0x4b, 0x65, 0x73, 0xe4, 0x79,
+ 0xf6, 0x6e, 0x20, 0x6f, 0x6e, 0x20, 0x6f, 0x6e, 0x6e, 0x69, 0x20, 0x6f, 0x6d, 0x61, 0x6e, 0x61,
+ 0x6e, 0x69, 0x2c, 0x20, 0x6b, 0x61, 0x73, 0x6b, 0x69, 0x73, 0x61, 0x76, 0x75, 0x75, 0x6e, 0x20,
+ 0x6c, 0x61, 0x61, 0x6b, 0x73, 0x6f, 0x74, 0x20, 0x76, 0x65, 0x72, 0x68, 0x6f, 0x75, 0x75, 0x2e,
+ 0x20, 0x45, 0x6e, 0x20, 0x6d, 0x61, 0x20, 0x69, 0x6c, 0x6f, 0x69, 0x74, 0x73, 0x65, 0x2c, 0x20,
+ 0x73, 0x75, 0x72, 0x65, 0x20, 0x68, 0x75, 0x6f, 0x6b, 0x61, 0x61, 0x2c, 0x20, 0x6d, 0x75, 0x74,
+ 0x74, 0x61, 0x20, 0x6d, 0x65, 0x74, 0x73, 0xe4, 0x6e, 0x20, 0x74, 0x75, 0x6d, 0x6d, 0x75, 0x75,
+ 0x73, 0x20, 0x6d, 0x75, 0x6c, 0x6c, 0x65, 0x20, 0x74, 0x75, 0x6f, 0x6b, 0x61, 0x61, 0x2e, 0x20,
+ 0x50, 0x75, 0x75, 0x6e, 0x74, 0x6f, 0x20, 0x70, 0x69, 0x6c, 0x76, 0x65, 0x6e, 0x2c, 0x20, 0x6d,
+ 0x69, 0x20, 0x68, 0x75, 0x6b, 0x6b, 0x75, 0x75, 0x2c, 0x20, 0x73, 0x69, 0x69, 0x6e, 0x74, 0x6f,
+ 0x20, 0x76, 0x61, 0x72, 0x61, 0x6e, 0x20, 0x74, 0x75, 0x75, 0x6c, 0x69, 0x73, 0x65, 0x6e, 0x2c,
+ 0x20, 0x6d, 0x69, 0x20, 0x6e, 0x75, 0x6b, 0x6b, 0x75, 0x75, 0x2e, 0x20, 0x54, 0x75, 0x6f, 0x6b,
+ 0x73, 0x75, 0x74, 0x20, 0x76, 0x61, 0x6e, 0x61, 0x6d, 0x6f, 0x6e, 0x20, 0x6a, 0x61, 0x20, 0x76,
+ 0x61, 0x72, 0x6a, 0x6f, 0x74, 0x20, 0x76, 0x65, 0x65, 0x6e, 0x2c, 0x20, 0x6e, 0x69, 0x69, 0x73,
+ 0x74, 0xe4, 0x20, 0x73, 0x79, 0x64, 0xe4, 0x6d, 0x65, 0x6e, 0x69, 0x20, 0x6c, 0x61, 0x75, 0x6c,
+ 0x75, 0x6e, 0x20, 0x74, 0x65, 0x65, 0x6e, 0x2e, 0x20, 0x2d, 0x20, 0x45, 0x69, 0x6e, 0x6f, 0x20,
+ 0x4c, 0x65, 0x69, 0x6e, 0x6f
+ };
+
+ AssertEncrypt (key, text, expected);
+ }
+ }
+}
diff --git a/UnitTests/Security/Ntlm/Type1MessageTests.cs b/UnitTests/Security/Ntlm/Type1MessageTests.cs
new file mode 100644
index 0000000000..794fa29bf0
--- /dev/null
+++ b/UnitTests/Security/Ntlm/Type1MessageTests.cs
@@ -0,0 +1,85 @@
+//
+// Type1MessageTests.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2021 .NET Foundation and Contributors
+//
+// 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 NUnit.Framework;
+
+using MailKit.Security.Ntlm;
+
+namespace UnitTests.Security.Ntlm {
+ [TestFixture]
+ public class Type1MessageTests
+ {
+ [Test]
+ public void TestArgumentExceptions ()
+ {
+ byte[] badMessageData = { 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x01, 0x00, 0x00, 0x00, 0x00 };
+
+ Assert.Throws (() => new Type1Message (null, 0, 16));
+ Assert.Throws (() => new Type1Message (new byte[8], 0, 8));
+ Assert.Throws (() => new Type1Message (new byte[8], -1, 8));
+ Assert.Throws (() => new Type1Message (badMessageData, 0, badMessageData.Length));
+ }
+
+ [Test]
+ // Example from http://www.innovation.ch/java/ntlm.html
+ public void TestEncodeJavaExample ()
+ {
+ var type1 = new Type1Message (NtlmFlags.NegotiateUnicode | NtlmFlags.NegotiateOem | NtlmFlags.RequestTarget | NtlmFlags.NegotiateNtlm | NtlmFlags.NegotiateAlwaysSign, "Ursa-Minor", "LightCity");
+
+ Assert.AreEqual (1, type1.Type, "Type");
+ Assert.AreEqual ((NtlmFlags) 0xb207, type1.Flags, "Flags");
+ Assert.AreEqual ("4E-54-4C-4D-53-53-50-00-01-00-00-00-07-B2-00-00-0A-00-0A-00-29-00-00-00-09-00-09-00-20-00-00-00-4C-49-47-48-54-43-49-54-59-55-52-53-41-2D-4D-49-4E-4F-52", BitConverter.ToString (type1.Encode ()), "Encode");
+ }
+
+ [Test]
+ // Example from http://www.innovation.ch/java/ntlm.html
+ public void TestDecodeJavaExample ()
+ {
+ byte[] rawData = { 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0xb2, 0x00, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x29, 0x00, 0x00, 0x00, 0x09, 0x00, 0x09, 0x00, 0x20, 0x00, 0x00, 0x00, 0x4c, 0x49, 0x47, 0x48, 0x54, 0x43, 0x49, 0x54, 0x59, 0x55, 0x52, 0x53, 0x41, 0x2d, 0x4d, 0x49, 0x4e, 0x4f, 0x52 };
+ var type1 = new Type1Message (rawData, 0, rawData.Length);
+
+ Assert.AreEqual (1, type1.Type, "Type");
+ Assert.AreEqual ((NtlmFlags) 0xb203, type1.Flags, "Flags");
+ Assert.AreEqual ("URSA-MINOR", type1.Domain, "Domain");
+ Assert.AreEqual ("LIGHTCITY", type1.Workstation, "Workstation");
+ }
+
+ [Test]
+ // Example from http://davenport.sourceforge.net/ntlm.html#type1MessageExample
+ public void TestDecodeDavenportExample ()
+ {
+ byte[] rawData = { 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x32, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x0b, 0x00, 0x20, 0x00, 0x00, 0x00, 0x57, 0x4f, 0x52, 0x4b, 0x53, 0x54, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x44, 0x4f, 0x4d, 0x41, 0x49, 0x4e };
+ var type1 = new Type1Message (rawData, 0, rawData.Length);
+
+ Assert.AreEqual (1, type1.Type, "Type");
+ Assert.AreEqual ((NtlmFlags) 0x3207, type1.Flags, "Flags");
+ Assert.AreEqual ("DOMAIN", type1.Domain, "Domain");
+ Assert.AreEqual ("WORKSTATION", type1.Workstation, "Workstation");
+ }
+ }
+}
diff --git a/UnitTests/Security/Ntlm/Type2MessageTests.cs b/UnitTests/Security/Ntlm/Type2MessageTests.cs
new file mode 100644
index 0000000000..8951c2e707
--- /dev/null
+++ b/UnitTests/Security/Ntlm/Type2MessageTests.cs
@@ -0,0 +1,121 @@
+//
+// Type2MessageTests.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2021 .NET Foundation and Contributors
+//
+// 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 NUnit.Framework;
+
+using MailKit.Security.Ntlm;
+
+namespace UnitTests.Security.Ntlm {
+ [TestFixture]
+ public class Type2MessageTests
+ {
+ static byte[] DavenportExampleNonce = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef };
+ static byte[] JavaExampleNonce = { 0x53, 0x72, 0x76, 0x4e, 0x6f, 0x6e, 0x63, 0x65 };
+
+ [Test]
+ public void TestArgumentExceptions ()
+ {
+ byte[] badMessageData = { 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x01, 0x00, 0x00, 0x00, 0x00 };
+ var type2 = new Type2Message ();
+
+ Assert.Throws (() => new Type2Message (null, 0, 16));
+ Assert.Throws (() => new Type2Message (new byte[8], 0, 8));
+ Assert.Throws (() => new Type2Message (new byte[8], -1, 8));
+ Assert.Throws (() => new Type2Message (badMessageData, 0, badMessageData.Length));
+
+ Assert.Throws (() => type2.ServerChallenge = null);
+ Assert.Throws (() => type2.ServerChallenge = new byte[9]);
+ }
+
+ [Test]
+ // Example from http://www.innovation.ch/java/ntlm.html
+ public void TestEncodeJavaExample ()
+ {
+ var type2 = new Type2Message (NtlmFlags.NegotiateUnicode | NtlmFlags.NegotiateNtlm | NtlmFlags.NegotiateAlwaysSign) { ServerChallenge = JavaExampleNonce };
+
+ Assert.AreEqual (2, type2.Type, "Type");
+ Assert.AreEqual ((NtlmFlags) 0x8201, type2.Flags, "Flags");
+ Assert.AreEqual ("4E-54-4C-4D-53-53-50-00-02-00-00-00-00-00-00-00-00-00-00-00-01-82-00-00-53-72-76-4E-6F-6E-63-65-00-00-00-00-00-00-00-00", BitConverter.ToString (type2.Encode ()), "Encode");
+ }
+
+ [Test]
+ // Example from http://www.innovation.ch/java/ntlm.html
+ public void TestDecodeJavaExample ()
+ {
+ byte[] rawData = { 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x82, 0x00, 0x00, 0x53, 0x72, 0x76, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+ var type2 = new Type2Message (rawData, 0, rawData.Length);
+
+ Assert.AreEqual (2, type2.Type, "Type");
+ Assert.AreEqual ((NtlmFlags) 0x8201, type2.Flags, "Flags");
+ Assert.AreEqual (BitConverter.ToString (JavaExampleNonce), BitConverter.ToString (type2.ServerChallenge), "ServerChallenge");
+ }
+
+ [Test]
+ // Example from http://davenport.sourceforge.net/ntlm.html#type2MessageExample
+ public void TestEncodeDavenportExample ()
+ {
+ var type2 = new Type2Message (NtlmFlags.NegotiateUnicode | NtlmFlags.NegotiateNtlm | NtlmFlags.TargetTypeDomain | NtlmFlags.NegotiateTargetInfo) {
+ TargetInfo = new NtlmTargetInfo () {
+ DomainName = "DOMAIN",
+ ServerName = "SERVER",
+ DnsDomainName = "domain.com",
+ DnsServerName = "server.domain.com"
+ },
+ ServerChallenge = DavenportExampleNonce,
+ TargetName = "DOMAIN"
+ };
+
+ Assert.AreEqual (2, type2.Type, "Type");
+ Assert.AreEqual ((NtlmFlags) 0x00810201, type2.Flags, "Flags");
+ Assert.AreEqual ("DOMAIN", type2.TargetName, "TargetName");
+ Assert.AreEqual ("SERVER", type2.TargetInfo.ServerName, "ServerName");
+ Assert.AreEqual ("DOMAIN", type2.TargetInfo.DomainName, "DomainName");
+ Assert.AreEqual ("server.domain.com", type2.TargetInfo.DnsServerName, "DnsServerName");
+ Assert.AreEqual ("domain.com", type2.TargetInfo.DnsDomainName, "DnsDomainName");
+ Assert.AreEqual ("01-23-45-67-89-AB-CD-EF", BitConverter.ToString (type2.ServerChallenge), "ServerChallenge");
+ Assert.AreEqual ("4E-54-4C-4D-53-53-50-00-02-00-00-00-0C-00-0C-00-30-00-00-00-01-02-81-00-01-23-45-67-89-AB-CD-EF-00-00-00-00-00-00-00-00-62-00-62-00-3C-00-00-00-44-00-4F-00-4D-00-41-00-49-00-4E-00-02-00-0C-00-44-00-4F-00-4D-00-41-00-49-00-4E-00-01-00-0C-00-53-00-45-00-52-00-56-00-45-00-52-00-04-00-14-00-64-00-6F-00-6D-00-61-00-69-00-6E-00-2E-00-63-00-6F-00-6D-00-03-00-22-00-73-00-65-00-72-00-76-00-65-00-72-00-2E-00-64-00-6F-00-6D-00-61-00-69-00-6E-00-2E-00-63-00-6F-00-6D-00-00-00-00-00", BitConverter.ToString (type2.Encode ()), "Encode");
+ }
+
+ [Test]
+ // Example from http://davenport.sourceforge.net/ntlm.html#type2MessageExample
+ public void TestDecodeDavenportExample ()
+ {
+ byte[] rawData = { 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x02, 0x81, 0x00, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x62, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x44, 0x00, 0x4f, 0x00, 0x4d, 0x00, 0x41, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x02, 0x00, 0x0c, 0x00, 0x44, 0x00, 0x4f, 0x00, 0x4d, 0x00, 0x41, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x01, 0x00, 0x0c, 0x00, 0x53, 0x00, 0x45, 0x00, 0x52, 0x00, 0x56, 0x00, 0x45, 0x00, 0x52, 0x00, 0x04, 0x00, 0x14, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x03, 0x00, 0x22, 0x00, 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x2e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00 };
+ var type2 = new Type2Message (rawData, 0, rawData.Length);
+
+ Assert.AreEqual (2, type2.Type, "Type");
+ Assert.AreEqual ((NtlmFlags) 0x00810201, type2.Flags, "Flags");
+ Assert.AreEqual ("DOMAIN", type2.TargetName, "TargetName");
+ Assert.AreEqual ("SERVER", type2.TargetInfo.ServerName, "ServerName");
+ Assert.AreEqual ("DOMAIN", type2.TargetInfo.DomainName, "DomainName");
+ Assert.AreEqual ("server.domain.com", type2.TargetInfo.DnsServerName, "DnsServerName");
+ Assert.AreEqual ("domain.com", type2.TargetInfo.DnsDomainName, "DnsDomainName");
+ Assert.AreEqual ("01-23-45-67-89-AB-CD-EF", BitConverter.ToString (type2.ServerChallenge), "ServerChallenge");
+ }
+ }
+}
diff --git a/UnitTests/Security/Ntlm/Type3MessageTests.cs b/UnitTests/Security/Ntlm/Type3MessageTests.cs
new file mode 100644
index 0000000000..fe14cee7eb
--- /dev/null
+++ b/UnitTests/Security/Ntlm/Type3MessageTests.cs
@@ -0,0 +1,95 @@
+//
+// Type3MessageTests.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2021 .NET Foundation and Contributors
+//
+// 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 NUnit.Framework;
+
+using MailKit.Security.Ntlm;
+
+namespace UnitTests.Security.Ntlm {
+ [TestFixture]
+ public class Type3MessageTests
+ {
+ [Test]
+ public void TestArgumentExceptions ()
+ {
+ byte[] badMessageData = { 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x01, 0x00, 0x00, 0x00, 0x00 };
+ var type1 = new Type1Message ();
+ var type2 = new Type2Message ();
+ var type3 = new Type3Message (type1, type2, "username", "password", "workstation");
+
+ Assert.Throws (() => new Type3Message (null, type2, "username", "password", "workstation"));
+ Assert.Throws (() => new Type3Message (type1, null, "username", "password", "workstation"));
+ Assert.Throws (() => new Type3Message (type1, type2, null, "password", "workstation"));
+ Assert.Throws (() => new Type3Message (type1, type2, "username", null, "workstation"));
+
+ Assert.Throws (() => new Type3Message (null, 0, 16));
+ Assert.Throws (() => new Type3Message (new byte[8], 0, 8));
+ Assert.Throws (() => new Type3Message (new byte[8], -1, 8));
+ Assert.Throws (() => new Type3Message (badMessageData, 0, badMessageData.Length));
+
+ Assert.DoesNotThrow (() => type3.ClientChallenge = null);
+ Assert.Throws (() => type3.ClientChallenge = new byte[9]);
+ }
+
+ [Test]
+ // Example from http://www.innovation.ch/java/ntlm.html
+ public void TestDecodeJavaExample ()
+ {
+ byte[] rawData = { 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x03, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0x72, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x14, 0x00, 0x14, 0x00, 0x40, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x54, 0x00, 0x00, 0x00, 0x12, 0x00, 0x12, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa2, 0x00, 0x00, 0x00, 0x01, 0x82, 0x00, 0x00, 0x55, 0x00, 0x52, 0x00, 0x53, 0x00, 0x41, 0x00, 0x2d, 0x00, 0x4d, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x4f, 0x00, 0x52, 0x00, 0x5a, 0x00, 0x61, 0x00, 0x70, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x4c, 0x00, 0x49, 0x00, 0x47, 0x00, 0x48, 0x00, 0x54, 0x00, 0x43, 0x00, 0x49, 0x00, 0x54, 0x00, 0x59, 0x00, 0xad, 0x87, 0xca, 0x6d, 0xef, 0xe3, 0x46, 0x85, 0xb9, 0xc4, 0x3c, 0x47, 0x7a, 0x8c, 0x42, 0xd6, 0x00, 0x66, 0x7d, 0x68, 0x92, 0xe7, 0xe8, 0x97, 0xe0, 0xe0, 0x0d, 0xe3, 0x10, 0x4a, 0x1b, 0xf2, 0x05, 0x3f, 0x07, 0xc7, 0xdd, 0xa8, 0x2d, 0x3c, 0x48, 0x9a, 0xe9, 0x89, 0xe1, 0xb0, 0x00, 0xd3 };
+ var type3 = new Type3Message (rawData, 0, rawData.Length);
+
+ Assert.AreEqual (3, type3.Type, "Type");
+ Assert.AreEqual ((NtlmFlags) 0x8201, type3.Flags, "Flags");
+ Assert.AreEqual ("URSA-MINOR", type3.Domain, "Domain");
+ Assert.AreEqual ("LIGHTCITY", type3.Workstation, "Workstation");
+ Assert.AreEqual ("Zaphod", type3.UserName, "UserName");
+ Assert.IsNull (type3.Password, "Password");
+
+ Assert.AreEqual ("AD-87-CA-6D-EF-E3-46-85-B9-C4-3C-47-7A-8C-42-D6-00-66-7D-68-92-E7-E8-97", BitConverter.ToString (type3.LmChallengeResponse), "LmChallengeResponse");
+ Assert.AreEqual ("E0-E0-0D-E3-10-4A-1B-F2-05-3F-07-C7-DD-A8-2D-3C-48-9A-E9-89-E1-B0-00-D3", BitConverter.ToString (type3.NtChallengeResponse), "NtChallengeResponse");
+ }
+
+ [Test]
+ // Example from http://davenport.sourceforge.net/ntlm.html#type3MessageExample
+ public void TestDecodeDavenportExample ()
+ {
+ byte[] rawData = { 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x03, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0x82, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x40, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x16, 0x00, 0x16, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x44, 0x00, 0x4f, 0x00, 0x4d, 0x00, 0x41, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x75, 0x00, 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x57, 0x00, 0x4f, 0x00, 0x52, 0x00, 0x4b, 0x00, 0x53, 0x00, 0x54, 0x00, 0x41, 0x00, 0x54, 0x00, 0x49, 0x00, 0x4f, 0x00, 0x4e, 0x00, 0xc3, 0x37, 0xcd, 0x5c, 0xbd, 0x44, 0xfc, 0x97, 0x82, 0xa6, 0x67, 0xaf, 0x6d, 0x42, 0x7c, 0x6d, 0xe6, 0x7c, 0x20, 0xc2, 0xd3, 0xe7, 0x7c, 0x56, 0x25, 0xa9, 0x8c, 0x1c, 0x31, 0xe8, 0x18, 0x47, 0x46, 0x6b, 0x29, 0xb2, 0xdf, 0x46, 0x80, 0xf3, 0x99, 0x58, 0xfb, 0x8c, 0x21, 0x3a, 0x9c, 0xc6 };
+ Type3Message type3 = new Type3Message (rawData, 0, rawData.Length);
+
+ Assert.AreEqual (3, type3.Type, "Type");
+ Assert.AreEqual ((NtlmFlags) 0x201, type3.Flags, "Flags");
+ Assert.AreEqual ("DOMAIN", type3.Domain, "Domain");
+ Assert.AreEqual ("WORKSTATION", type3.Workstation, "Workstation");
+ Assert.AreEqual ("user", type3.UserName, "UserName");
+ Assert.IsNull (type3.Password, "Password");
+
+ Assert.AreEqual ("C3-37-CD-5C-BD-44-FC-97-82-A6-67-AF-6D-42-7C-6D-E6-7C-20-C2-D3-E7-7C-56", BitConverter.ToString (type3.LmChallengeResponse), "LmChallengeResponse");
+ Assert.AreEqual ("25-A9-8C-1C-31-E8-18-47-46-6B-29-B2-DF-46-80-F3-99-58-FB-8C-21-3A-9C-C6", BitConverter.ToString (type3.NtChallengeResponse), "NtChallengeResponse");
+ }
+ }
+}
diff --git a/UnitTests/Security/SaslMechanismNtlmTests.cs b/UnitTests/Security/SaslMechanismNtlmTests.cs
index 773d7a5b43..16644645ad 100644
--- a/UnitTests/Security/SaslMechanismNtlmTests.cs
+++ b/UnitTests/Security/SaslMechanismNtlmTests.cs
@@ -50,6 +50,28 @@ public void TestArgumentExceptions ()
Assert.Throws (() => new SaslMechanismNtlm ("username", null));
}
+#if false
+ static string ToCSharpByteArrayInitializer (string name, byte[] buffer)
+ {
+ var builder = new System.Text.StringBuilder ();
+ int index = 0;
+
+ builder.AppendLine ($"static readonly byte[] {name} = {{");
+ while (index < buffer.Length) {
+ builder.Append ('\t');
+ for (int i = 0; i < 16 && index < buffer.Length; i++, index++)
+ builder.AppendFormat ("0x{0}, ", buffer[index].ToString ("x2"));
+ builder.Length--;
+ if (index == buffer.Length)
+ builder.Length--;
+ builder.AppendLine ();
+ }
+ builder.AppendLine ($"}};");
+
+ return builder.ToString ();
+ }
+#endif
+
static byte ToXDigit (char c)
{
if (c >= 0x41) {
@@ -86,12 +108,33 @@ static string HexEncode (byte[] value)
return builder.ToString ();
}
+ static Type1Message DecodeType1Message (string token)
+ {
+ var message = Convert.FromBase64String (token);
+
+ return new Type1Message (message, 0, message.Length);
+ }
+
+ static Type2Message DecodeType2Message (string token)
+ {
+ var message = Convert.FromBase64String (token);
+
+ return new Type2Message (message, 0, message.Length);
+ }
+
+ static Type3Message DecodeType3Message (string token)
+ {
+ var message = Convert.FromBase64String (token);
+
+ return new Type3Message (message, 0, message.Length);
+ }
+
[Test]
public void TestNtlmTargetInfoEncode ()
{
var now = DateTime.Now.Ticks;
- var targetInfo = new TargetInfo {
+ var targetInfo = new NtlmTargetInfo {
ChannelBinding = Encoding.ASCII.GetBytes ("channel-binding"),
TargetName = "TARGET",
DnsTreeName = "target.domain.com",
@@ -104,7 +147,7 @@ public void TestNtlmTargetInfoEncode ()
};
var encoded = targetInfo.Encode (true);
- var decoded = new TargetInfo (encoded, 0, encoded.Length, true);
+ var decoded = new NtlmTargetInfo (encoded, 0, encoded.Length, true);
Assert.AreEqual ("channel-binding", Encoding.ASCII.GetString (decoded.ChannelBinding), "ChannelBinding does not match.");
Assert.AreEqual (targetInfo.DnsDomainName, decoded.DnsDomainName, "DnsDomainName does not match.");
@@ -119,16 +162,24 @@ public void TestNtlmTargetInfoEncode ()
static readonly byte [] NtlmType1EncodedMessage = {
0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x07, 0x32, 0x00, 0x02, 0x06, 0x00, 0x06, 0x00, 0x33, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x0b, 0x00, 0x28, 0x00, 0x00, 0x00, 0x05, 0x00, 0x93, 0x08,
- 0x00, 0x00, 0x00, 0x0f, 0x57, 0x4f, 0x52, 0x4b, 0x53, 0x54, 0x41, 0x54,
- 0x49, 0x4f, 0x4e, 0x44, 0x4f, 0x4d, 0x41, 0x49, 0x4e
+ 0x07, 0xb2, 0x08, 0x20, 0x06, 0x00, 0x06, 0x00, 0x2b, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x0b, 0x00, 0x20, 0x00, 0x00, 0x00, 0x57, 0x4f, 0x52, 0x4b,
+ 0x53, 0x54, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x44, 0x4f, 0x4d, 0x41, 0x49,
+ 0x4e
+ };
+
+ static readonly byte[] NtlmType1EncodedMessageWithVersion = {
+ 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x07, 0x82, 0x08, 0x22, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x05, 0x00, 0x93, 0x08,
+ 0x00, 0x00, 0x00, 0x0f,
};
[Test]
public void TestNtlmType1MessageEncode ()
{
- var type1 = new Type1Message ("Workstation", "Domain", new Version (5, 0, 2195));
+ var flags = NtlmFlags.NegotiateUnicode | NtlmFlags.NegotiateOem | NtlmFlags.RequestTarget | NtlmFlags.NegotiateNtlm | NtlmFlags.NegotiateAlwaysSign | NtlmFlags.NegotiateExtendedSessionSecurity | NtlmFlags.Negotiate128;
+ var type1 = new Type1Message (flags, "Domain", "Workstation");
var encoded = type1.Encode ();
string actual, expected;
@@ -138,17 +189,46 @@ public void TestNtlmType1MessageEncode ()
Assert.AreEqual (expected, actual, "The encoded Type1Message did not match the expected result.");
}
+ [Test]
+ public void TestNtlmType1MessageEncodeWithVersion ()
+ {
+ var flags = NtlmFlags.NegotiateUnicode | NtlmFlags.NegotiateOem | NtlmFlags.RequestTarget | NtlmFlags.NegotiateNtlm | NtlmFlags.NegotiateAlwaysSign | NtlmFlags.NegotiateExtendedSessionSecurity | NtlmFlags.Negotiate128;
+ var type1 = new Type1Message (flags, "Domain", "Workstation", new Version (5, 0, 2195));
+ var encoded = type1.Encode ();
+ string actual, expected;
+
+ expected = HexEncode (NtlmType1EncodedMessageWithVersion);
+ actual = HexEncode (encoded);
+
+ Assert.AreEqual (expected, actual, "The encoded Type1Message did not match the expected result.");
+ }
+
[Test]
public void TestNtlmType1MessageDecode ()
{
- var flags = NtlmFlags.NegotiateUnicode | NtlmFlags.NegotiateDomainSupplied | NtlmFlags.NegotiateWorkstationSupplied |
- NtlmFlags.NegotiateNtlm | NtlmFlags.NegotiateOem | NtlmFlags.RequestTarget | NtlmFlags.NegotiateVersion;
+ var flags = NtlmFlags.NegotiateUnicode | NtlmFlags.NegotiateOem | NtlmFlags.RequestTarget | NtlmFlags.NegotiateNtlm |
+ NtlmFlags.NegotiateDomainSupplied | NtlmFlags.NegotiateWorkstationSupplied | NtlmFlags.NegotiateAlwaysSign |
+ NtlmFlags.NegotiateExtendedSessionSecurity | NtlmFlags.Negotiate128;
var type1 = new Type1Message (NtlmType1EncodedMessage, 0, NtlmType1EncodedMessage.Length);
- var osVersion = new Version (5, 0, 2195);
Assert.AreEqual (flags, type1.Flags, "The expected flags do not match.");
Assert.AreEqual ("WORKSTATION", type1.Workstation, "The expected workstation name does not match.");
Assert.AreEqual ("DOMAIN", type1.Domain, "The expected domain does not match.");
+ Assert.AreEqual (null, type1.OSVersion, "The expected OS Version does not match.");
+ }
+
+ [Test]
+ public void TestNtlmType1MessageDecodeWithVersion ()
+ {
+ var flags = NtlmFlags.NegotiateUnicode | NtlmFlags.NegotiateOem | NtlmFlags.RequestTarget | NtlmFlags.NegotiateNtlm |
+ NtlmFlags.NegotiateAlwaysSign | NtlmFlags.NegotiateExtendedSessionSecurity | NtlmFlags.NegotiateVersion |
+ NtlmFlags.Negotiate128;
+ var type1 = new Type1Message (NtlmType1EncodedMessageWithVersion, 0, NtlmType1EncodedMessageWithVersion.Length);
+ var osVersion = new Version (5, 0, 2195);
+
+ Assert.AreEqual (flags, type1.Flags, "The expected flags do not match.");
+ Assert.AreEqual (string.Empty, type1.Workstation, "The expected workstation name does not match.");
+ Assert.AreEqual (string.Empty, type1.Domain, "The expected domain does not match.");
Assert.AreEqual (osVersion, type1.OSVersion, "The expected OS Version does not match.");
}
@@ -172,22 +252,21 @@ public void TestNtlmType1MessageDecode ()
[Test]
public void TestNtlmType2MessageEncode ()
{
- var targetInfo = new TargetInfo {
+ var targetInfo = new NtlmTargetInfo {
DomainName = "DOMAIN",
ServerName = "SERVER",
DnsDomainName = "domain.com",
DnsServerName = "server.domain.com"
};
- var type2 = new Type2Message {
- Flags = NtlmFlags.NegotiateUnicode | NtlmFlags.NegotiateNtlm | NtlmFlags.TargetTypeDomain | NtlmFlags.NegotiateTargetInfo,
- Nonce = HexDecode ("0123456789abcdef"),
+ var type2 = new Type2Message (NtlmFlags.NegotiateUnicode | NtlmFlags.NegotiateNtlm | NtlmFlags.TargetTypeDomain | NtlmFlags.NegotiateTargetInfo) {
+ ServerChallenge = HexDecode ("0123456789abcdef"),
TargetInfo = targetInfo,
TargetName = "DOMAIN",
};
- Assert.Throws (() => type2.Nonce = null);
- Assert.Throws (() => type2.Nonce = new byte[0]);
+ Assert.Throws (() => type2.ServerChallenge = null);
+ Assert.Throws (() => type2.ServerChallenge = new byte[0]);
var encoded = type2.Encode ();
string actual, expected;
@@ -208,10 +287,10 @@ public void TestNtlmType2MessageDecode ()
Assert.AreEqual (flags, type2.Flags, "The expected flags do not match.");
Assert.AreEqual ("DOMAIN", type2.TargetName, "The expected TargetName does not match.");
- var nonce = HexEncode (type2.Nonce);
+ var nonce = HexEncode (type2.ServerChallenge);
Assert.AreEqual ("0123456789abcdef", nonce, "The expected nonce does not match.");
- var targetInfo = HexEncode (type2.EncodedTargetInfo);
+ var targetInfo = HexEncode (type2.GetEncodedTargetInfo ());
Assert.AreEqual (expectedTargetInfo, targetInfo, "The expected TargetInfo does not match.");
Assert.AreEqual ("DOMAIN", type2.TargetInfo.DomainName, "The expected TargetInfo domain name does not match.");
@@ -224,28 +303,26 @@ public void TestNtlmType2MessageDecode ()
}
static readonly byte[] NtlmType2EncodedMessageWithOSVersion = {
- 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x0c, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x02, 0x81, 0x02,
- 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6e, 0x00, 0x4c, 0x00, 0x00, 0x00,
- 0x06, 0x03, 0x80, 0x25, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x4f, 0x00, 0x4d, 0x00, 0x41, 0x00,
- 0x49, 0x00, 0x4e, 0x00, 0x02, 0x00, 0x0c, 0x00, 0x44, 0x00, 0x4f, 0x00,
- 0x4d, 0x00, 0x41, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x01, 0x00, 0x0c, 0x00,
- 0x53, 0x00, 0x45, 0x00, 0x52, 0x00, 0x56, 0x00, 0x45, 0x00, 0x52, 0x00,
- 0x04, 0x00, 0x14, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00,
- 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00,
- 0x03, 0x00, 0x22, 0x00, 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00,
- 0x65, 0x00, 0x72, 0x00, 0x2e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x6d, 0x00,
- 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00,
- 0x6d, 0x00, 0x07, 0x00, 0x08, 0x00, 0xd2, 0x02, 0x96, 0x49, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00,
+ 0x38, 0x00, 0x00, 0x00, 0x01, 0x02, 0x81, 0x02, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6e, 0x00, 0x44, 0x00, 0x00, 0x00,
+ 0x06, 0x03, 0x80, 0x25, 0x00, 0x00, 0x00, 0x0f, 0x44, 0x00, 0x4f, 0x00, 0x4d, 0x00, 0x41, 0x00,
+ 0x49, 0x00, 0x4e, 0x00, 0x02, 0x00, 0x0c, 0x00, 0x44, 0x00, 0x4f, 0x00, 0x4d, 0x00, 0x41, 0x00,
+ 0x49, 0x00, 0x4e, 0x00, 0x01, 0x00, 0x0c, 0x00, 0x53, 0x00, 0x45, 0x00, 0x52, 0x00, 0x56, 0x00,
+ 0x45, 0x00, 0x52, 0x00, 0x04, 0x00, 0x14, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x03, 0x00, 0x22, 0x00,
+ 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x2e, 0x00, 0x64, 0x00,
+ 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00,
+ 0x6d, 0x00, 0x07, 0x00, 0x08, 0x00, 0xd2, 0x02, 0x96, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00
};
[Test]
public void TestNtlmType2MessageEncodeWithOSVersion ()
{
- var targetInfo = new TargetInfo {
+ var flags = NtlmFlags.NegotiateUnicode | NtlmFlags.NegotiateNtlm | NtlmFlags.TargetTypeDomain | NtlmFlags.NegotiateTargetInfo | NtlmFlags.NegotiateVersion;
+
+ var targetInfo = new NtlmTargetInfo {
DomainName = "DOMAIN",
ServerName = "SERVER",
DnsDomainName = "domain.com",
@@ -253,15 +330,14 @@ public void TestNtlmType2MessageEncodeWithOSVersion ()
Timestamp = 1234567890
};
- var type2 = new Type2Message (new Version (6, 3, 9600)) {
- Flags = NtlmFlags.NegotiateUnicode | NtlmFlags.NegotiateNtlm | NtlmFlags.TargetTypeDomain | NtlmFlags.NegotiateTargetInfo | NtlmFlags.NegotiateVersion,
- Nonce = HexDecode ("0123456789abcdef"),
+ var type2 = new Type2Message (flags, new Version (6, 3, 9600)) {
+ ServerChallenge = HexDecode ("0123456789abcdef"),
TargetInfo = targetInfo,
TargetName = "DOMAIN",
};
- Assert.Throws (() => type2.Nonce = null);
- Assert.Throws (() => type2.Nonce = new byte[0]);
+ Assert.Throws (() => type2.ServerChallenge = null);
+ Assert.Throws (() => type2.ServerChallenge = new byte[0]);
var encoded = type2.Encode ();
string actual, expected;
@@ -282,10 +358,10 @@ public void TestNtlmType2MessageDecodeWithOSVersion ()
Assert.AreEqual (flags, type2.Flags, "The expected flags do not match.");
Assert.AreEqual ("DOMAIN", type2.TargetName, "The expected TargetName does not match.");
- var nonce = HexEncode (type2.Nonce);
+ var nonce = HexEncode (type2.ServerChallenge);
Assert.AreEqual ("0123456789abcdef", nonce, "The expected nonce does not match.");
- var targetInfo = HexEncode (type2.EncodedTargetInfo);
+ var targetInfo = HexEncode (type2.GetEncodedTargetInfo ());
Assert.AreEqual (expectedTargetInfo, targetInfo, "The expected TargetInfo does not match.");
Assert.AreEqual ("DOMAIN", type2.TargetInfo.DomainName, "The expected TargetInfo domain name does not match.");
@@ -301,13 +377,23 @@ public void TestNtlmType2MessageDecodeWithOSVersion ()
[Test]
public void TestNtlmType3MessageEncode ()
{
- const string expected = "TlRMTVNTUAADAAAAGAAYAGoAAAAYABgAggAAAAwADABAAAAACAAIAEwAAAAWABYAVAAAAAAAAACaAAAAAQKAAEQATwBNAEEASQBOAHUAcwBlAHIAVwBPAFIASwBTAFQAQQBUAEkATwBOAJje97h/iKpdr+Lfd5aIoXLe8Rx9XM3vE91UKLAehvTfyr6sOUlG29Q+6I95TdYyVQ==";
+ const string expected = "TlRMTVNTUAADAAAAGAAYAGoAAACqAKoAggAAAAwADABAAAAACAAIAEwAAAAWABYAVAAAAAAAAAAsAQAAAQKBAEQATwBNAEEASQBOAHUAcwBlAHIAVwBPAFIASwBTAFQAQQBUAEkATwBOAAFVaqKdtK6RUUd6vhq2MnkBAgMEBQUGByItsEaz8xYhhLclKBEweI0BAQAAAAAAAACQVAPpkdcBAQIDBAUFBgcAAAAAAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAkAAAAKABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
- var token = Convert.FromBase64String (challenge2);
- var type2 = new Type2Message (token, 0, token.Length);
- var type3 = new Type3Message (type2, null, NtlmAuthLevel.LM_and_NTLM, "user", "password", "WORKSTATION");
+ var flags = NtlmFlags.NegotiateUnicode | NtlmFlags.NegotiateNtlm | NtlmFlags.TargetTypeDomain | NtlmFlags.NegotiateTargetInfo;
+ var timestamp = new DateTime (2021, 08, 15, 15, 20, 00, DateTimeKind.Utc).Ticks;
+ var nonce = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x05, 0x06, 0x07 };
+ var type1 = new Type1Message (flags, null, null, new Version (10, 0, 19043));
+ var type2 = DecodeType2Message (challenge2);
+ var type3 = new Type3Message (type1, type2, "user", "password", "WORKSTATION") {
+ ClientChallenge = nonce,
+ Timestamp = timestamp
+ };
+
+ type3.ComputeNtlmV2 (null, false, null);
var actual = Convert.ToBase64String (type3.Encode ());
+ //var expectedType3 = DecodeType3Message (expected);
+
Assert.AreEqual (expected, actual, "The encoded Type3Message did not match the expected result.");
}
@@ -316,32 +402,29 @@ public void TestNtlmType3MessageDecode ()
{
const string challenge3 = "TlRMTVNTUAADAAAAGAAYAGoAAAAYABgAggAAAAwADABAAAAACAAIAEwAAAAWABYAVAAAAAAAAACaAAAAAQIAAEQATwBNAEEASQBOAHUAcwBlAHIAVwBPAFIASwBTAFQAQQBUAEkATwBOAJje97h/iKpdr+Lfd5aIoXLe8Rx9XM3vE91UKLAehvTfyr6sOUlG29Q+6I95TdYyVQ==";
var flags = NtlmFlags.NegotiateNtlm | NtlmFlags.NegotiateUnicode;
- var token = Convert.FromBase64String (challenge3);
- var type3 = new Type3Message (token, 0, token.Length);
+ var type3 = DecodeType3Message (challenge3);
Assert.AreEqual (flags, type3.Flags, "The expected flags do not match.");
Assert.AreEqual ("DOMAIN", type3.Domain, "The expected Domain does not match.");
Assert.AreEqual ("WORKSTATION", type3.Workstation, "The expected Workstation does not match.");
- Assert.AreEqual ("user", type3.Username, "The expected Username does not match.");
+ Assert.AreEqual ("user", type3.UserName, "The expected Username does not match.");
- var nt = HexEncode (type3.NT);
+ var nt = HexEncode (type3.NtChallengeResponse);
Assert.AreEqual ("dd5428b01e86f4dfcabeac394946dbd43ee88f794dd63255", nt, "The NT payload does not match.");
- var lm = HexEncode (type3.LM);
+ var lm = HexEncode (type3.LmChallengeResponse);
Assert.AreEqual ("98def7b87f88aa5dafe2df779688a172def11c7d5ccdef13", lm, "The LM payload does not match.");
}
static void AssertNtlmAuthNoDomain (SaslMechanismNtlm sasl, string prefix)
{
string challenge;
- byte [] decoded;
Assert.IsTrue (sasl.SupportsInitialResponse, "{0}: SupportsInitialResponse", prefix);
challenge = sasl.Challenge (string.Empty);
- decoded = Convert.FromBase64String (challenge);
- var type1 = new Type1Message (decoded, 0, decoded.Length);
+ var type1 = DecodeType1Message (challenge);
Assert.AreEqual (Type1Message.DefaultFlags, type1.Flags, "{0}: Expected initial NTLM client challenge flags do not match.", prefix);
Assert.AreEqual (string.Empty, type1.Domain, "{0}: Expected initial NTLM client challenge domain does not match.", prefix);
@@ -353,11 +436,11 @@ static void AssertNtlmAuthNoDomain (SaslMechanismNtlm sasl, string prefix)
public void TestNtlmAuthNoDomain ()
{
var credentials = new NetworkCredential ("username", "password");
- var sasl = new SaslMechanismNtlm (credentials);
+ var sasl = new SaslMechanismNtlm (credentials) { OSVersion = null, Workstation = null };
AssertNtlmAuthNoDomain (sasl, "NetworkCredential");
- sasl = new SaslMechanismNtlm ("username", "password");
+ sasl = new SaslMechanismNtlm ("username", "password") { OSVersion = null, Workstation = null };
AssertNtlmAuthNoDomain (sasl, "user/pass");
}
@@ -366,14 +449,12 @@ static void AssertNtlmAuthWithDomain (SaslMechanismNtlm sasl, string prefix)
{
var initialFlags = Type1Message.DefaultFlags | NtlmFlags.NegotiateDomainSupplied;
string challenge;
- byte [] decoded;
Assert.IsTrue (sasl.SupportsInitialResponse, "{0}: SupportsInitialResponse", prefix);
challenge = sasl.Challenge (string.Empty);
- decoded = Convert.FromBase64String (challenge);
- var type1 = new Type1Message (decoded, 0, decoded.Length);
+ var type1 = DecodeType1Message (challenge);
Assert.AreEqual (initialFlags, type1.Flags, "{0}: Expected initial NTLM client challenge flags do not match.", prefix);
Assert.AreEqual ("DOMAIN", type1.Domain, "{0}: Expected initial NTLM client challenge domain does not match.", prefix);
@@ -385,328 +466,245 @@ static void AssertNtlmAuthWithDomain (SaslMechanismNtlm sasl, string prefix)
public void TestNtlmAuthWithDomain ()
{
var credentials = new NetworkCredential ("domain\\username", "password");
- var sasl = new SaslMechanismNtlm (credentials);
+ var sasl = new SaslMechanismNtlm (credentials) { OSVersion = null, Workstation = null };
AssertNtlmAuthWithDomain (sasl, "NetworkCredential");
- sasl = new SaslMechanismNtlm ("domain\\username", "password");
+ sasl = new SaslMechanismNtlm ("domain\\username", "password") { OSVersion = null, Workstation = null };
AssertNtlmAuthWithDomain (sasl, "user/pass");
}
- static Type1Message DecodeType1Message (string token)
- {
- var message = Convert.FromBase64String (token);
-
- return new Type1Message (message, 0, message.Length);
- }
-
- static Type2Message DecodeType2Message (string token)
- {
- var message = Convert.FromBase64String (token);
-
- return new Type2Message (message, 0, message.Length);
- }
-
- static Type3Message DecodeType3Message (string token)
- {
- var message = Convert.FromBase64String (token);
-
- return new Type3Message (message, 0, message.Length);
- }
-
- static void AssertLmAndNtlm (SaslMechanismNtlm sasl, string challenge1, string challenge2, string challenge3)
+ static void AssertNtlmv2 (SaslMechanismNtlm sasl, string challenge1, string challenge2)
{
var challenge = sasl.Challenge (string.Empty);
+ var timestamp = DateTime.UtcNow.Ticks;
+ var nonce = NtlmUtils.NONCE (8);
- Assert.AreEqual (challenge1, challenge, "Initial challenge");
- Assert.IsFalse (sasl.IsAuthenticated, "IsAuthenticated");
-
- challenge = sasl.Challenge (challenge2);
-
- Assert.AreEqual (challenge3, challenge, "Final challenge");
- Assert.IsTrue (sasl.IsAuthenticated, "IsAuthenticated");
- }
-
- [Test]
- public void TestAuthenticationLmAndNtlm ()
- {
- const string challenge1 = "TlRMTVNTUAABAAAABwIAAAAAAAAgAAAAAAAAACAAAAA=";
- const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
- const string challenge3 = "TlRMTVNTUAADAAAAGAAYAFQAAAAYABgAbAAAAAwADABAAAAACAAIAEwAAAAAAAAAVAAAAAAAAACEAAAAAQKAAEQATwBNAEEASQBOAHUAcwBlAHIAmN73uH+Iql2v4t93loihct7xHH1cze8T3VQosB6G9N/Kvqw5SUbb1D7oj3lN1jJV";
-
- var credentials = new NetworkCredential ("user", "password");
- var sasl = new SaslMechanismNtlm (credentials) { Level = NtlmAuthLevel.LM_and_NTLM };
-
- AssertLmAndNtlm (sasl, challenge1, challenge2, challenge3);
- }
-
- [Test]
- public void TestAuthenticationLmAndNtlmWithDomain ()
- {
- const string challenge1 = "TlRMTVNTUAABAAAABxIAAAYABgAgAAAAAAAAACAAAABET01BSU4=";
- const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
- const string challenge3 = "TlRMTVNTUAADAAAAGAAYAFQAAAAYABgAbAAAAAwADABAAAAACAAIAEwAAAAAAAAAVAAAAAAAAACEAAAAAQKAAEQATwBNAEEASQBOAHUAcwBlAHIAmN73uH+Iql2v4t93loihct7xHH1cze8T3VQosB6G9N/Kvqw5SUbb1D7oj3lN1jJV";
- var credentials = new NetworkCredential ("user", "password", "DOMAIN");
- var sasl = new SaslMechanismNtlm (credentials) { Level = NtlmAuthLevel.LM_and_NTLM };
-
- AssertLmAndNtlm (sasl, challenge1, challenge2, challenge3);
- }
-
- [Test]
- public void TestAuthenticationLmAndNtlmWithDomainAndWorkstation ()
- {
- const string challenge1 = "TlRMTVNTUAABAAAABzIAAAYABgArAAAACwALACAAAABXT1JLU1RBVElPTkRPTUFJTg==";
- const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
- const string challenge3 = "TlRMTVNTUAADAAAAGAAYAGoAAAAYABgAggAAAAwADABAAAAACAAIAEwAAAAWABYAVAAAAAAAAACaAAAAAQKAAEQATwBNAEEASQBOAHUAcwBlAHIAVwBPAFIASwBTAFQAQQBUAEkATwBOAJje97h/iKpdr+Lfd5aIoXLe8Rx9XM3vE91UKLAehvTfyr6sOUlG29Q+6I95TdYyVQ==";
- var credentials = new NetworkCredential ("user", "password", "DOMAIN");
- var sasl = new SaslMechanismNtlm (credentials) { Workstation = "WORKSTATION", Level = NtlmAuthLevel.LM_and_NTLM };
-
- AssertLmAndNtlm (sasl, challenge1, challenge2, challenge3);
- }
-
- [Test]
- public void TestAuthenticationLmAndNtlmSessionFallback ()
- {
- // Note: this will fallback to LN_and_NTLM because the type2 message does not contain the NegotiateNtlm2Key flag
- const string challenge1 = "TlRMTVNTUAABAAAABwIAAAAAAAAgAAAAAAAAACAAAAA=";
- const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
- const string challenge3 = "TlRMTVNTUAADAAAAGAAYAFQAAAAYABgAbAAAAAwADABAAAAACAAIAEwAAAAAAAAAVAAAAAAAAACEAAAAAQKAAEQATwBNAEEASQBOAHUAcwBlAHIAmN73uH+Iql2v4t93loihct7xHH1cze8T3VQosB6G9N/Kvqw5SUbb1D7oj3lN1jJV";
-
- var credentials = new NetworkCredential ("user", "password");
- var sasl = new SaslMechanismNtlm (credentials) { Level = NtlmAuthLevel.LM_and_NTLM_and_try_NTLMv2_Session };
-
- AssertLmAndNtlm (sasl, challenge1, challenge2, challenge3);
- }
-
- [Test]
- public void TestAuthenticationLmAndNtlmSessionFallbackWithDomain ()
- {
- // Note: this will fallback to LN_and_NTLM because the type2 message does not contain the NegotiateNtlm2Key flag
- const string challenge1 = "TlRMTVNTUAABAAAABxIAAAYABgAgAAAAAAAAACAAAABET01BSU4=";
- const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
- const string challenge3 = "TlRMTVNTUAADAAAAGAAYAFQAAAAYABgAbAAAAAwADABAAAAACAAIAEwAAAAAAAAAVAAAAAAAAACEAAAAAQKAAEQATwBNAEEASQBOAHUAcwBlAHIAmN73uH+Iql2v4t93loihct7xHH1cze8T3VQosB6G9N/Kvqw5SUbb1D7oj3lN1jJV";
- var credentials = new NetworkCredential ("user", "password", "DOMAIN");
- var sasl = new SaslMechanismNtlm (credentials) { Level = NtlmAuthLevel.LM_and_NTLM_and_try_NTLMv2_Session };
-
- AssertLmAndNtlm (sasl, challenge1, challenge2, challenge3);
- }
-
- [Test]
- public void TestAuthenticationLmAndNtlmSessionFallbackWithDomainAndWorkstation ()
- {
- // Note: this will fallback to LN_and_NTLM because the type2 message does not contain the NegotiateNtlm2Key flag
- const string challenge1 = "TlRMTVNTUAABAAAABzIAAAYABgArAAAACwALACAAAABXT1JLU1RBVElPTkRPTUFJTg==";
- const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
- const string challenge3 = "TlRMTVNTUAADAAAAGAAYAGoAAAAYABgAggAAAAwADABAAAAACAAIAEwAAAAWABYAVAAAAAAAAACaAAAAAQKAAEQATwBNAEEASQBOAHUAcwBlAHIAVwBPAFIASwBTAFQAQQBUAEkATwBOAJje97h/iKpdr+Lfd5aIoXLe8Rx9XM3vE91UKLAehvTfyr6sOUlG29Q+6I95TdYyVQ==";
- var credentials = new NetworkCredential ("user", "password", "DOMAIN");
- var sasl = new SaslMechanismNtlm (credentials) { Workstation = "WORKSTATION", Level = NtlmAuthLevel.LM_and_NTLM_and_try_NTLMv2_Session };
-
- AssertLmAndNtlm (sasl, challenge1, challenge2, challenge3);
- }
-
- static void AssertNtlm2Key (SaslMechanismNtlm sasl, string challenge1, string challenge2)
- {
- var challenge = sasl.Challenge (string.Empty);
+ sasl.Timestamp = timestamp;
+ sasl.Nonce = nonce;
Assert.AreEqual (challenge1, challenge, "Initial challenge");
Assert.IsFalse (sasl.IsAuthenticated, "IsAuthenticated");
challenge = sasl.Challenge (challenge2);
- var token = Convert.FromBase64String (challenge2);
- var type2 = new Type2Message (token, 0, token.Length);
- var type3 = new Type3Message (type2, null, sasl.Level, sasl.Credentials.UserName, sasl.Credentials.Password, sasl.Workstation);
- var ignoreLength = 48;
+ var type1 = DecodeType1Message (challenge1);
+ var type2 = DecodeType2Message (challenge2);
+ var type3 = new Type3Message (type1, type2, sasl.Credentials.UserName, sasl.Credentials.Password, sasl.Workstation) {
+ ClientChallenge = nonce,
+ Timestamp = timestamp
+ };
+ type3.ComputeNtlmV2 (null, false, null);
var actual = Convert.FromBase64String (challenge);
var expected = type3.Encode ();
Assert.AreEqual (expected.Length, actual.Length, "Final challenge differs in length: {0} vs {1}", expected.Length, actual.Length);
- for (int i = 0; i < expected.Length - ignoreLength; i++)
+ for (int i = 0; i < expected.Length; i++)
Assert.AreEqual (expected[i], actual[i], "Final challenge differs at index {0}", i);
Assert.IsTrue (sasl.IsAuthenticated, "IsAuthenticated");
}
[Test]
- public void TestAuthenticationLmAndNtlmSessionNegotiateNtlm2Key ()
+ public void TestAuthenticationNtlmv2 ()
{
- const string challenge1 = "TlRMTVNTUAABAAAABwIAAAAAAAAgAAAAAAAAACAAAAA=";
- const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAokAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
+ const string challenge1 = "TlRMTVNTUAABAAAAB4IIoAAAAAAgAAAAAAAAACAAAAA=";
+ const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
var credentials = new NetworkCredential ("user", "password");
- var sasl = new SaslMechanismNtlm (credentials) { Level = NtlmAuthLevel.LM_and_NTLM_and_try_NTLMv2_Session };
+ var sasl = new SaslMechanismNtlm (credentials) { OSVersion = null, Workstation = null };
- AssertNtlm2Key (sasl, challenge1, challenge2);
+ AssertNtlmv2 (sasl, challenge1, challenge2);
}
[Test]
- public void TestAuthenticationLmAndNtlmSessionNegotiateNtlm2KeyWithDomain ()
+ public void TestAuthenticationNtlmv2WithDomain ()
{
- const string challenge1 = "TlRMTVNTUAABAAAABxIAAAYABgAgAAAAAAAAACAAAABET01BSU4=";
- const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAokAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
+ const string challenge1 = "TlRMTVNTUAABAAAAB5IIoAYABgAgAAAAAAAAACAAAABET01BSU4=";
+ const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
var credentials = new NetworkCredential ("user", "password", "DOMAIN");
- var sasl = new SaslMechanismNtlm (credentials) { Level = NtlmAuthLevel.LM_and_NTLM_and_try_NTLMv2_Session };
+ var sasl = new SaslMechanismNtlm (credentials) { OSVersion = null, Workstation = null };
- AssertNtlm2Key (sasl, challenge1, challenge2);
+ AssertNtlmv2 (sasl, challenge1, challenge2);
}
[Test]
- public void TestAuthenticationLmAndNtlmSessionNegotiateNtlm2KeyWithDomainAndWorkstation ()
+ public void TestAuthenticationNtlmv2WithDomainAndWorkstation ()
{
- const string challenge1 = "TlRMTVNTUAABAAAABzIAAAYABgArAAAACwALACAAAABXT1JLU1RBVElPTkRPTUFJTg==";
- const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAokAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
+ const string challenge1 = "TlRMTVNTUAABAAAAB7IIoAYABgArAAAACwALACAAAABXT1JLU1RBVElPTkRPTUFJTg==";
+ const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
var credentials = new NetworkCredential ("user", "password", "DOMAIN");
- var sasl = new SaslMechanismNtlm (credentials) { Workstation = "WORKSTATION", Level = NtlmAuthLevel.LM_and_NTLM_and_try_NTLMv2_Session };
+ var sasl = new SaslMechanismNtlm (credentials) { OSVersion = null, Workstation = "WORKSTATION" };
- AssertNtlm2Key (sasl, challenge1, challenge2);
+ AssertNtlmv2 (sasl, challenge1, challenge2);
}
- [Test]
- public void TestAuthenticationNtlmNegotiateNtlm2Key ()
- {
- const string challenge1 = "TlRMTVNTUAABAAAABwIAAAAAAAAgAAAAAAAAACAAAAA=";
- const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAokAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
- var credentials = new NetworkCredential ("user", "password");
- var sasl = new SaslMechanismNtlm (credentials) { Level = NtlmAuthLevel.NTLM_only };
-
- AssertNtlm2Key (sasl, challenge1, challenge2);
- }
+ // From Section 4.2.4.3
+ static byte[] ExampleNtlmV2ChallengeMessage = new byte[] {
+ 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00,
+ 0x38, 0x00, 0x00, 0x00, 0x33, 0x82, 0x8a, 0xe2, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x24, 0x00, 0x44, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x70, 0x17, 0x00, 0x00, 0x00, 0x0f, 0x53, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00,
+ 0x65, 0x00, 0x72, 0x00, 0x02, 0x00, 0x0c, 0x00, 0x44, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x01, 0x00, 0x0c, 0x00, 0x53, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00,
+ 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
- [Test]
- public void TestAuthenticationNtlmNegotiateNtlm2KeyWithDomain ()
- {
- const string challenge1 = "TlRMTVNTUAABAAAABxIAAAYABgAgAAAAAAAAACAAAABET01BSU4=";
- const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAokAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
- var credentials = new NetworkCredential ("user", "password", "DOMAIN");
- var sasl = new SaslMechanismNtlm (credentials) { Level = NtlmAuthLevel.NTLM_only };
+ // From Section 4.2.4.3
+ static byte[] ExampleNtlmV2AuthenticateMessageOriginal = new byte[] {
+ 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x03, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00,
+ 0x6c, 0x00, 0x00, 0x00, 0x54, 0x00, 0x54, 0x00, 0x84, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00,
+ 0x48, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x54, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00,
+ 0x5c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x35, 0x82, 0x88, 0xe2,
+ 0x05, 0x01, 0x28, 0x0a, 0x00, 0x00, 0x00, 0x0f, 0x44, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x55, 0x00, 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x43, 0x00, 0x4f, 0x00,
+ 0x4d, 0x00, 0x50, 0x00, 0x55, 0x00, 0x54, 0x00, 0x45, 0x00, 0x52, 0x00, 0x86, 0xc3, 0x50, 0x97,
+ 0xac, 0x9c, 0xec, 0x10, 0x25, 0x54, 0x76, 0x4a, 0x57, 0xcc, 0xcc, 0x19, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0x68, 0xcd, 0x0a, 0xb8, 0x51, 0xe5, 0x1c, 0x96, 0xaa, 0xbc, 0x92, 0x7b,
+ 0xeb, 0xef, 0x6a, 0x1c, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x0c, 0x00, 0x44, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00,
+ 0x01, 0x00, 0x0c, 0x00, 0x53, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc5, 0xda, 0xd2, 0x54, 0x4f, 0xc9, 0x79, 0x90,
+ 0x94, 0xce, 0x1c, 0xe9, 0x0b, 0xc9, 0xd0, 0x3e
+ };
- AssertNtlm2Key (sasl, challenge1, challenge2);
- }
+ // This is the modified version that includes the ChannelBinding=Z16 and TargetName="" string in the TargetInfo embedded in the NTChallengeResponse.
+ static readonly byte[] ExampleNtlmV2AuthenticateMessage = {
+ 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00, 0x03, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00,
+ 0x6c, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x84, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00,
+ 0x48, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x54, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00,
+ 0x5c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x35, 0x82, 0x88, 0xe2,
+ 0x05, 0x01, 0x28, 0x0a, 0x00, 0x00, 0x00, 0x0f, 0x44, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x55, 0x00, 0x73, 0x00, 0x65, 0x00, 0x72, 0x00, 0x43, 0x00, 0x4f, 0x00,
+ 0x4d, 0x00, 0x50, 0x00, 0x55, 0x00, 0x54, 0x00, 0x45, 0x00, 0x52, 0x00, 0x86, 0xc3, 0x50, 0x97,
+ 0xac, 0x9c, 0xec, 0x10, 0x25, 0x54, 0x76, 0x4a, 0x57, 0xcc, 0xcc, 0x19, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xf3, 0x37, 0xab, 0x75, 0x7a, 0xbc, 0x12, 0xf7, 0x68, 0x10, 0x55, 0x60,
+ 0xb4, 0xcb, 0x30, 0x17, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x0c, 0x00, 0x44, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00,
+ 0x01, 0x00, 0x0c, 0x00, 0x53, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
- [Test]
- public void TestAuthenticationNtlmNegotiateNtlm2KeyWithDomainAndWorkstation ()
+ static NtlmTargetInfo GetNtChallengeResponseTargetInfo (byte[] ntChallengeResponse)
{
- const string challenge1 = "TlRMTVNTUAABAAAABzIAAAYABgArAAAACwALACAAAABXT1JLU1RBVElPTkRPTUFJTg==";
- const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAokAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
- var credentials = new NetworkCredential ("user", "password", "DOMAIN");
- var sasl = new SaslMechanismNtlm (credentials) { Workstation = "WORKSTATION", Level = NtlmAuthLevel.NTLM_only };
+ int index = 0;
- AssertNtlm2Key (sasl, challenge1, challenge2);
- }
-
- static void AssertNtlm (SaslMechanismNtlm sasl, string challenge1, string challenge2, string challenge3)
- {
- var challenge = sasl.Challenge (string.Empty);
+ // Proof (16-bytes) HMACMD5 of the following data
+ index += 16;
- Assert.AreEqual (challenge1, challenge, "Initial challenge");
- Assert.IsFalse (sasl.IsAuthenticated, "IsAuthenticated");
+ // 2 bytes of version info
+ index += 2;
- challenge = sasl.Challenge (challenge2);
+ // Z6
+ index += 6;
- Assert.AreEqual (challenge3, challenge, "Final challenge");
- Assert.IsTrue (sasl.IsAuthenticated, "IsAuthenticated");
- }
+ // Timestamp
+ index += 8;
- [Test]
- public void TestAuthenticationNtlm ()
- {
- const string challenge1 = "TlRMTVNTUAABAAAABwIAAAAAAAAgAAAAAAAAACAAAAA=";
- const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
- const string challenge3 = "TlRMTVNTUAADAAAAAAAAAFQAAAAYABgAVAAAAAwADABAAAAACAAIAEwAAAAAAAAAVAAAAAAAAABsAAAAAQKAAEQATwBNAEEASQBOAHUAcwBlAHIA3VQosB6G9N/Kvqw5SUbb1D7oj3lN1jJV";
- var credentials = new NetworkCredential ("user", "password");
- var sasl = new SaslMechanismNtlm (credentials) { Level = NtlmAuthLevel.NTLM_only };
+ // ClientChallenge
+ index += 8;
- AssertNtlm (sasl, challenge1, challenge2, challenge3);
- }
+ // Z4
+ index += 4;
- [Test]
- public void TestAuthenticationNtlmWithDomain ()
- {
- const string challenge1 = "TlRMTVNTUAABAAAABxIAAAYABgAgAAAAAAAAACAAAABET01BSU4=";
- const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
- const string challenge3 = "TlRMTVNTUAADAAAAAAAAAFQAAAAYABgAVAAAAAwADABAAAAACAAIAEwAAAAAAAAAVAAAAAAAAABsAAAAAQKAAEQATwBNAEEASQBOAHUAcwBlAHIA3VQosB6G9N/Kvqw5SUbb1D7oj3lN1jJV";
- var credentials = new NetworkCredential ("user", "password", "DOMAIN");
- var sasl = new SaslMechanismNtlm (credentials) { Level = NtlmAuthLevel.NTLM_only };
+ // TargetInfo followed by Z4
+ int targetInfoLength = (ntChallengeResponse.Length - 4) - index;
- AssertNtlm (sasl, challenge1, challenge2, challenge3);
+ return new NtlmTargetInfo (ntChallengeResponse, index, targetInfoLength, true);
}
[Test]
- public void TestAuthenticationNtlmWithDomainAndWorkstation ()
- {
- const string challenge1 = "TlRMTVNTUAABAAAABzIAAAYABgArAAAACwALACAAAABXT1JLU1RBVElPTkRPTUFJTg==";
- const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
- const string challenge3 = "TlRMTVNTUAADAAAAAAAAAGoAAAAYABgAagAAAAwADABAAAAACAAIAEwAAAAWABYAVAAAAAAAAACCAAAAAQKAAEQATwBNAEEASQBOAHUAcwBlAHIAVwBPAFIASwBTAFQAQQBUAEkATwBOAN1UKLAehvTfyr6sOUlG29Q+6I95TdYyVQ==";
- var credentials = new NetworkCredential ("user", "password", "DOMAIN");
- var sasl = new SaslMechanismNtlm (credentials) { Workstation = "WORKSTATION", Level = NtlmAuthLevel.NTLM_only };
-
- AssertNtlm (sasl, challenge1, challenge2, challenge3);
- }
-
- static void AssertNtlmv2 (SaslMechanismNtlm sasl, string challenge1, string challenge2)
- {
- var challenge = sasl.Challenge (string.Empty);
-
- Assert.AreEqual (challenge1, challenge, "Initial challenge");
- Assert.IsFalse (sasl.IsAuthenticated, "IsAuthenticated");
-
- challenge = sasl.Challenge (challenge2);
-
- var token = Convert.FromBase64String (challenge2);
- var type2 = new Type2Message (token, 0, token.Length);
- var type3 = new Type3Message (type2, null, sasl.Level, sasl.Credentials.UserName, sasl.Credentials.Password, sasl.Workstation);
- var ignoreLength = type2.EncodedTargetInfo.Length + 28 + 16;
-
- var actual = Convert.FromBase64String (challenge);
- var expected = type3.Encode ();
- var ntlmBufferIndex = expected.Length - ignoreLength;
- var targetInfoIndex = ntlmBufferIndex + 16 /* md5 hash */ + 28;
-
- Assert.AreEqual (expected.Length, actual.Length, "Final challenge differs in length: {0} vs {1}", expected.Length, actual.Length);
-
- for (int i = 0; i < expected.Length - ignoreLength; i++)
- Assert.AreEqual (expected[i], actual[i], "Final challenge differs at index {0}", i);
-
- // now compare the TargetInfo blobs
- for (int i = targetInfoIndex; i < expected.Length; i++)
- Assert.AreEqual (expected[i], actual[i], "Final challenge differs at index {0}", i);
-
- Assert.IsTrue (sasl.IsAuthenticated, "IsAuthenticated");
- }
+ public void TestNtlmv2Example ()
+ {
+ var flags = NtlmFlags.RequestTarget | NtlmFlags.NegotiateKeyExchange | NtlmFlags.Negotiate56 | NtlmFlags.Negotiate128 | NtlmFlags.NegotiateVersion | NtlmFlags.NegotiateTargetInfo | NtlmFlags.NegotiateExtendedSessionSecurity |
+ NtlmFlags.NegotiateAlwaysSign | NtlmFlags.NegotiateNtlm | NtlmFlags.NegotiateSeal | NtlmFlags.NegotiateSign | NtlmFlags.NegotiateOem | NtlmFlags.NegotiateUnicode;
+ var type1 = new Type1Message (flags, "", "", new Version (5, 1, 2600));
+
+ var type2 = new Type2Message (ExampleNtlmV2ChallengeMessage, 0, ExampleNtlmV2ChallengeMessage.Length);
+ Assert.AreEqual ("Server", type2.TargetName, "TargetName");
+ Assert.AreEqual ("Server", type2.TargetInfo.ServerName, "ServerName");
+ Assert.AreEqual ("Domain", type2.TargetInfo.DomainName, "DomainName");
+
+ // Note: Had to reverse engineer these values from the example. The nonce is the last 8 bytes of the lmChallengeResponse
+ // and the timestamp was bytes 8-16 of the 'temp' buffer.
+ var nonce = new byte[] { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa };
+ var timestamp = DateTime.FromFileTimeUtc (0).Ticks;
+
+ var expectedType3 = new Type3Message (ExampleNtlmV2AuthenticateMessage, 0, ExampleNtlmV2AuthenticateMessage.Length);
+ var expectedTargetInfo = GetNtChallengeResponseTargetInfo (expectedType3.NtChallengeResponse);
+ var type3 = new Type3Message (type1, type2, "User", "Password", "COMPUTER") {
+ ClientChallenge = nonce,
+ Timestamp = timestamp
+ };
+ type3.ComputeNtlmV2 (null, false, null);
- [Test]
- public void TestAuthenticationNtlmv2 ()
- {
- const string challenge1 = "TlRMTVNTUAABAAAABwIAAAAAAAAgAAAAAAAAACAAAAA=";
- const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
- var credentials = new NetworkCredential ("user", "password");
- var sasl = new SaslMechanismNtlm (credentials) { Level = NtlmAuthLevel.NTLMv2_only };
+ var actualTargetInfo = GetNtChallengeResponseTargetInfo (type3.NtChallengeResponse);
+ var actual = type3.Encode ();
- AssertNtlmv2 (sasl, challenge1, challenge2);
- }
+ //var initializer = ToCSharpByteArrayInitializer ("ExampleNtlmV2AuthenticateMessage", actual);
- [Test]
- public void TestAuthenticationNtlmv2WithDomain ()
- {
- const string challenge1 = "TlRMTVNTUAABAAAABxIAAAYABgAgAAAAAAAAACAAAABET01BSU4=";
- const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
- var credentials = new NetworkCredential ("user", "password", "DOMAIN");
- var sasl = new SaslMechanismNtlm (credentials) { Level = NtlmAuthLevel.NTLMv2_only };
+ Assert.AreEqual (ExampleNtlmV2AuthenticateMessage.Length, actual.Length, "Raw message lengths differ.");
- AssertNtlmv2 (sasl, challenge1, challenge2);
+ /// Note: The EncryptedRandomSessionKey is random and is the last 16 bytes of the message.
+ for (int i = 0; i < ExampleNtlmV2AuthenticateMessage.Length - 16; i++)
+ Assert.AreEqual (ExampleNtlmV2AuthenticateMessage[i], actual[i], $"Messages differ at index [{i}]");
}
[Test]
- public void TestAuthenticationNtlmv2WithDomainAndWorkstation ()
- {
- const string challenge1 = "TlRMTVNTUAABAAAABzIAAAYABgArAAAACwALACAAAABXT1JLU1RBVElPTkRPTUFJTg==";
- const string challenge2 = "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
- var credentials = new NetworkCredential ("user", "password", "DOMAIN");
- var sasl = new SaslMechanismNtlm (credentials) { Workstation = "WORKSTATION", Level = NtlmAuthLevel.NTLMv2_only };
-
- AssertNtlmv2 (sasl, challenge1, challenge2);
+ public void TestSystemNetMailNtlmNegotiation ()
+ {
+ const string challenge1 = "TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAKAO5CAAAADw==";
+ const string challenge2 = "TlRMTVNTUAACAAAADAAMADgAAAAFgomi18THmUUjMM4AAAAAAAAAAMoAygBEAAAABgOAJQAAAA9EAEUAVgBEAEkAVgACAAwARABFAFYARABJAFYAAQAQAEUAWABDAEgAQQBOAEcARQAEACgAZABlAHYAZABpAHYALgBtAGkAYwByAG8AcwBvAGYAdAAuAGMAbwBtAAMAOgBlAHgAYwBoAGEAbgBnAGUALgBkAGUAdgBkAGkAdgAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ABQAoAGQAZQB2AGQAaQB2AC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAHAAgAS9aGGn7B1gEAAAAATlRMTVNTUAACAAAADAAMADgAAAAFgomi18THmUUjMM4AAAAAAAAAAMoAygBEAAAABgOAJQAAAA9EAEUAVgBEAEkAVgACAAwARABFAFYARABJAFYAAQAQAEUAWABDAEgAQQBOAEcARQAEACgAZABlAHYAZABpAHYALgBtAGkAYwByAG8AcwBvAGYAdAAuAGMAbwBtAAMAOgBlAHgAYwBoAGEAbgBnAGUALgBkAGUAdgBkAGkAdgAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ABQAoAGQAZQB2AGQAaQB2AC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAHAAgAS9aGGn7B1gEAAAAA";
+ const string challenge3 = "TlRMTVNTUAADAAAAGAAYAIoAAABAAUABogAAAAwADABYAAAAEAAQAGQAAAAWABYAdAAAAAAAAADiAQAABYIIogoA7kIAAAAPDYFh2Vjzwk5e9YHnWRvYnUQARQBWAEQASQBWAHUAcwBlAHIAbgBhAG0AZQBXAG8AcgBrAHMAdABhAHQAaQBvAG4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbqz8wqlkKSdjmeI+rX9+lwEBAAAAAAAAS9aGGn7B1gGZ26Mto5srdQAAAAACAAwARABFAFYARABJAFYAAQAQAEUAWABDAEgAQQBOAEcARQAEACgAZABlAHYAZABpAHYALgBtAGkAYwByAG8AcwBvAGYAdAAuAGMAbwBtAAMAOgBlAHgAYwBoAGEAbgBnAGUALgBkAGUAdgBkAGkAdgAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ABQAoAGQAZQB2AGQAaQB2AC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAHAAgAS9aGGn7B1gEGAAQAAgAAAAkAJgBTAE0AVABQAFMAVgBDAC8AMQA5ADIALgAxADYAOAAuADEALgAxAAoAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
+ var type1 = DecodeType1Message (challenge1);
+ //var type2 = DecodeType2Message (challenge2);
+ var type3 = DecodeType3Message (challenge3);
+
+ // This is what System.Net.Mail sends as the initial challenge.
+ Assert.AreEqual (Type1Message.DefaultFlags | NtlmFlags.NegotiateAlwaysSign | NtlmFlags.NegotiateVersion | NtlmFlags.Negotiate56, type1.Flags, "System.Net.Mail Initial Flags");
+
+ var ntlm = new SaslMechanismNtlm ("username", "password") {
+ ServicePrincipalName = "SMTPSVC/192.168.1.1",
+ OSVersion = new Version (10, 0, 17134),
+ Workstation = "Workstation"
+ };
+ var challenge = ntlm.Challenge (null);
+
+ Assert.AreEqual ("TlRMTVNTUAABAAAAB4IIogAAAAAoAAAAAAAAACgAAAAKAO5CAAAADw==", challenge, "MailKit Initial Challenge");
+
+ challenge = ntlm.Challenge (challenge2);
+ var auth = DecodeType3Message (challenge);
+
+ //Assert.AreEqual (type3.Domain, auth.Domain, "Domain");
+ Assert.AreEqual (type3.UserName, auth.UserName, "UserName");
+ Assert.AreEqual (type3.Workstation, auth.Workstation, "Workstation");
+ Assert.AreEqual (type3.OSVersion, auth.OSVersion, "OSVersion");
+
+ Assert.AreEqual (type3.LmChallengeResponse.Length, auth.LmChallengeResponse.Length, "LmChallengeResponseLength");
+ for (int i = 0; i < auth.LmChallengeResponse.Length; i++)
+ Assert.AreEqual (0, auth.LmChallengeResponse[i], $"LmChallengeResponse[{i}]");
+ Assert.NotNull (auth.Mic, "Mic");
+ Assert.AreEqual (type3.Mic.Length, auth.Mic.Length, "Mic");
+
+ var targetInfo = GetNtChallengeResponseTargetInfo (auth.NtChallengeResponse);
+ var expected = GetNtChallengeResponseTargetInfo (type3.NtChallengeResponse);
+ Assert.NotNull (targetInfo.ChannelBinding, "ChannelBinding");
+ Assert.AreEqual (expected.ChannelBinding.Length, targetInfo.ChannelBinding.Length, "ChannelBinding");
+ Assert.AreEqual (expected.ServerName, targetInfo.ServerName, "ServerName");
+ Assert.AreEqual (expected.DomainName, targetInfo.DomainName, "DomainName");
+ Assert.AreEqual (expected.DnsServerName, targetInfo.DnsServerName, "DnsServerName");
+ Assert.AreEqual (expected.DnsDomainName, targetInfo.DnsDomainName, "DnsDomainName");
+ Assert.AreEqual (expected.DnsTreeName, targetInfo.DnsTreeName, "DnsTreeName");
+ Assert.AreEqual (expected.Flags, targetInfo.Flags, "Flags");
+ Assert.AreEqual (expected.Timestamp, targetInfo.Timestamp, "Timestamp");
+
+ Console.WriteLine ();
}
}
}
diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj
index 209f620472..1be06a9cb2 100644
--- a/UnitTests/UnitTests.csproj
+++ b/UnitTests/UnitTests.csproj
@@ -79,6 +79,12 @@
+
+
+
+
+
+