diff --git a/src/Common/src/Interop/Linux/System.Net.Security.Native/Interop.HeimdalNtlm.cs b/src/Common/src/Interop/Linux/System.Net.Security.Native/Interop.HeimdalNtlm.cs
new file mode 100644
index 000000000000..4241e15cb773
--- /dev/null
+++ b/src/Common/src/Interop/Linux/System.Net.Security.Native/Interop.HeimdalNtlm.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+ internal static class HeimdalNtlm
+ {
+ internal static byte[] CreateNegotiateMessage(uint flags)
+ {
+ SafeNtlmBufferHandle data;
+ int dataLen;
+ int status = NetSecurity.HeimNtlmEncodeType1(flags, out data, out dataLen);
+ NetSecurity.HeimdalNtlmException.AssertOrThrowIfError("HeimNtlmEncodeType1 failed", status);
+ using (data)
+ {
+ return data.ToByteArray(dataLen,0);
+ }
+ }
+
+ internal static byte[] CreateAuthenticateMessage(uint flags, string username, string password, string domain,
+ byte[] type2Data, int offset, int count, out byte[] sessionKey)
+ {
+ using (SafeNtlmType3Handle challengeMessage = new SafeNtlmType3Handle(type2Data, offset, count))
+ {
+ return challengeMessage.GetResponse(flags, username, password, domain, out sessionKey);
+ }
+ }
+
+ internal static void CreateKeys(byte[] sessionKey, out SafeNtlmKeyHandle serverSignKey, out SafeNtlmKeyHandle serverSealKey, out SafeNtlmKeyHandle clientSignKey, out SafeNtlmKeyHandle clientSealKey)
+ {
+ serverSignKey = new SafeNtlmKeyHandle(sessionKey, false, false);
+ serverSealKey = new SafeNtlmKeyHandle(sessionKey, false, true);
+ clientSignKey = new SafeNtlmKeyHandle(sessionKey, true, false);
+ clientSealKey = new SafeNtlmKeyHandle(sessionKey, true, true);
+ }
+ }
+}
+
diff --git a/src/Common/src/Interop/Linux/System.Net.Security.Native/Interop.HeimdalNtlmException.cs b/src/Common/src/Interop/Linux/System.Net.Security.Native/Interop.HeimdalNtlmException.cs
new file mode 100644
index 000000000000..d9f333514fa7
--- /dev/null
+++ b/src/Common/src/Interop/Linux/System.Net.Security.Native/Interop.HeimdalNtlmException.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class NetSecurity
+ {
+ internal sealed class HeimdalNtlmException : Exception
+ {
+ public HeimdalNtlmException(string message) : base(message)
+ {
+ }
+
+ public HeimdalNtlmException(int error)
+ : base(SR.Format(SR.net_generic_heimntlm_operation_failed, error))
+ {
+ HResult = error;
+ }
+
+ public static void AssertOrThrowIfError(string message, int error)
+ {
+ if (error != 0)
+ {
+ var ex = new HeimdalNtlmException(error);
+ Debug.Fail(message + ": " + ex);
+ throw ex;
+ }
+ }
+ }
+ }
+}
+
diff --git a/src/Common/src/Interop/Linux/System.Net.Security.Native/Interop.Ntlm.cs b/src/Common/src/Interop/Linux/System.Net.Security.Native/Interop.Ntlm.cs
new file mode 100644
index 000000000000..56847276c347
--- /dev/null
+++ b/src/Common/src/Interop/Linux/System.Net.Security.Native/Interop.Ntlm.cs
@@ -0,0 +1,78 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Text;
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+ internal static partial class NetSecurity
+ {
+ // The following constant is used in calculation of NTOWF2
+ // ref: https://msdn.microsoft.com/en-us/library/cc236700.aspx
+ public const int MD5DigestLength = 16;
+ [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_HeimNtlmFreeBuf")]
+ internal static extern int HeimNtlmFreeBuf(IntPtr bufferHandle);
+
+ [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_ExtractNtlmBuffer")]
+ internal static extern int ExtractNtlmBuffer(SafeNtlmBufferHandle data, byte[] buffer, int capacity, int offset);
+
+ [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_HeimNtlmEncodeType1")]
+ internal static extern int HeimNtlmEncodeType1(uint flags, out SafeNtlmBufferHandle data, out int length);
+
+ [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_HeimNtlmDecodeType2")]
+ internal static extern int HeimNtlmDecodeType2(byte[] data, int offset, int count, out SafeNtlmType2Handle type2Handle);
+
+ [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_HeimNtlmFreeType2")]
+ internal static extern int HeimNtlmFreeType2(IntPtr type2Handle);
+
+ [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_HeimNtlmNtKey", CharSet = CharSet.Ansi)]
+ internal static extern int HeimNtlmNtKey(string password, out SafeNtlmBufferHandle key, out int length);
+
+ [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_HeimNtlmCalculateResponse", CharSet = CharSet.Ansi)]
+ internal static extern int HeimNtlmCalculateResponse(
+ bool isLM,
+ SafeNtlmBufferHandle key,
+ SafeNtlmType2Handle type2Handle,
+ string username,
+ string target,
+ byte[] baseSessionKey,
+ int baseSessionKeyLen,
+ out SafeNtlmBufferHandle answer,
+ out int ansLength);
+
+ [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_CreateType3Message", CharSet = CharSet.Ansi)]
+ internal static extern int CreateType3Message(
+ SafeNtlmBufferHandle key,
+ SafeNtlmType2Handle type2Handle,
+ string username,
+ string domain,
+ uint flags,
+ SafeNtlmBufferHandle lmResponse,
+ SafeNtlmBufferHandle ntlmResponse,
+ byte [] baseSessionKey,
+ int baseSessionKeyLen,
+ out SafeNtlmBufferHandle sessionKey,
+ out int sessionKeyLen,
+ out SafeNtlmBufferHandle data,
+ out int dataLen
+ );
+
+ internal partial class NtlmFlags
+ {
+ internal const uint NTLMSSP_NEGOTIATE_UNICODE = 0x1;
+ internal const uint NTLMSSP_REQUEST_TARGET = 0x4;
+ internal const uint NTLMSSP_NEGOTIATE_SIGN = 0x10;
+ internal const uint NTLMSSP_NEGOTIATE_SEAL = 0x20;
+ internal const uint NTLMSSP_NEGOTIATE_NTLM = 0x200;
+ internal const uint NTLMSSP_NEGOTIATE_ALWAYS_SIGN = 0x8000;
+ internal const uint NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x80000;
+ internal const uint NTLMSSP_NEGOTIATE_128 = 0x20000000;
+ internal const uint NTLMSSP_NEGOTIATE_KEY_EXCH = 0x40000000;
+ }
+ }
+}
diff --git a/src/Common/src/Interop/Unix/Interop.Libraries.cs b/src/Common/src/Interop/Unix/Interop.Libraries.cs
index 8e5de4df736b..bd16abaec2bb 100644
--- a/src/Common/src/Interop/Unix/Interop.Libraries.cs
+++ b/src/Common/src/Interop/Unix/Interop.Libraries.cs
@@ -8,6 +8,7 @@ private static partial class Libraries
// Shims
internal const string SystemNative = "System.Native";
internal const string HttpNative = "System.Net.Http.Native";
+ internal const string NetSecurityNative = "System.Net.Security.Native";
internal const string CryptoNative = "System.Security.Cryptography.Native";
internal const string GlobalizationNative = "System.Globalization.Native";
internal const string CompressionNative = "System.IO.Compression.Native";
diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.Cipher.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.Cipher.cs
index 7e03d7d9a471..974d5eaf8537 100644
--- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.Cipher.cs
+++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.Cipher.cs
@@ -66,5 +66,16 @@ internal static extern unsafe bool EvpCipherFinalEx(
[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpDes3Cbc")]
internal static extern IntPtr EvpDes3Cbc();
+
+ [DllImport(Interop.Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpRc4")]
+ internal static extern IntPtr EvpRc4();
+
+ [DllImport(Interop.Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpCipher")]
+ internal static unsafe extern int EvpCipher(
+ SafeEvpCipherCtxHandle ctx,
+ byte[] output,
+ byte* input,
+ int inl);
+
}
}
diff --git a/src/Common/src/Microsoft/Win32/SafeHandles/NtlmSecuritySafeHandles.cs b/src/Common/src/Microsoft/Win32/SafeHandles/NtlmSecuritySafeHandles.cs
new file mode 100644
index 000000000000..d2ad88d0b653
--- /dev/null
+++ b/src/Common/src/Microsoft/Win32/SafeHandles/NtlmSecuritySafeHandles.cs
@@ -0,0 +1,271 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Security.Authentication;
+using System.Security.Authentication.ExtendedProtection;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+
+namespace Microsoft.Win32.SafeHandles
+{
+ ///
+ /// Wrapper around a ntlm_buf*
+ ///
+ internal sealed class SafeNtlmBufferHandle : SafeHandle
+ {
+ public SafeNtlmBufferHandle()
+ : base(IntPtr.Zero, true)
+ {
+ }
+
+ public byte[] ToByteArray(int length, int offset)
+ {
+ Debug.Assert(length >= 0, "negative length of buffer");
+ Debug.Assert(offset < length, "invalid offset for buffer");
+ byte[] target = new byte[length];
+ Interop.NetSecurity.ExtractNtlmBuffer(this, target, capacity: length, offset: offset);
+ return target;
+ }
+
+ public override bool IsInvalid
+ {
+ get { return handle == IntPtr.Zero; }
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ Interop.NetSecurity.HeimNtlmFreeBuf(handle);
+ SetHandle(IntPtr.Zero);
+ return true;
+ }
+ }
+
+ ///
+ /// Wrapper around a session key used for signing
+ ///
+ internal sealed class SafeNtlmKeyHandle : SafeHandle
+ {
+ private uint _sequenceNumber;
+ private readonly bool _isSealingKey;
+ private readonly byte[] _digest;
+ private SafeEvpCipherCtxHandle _cipherContext;
+
+ // From MS_NLMP SIGNKEY at https://msdn.microsoft.com/en-us/library/cc236711.aspx
+ private const string s_keyMagic = "session key to {0}-to-{1} {2} key magic constant\0";
+ private const string s_client = "client";
+ private const string s_server = "server";
+ private const string s_signing = "signing";
+ private const string s_sealing = "sealing";
+
+ public SafeNtlmKeyHandle(byte[] key, bool isClient, bool isSealingKey)
+ : base(IntPtr.Zero, true)
+ {
+ string keyMagic = string.Format(s_keyMagic, isClient ? s_client : s_server,
+ isClient ? s_server : s_client, isSealingKey ? s_sealing : s_signing);
+
+ using (IncrementalHash incremental = IncrementalHash.CreateHash(HashAlgorithmName.MD5))
+ {
+ incremental.AppendData(key);
+ incremental.AppendData(Encoding.UTF8.GetBytes(keyMagic));
+ _digest = incremental.GetHashAndReset();
+ }
+
+ _isSealingKey = isSealingKey;
+ if (_isSealingKey)
+ {
+ _cipherContext = Interop.Crypto.EvpCipherCreate(Interop.Crypto.EvpRc4(), _digest, null, 1);
+ }
+ }
+
+ public override bool IsInvalid
+ {
+ get { return true; }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (null != _cipherContext))
+ {
+ _cipherContext.Dispose();
+ _cipherContext = null;
+ }
+ base.Dispose(disposing);
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ return true;
+ }
+
+ public byte[] Sign(SafeNtlmKeyHandle sealingKey, byte[] buffer, int offset, int count)
+ {
+ Debug.Assert(!_isSealingKey, "Cannot sign with sealing key");
+ Debug.Assert(offset >= 0 && offset < buffer.Length, "Cannot sign with invalid offset " + offset);
+ Debug.Assert((count + offset) <= buffer.Length, "Cannot sign with invalid count " + count);
+
+ // reference for signing a message: https://msdn.microsoft.com/en-us/library/cc236702.aspx
+ const uint Version = 0x00000001;
+ const int ChecksumOffset = 4;
+ const int SequenceNumberOffset = 12;
+ const int HMacDigestLength = 8;
+
+ byte[] output = new byte[Interop.NetSecurity.MD5DigestLength];
+ Array.Clear(output, 0, output.Length);
+ MarshalUint(output, 0, Version); // version
+ MarshalUint(output, SequenceNumberOffset, _sequenceNumber);
+ byte[] hash;
+
+ using (var incremental = IncrementalHash.CreateHMAC(HashAlgorithmName.MD5, _digest))
+ {
+ incremental.AppendData(output, SequenceNumberOffset, ChecksumOffset);
+ incremental.AppendData(buffer, offset, count);
+ hash = incremental.GetHashAndReset();
+ _sequenceNumber++;
+ }
+
+ if (sealingKey == null)
+ {
+ Array.Copy(hash, 0, output, ChecksumOffset, HMacDigestLength);
+ }
+ else
+ {
+ byte[] cipher = sealingKey.SealOrUnseal(true, hash, 0, HMacDigestLength);
+ Array.Copy(cipher, 0, output, ChecksumOffset, cipher.Length);
+ }
+
+ return output;
+ }
+
+ public byte[] SealOrUnseal(bool seal, byte[] buffer, int offset, int count)
+ {
+ //Message Confidentiality. Reference: https://msdn.microsoft.com/en-us/library/cc236707.aspx
+ Debug.Assert(_isSealingKey, "Cannot seal or unseal with signing key");
+ Debug.Assert(offset >= 0 && offset < buffer.Length, "Cannot sign with invalid offset " + offset);
+ Debug.Assert((count + offset) <= buffer.Length, "Cannot sign with invalid count ");
+
+ unsafe
+ {
+ fixed (byte* bytePtr = buffer)
+ {
+ // Since RC4 is XOR-based, encrypt or decrypt is relative to input data
+ // reference: https://msdn.microsoft.com/en-us/library/cc236707.aspx
+ byte[] output = new byte[count];
+
+ Interop.Crypto.EvpCipher(_cipherContext, output, (bytePtr + offset), count);
+ return output;
+ }
+ }
+ }
+
+ private static void MarshalUint(byte[] ptr, int offset, uint num)
+ {
+ for (int i = 0; i < 4; i++)
+ {
+ ptr[offset + i] = (byte) (num & 0xff);
+ num >>= 8;
+ }
+ }
+ }
+
+ ///
+ /// Wrapper around a ntlm_type2*
+ ///
+ internal sealed class SafeNtlmType2Handle : SafeHandle
+ {
+ public override bool IsInvalid
+ {
+ get { return handle == IntPtr.Zero; }
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ Interop.NetSecurity.HeimNtlmFreeType2(handle);
+ SetHandle(IntPtr.Zero);
+ return true;
+ }
+
+ private SafeNtlmType2Handle() : base(IntPtr.Zero, true)
+ {
+ }
+ }
+
+ ///
+ /// Wrapper around a ntlm_type3*
+ ///
+ internal sealed class SafeNtlmType3Handle : SafeHandle
+ {
+ private SafeNtlmType2Handle _type2Handle;
+ public SafeNtlmType3Handle(byte[] type2Data, int offset, int count) : base(IntPtr.Zero, true)
+ {
+ int status = Interop.NetSecurity.HeimNtlmDecodeType2(type2Data, offset, count, out _type2Handle);
+ Interop.NetSecurity.HeimdalNtlmException.AssertOrThrowIfError("HeimNtlmDecodeType2 failed", status);
+ }
+
+ public override bool IsInvalid
+ {
+ get { return (null != _type2Handle) && !_type2Handle.IsInvalid; }
+ }
+
+ public byte[] GetResponse(uint flags, string username, string password, string domain,
+ out byte[] sessionKey)
+ {
+ // reference for NTLM response: https://msdn.microsoft.com/en-us/library/cc236700.aspx
+ sessionKey = null;
+ SafeNtlmBufferHandle key;
+ int keyLen;
+ int status = Interop.NetSecurity.HeimNtlmNtKey(password, out key, out keyLen);
+ Interop.NetSecurity.HeimdalNtlmException.AssertOrThrowIfError("HeimNtlmKey failed", status);
+
+ using (key)
+ {
+ byte[] baseSessionKey = new byte[Interop.NetSecurity.MD5DigestLength];
+ SafeNtlmBufferHandle lmResponse;
+ int lmResponseLength;
+
+ status = Interop.NetSecurity.HeimNtlmCalculateResponse(true, key, _type2Handle, username, domain,
+ baseSessionKey, baseSessionKey.Length, out lmResponse, out lmResponseLength);
+ Interop.NetSecurity.HeimdalNtlmException.AssertOrThrowIfError("HeimNtlmCalculateResponse lm1 failed",status);
+
+ SafeNtlmBufferHandle ntResponse;
+ int ntResponseLength;
+ status = Interop.NetSecurity.HeimNtlmCalculateResponse(false, key, _type2Handle, username, domain,
+ baseSessionKey, baseSessionKey.Length, out ntResponse, out ntResponseLength);
+ Interop.NetSecurity.HeimdalNtlmException.AssertOrThrowIfError("HeimNtlmCalculateResponse lm2 failed", status);
+
+ SafeNtlmBufferHandle sessionKeyHandle = null;
+ int sessionKeyLen;
+ SafeNtlmBufferHandle outputData = null;
+ int outputDataLen = 0;
+ status = Interop.NetSecurity.CreateType3Message(key, _type2Handle, username, domain, flags, lmResponse, ntResponse, baseSessionKey,
+ baseSessionKey.Length, out sessionKeyHandle, out sessionKeyLen, out outputData,
+ out outputDataLen);
+ Interop.NetSecurity.HeimdalNtlmException.AssertOrThrowIfError("CreateType3Message failed", status);
+ using (sessionKeyHandle)
+ using (outputData)
+ {
+ sessionKey = sessionKeyHandle.ToByteArray(sessionKeyLen,0);
+ return outputData.ToByteArray(outputDataLen, 0);
+ }
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _type2Handle.Dispose();
+ _type2Handle = null;
+ }
+ base.Dispose(disposing);
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ return true;
+ }
+ }
+}
diff --git a/src/Native/Common/pal_config.h.in b/src/Native/Common/pal_config.h.in
index ad7a5cc2869b..05e24573ac5c 100644
--- a/src/Native/Common/pal_config.h.in
+++ b/src/Native/Common/pal_config.h.in
@@ -46,6 +46,7 @@
#cmakedefine01 HAVE_TCP_H_TCPSTATE_ENUM
#cmakedefine01 HAVE_TCP_FSM_H
#cmakedefine01 HAVE_OPEN_MAX
+#cmakedefine01 HAVE_HEIMNTLM_HEADERS
// Mac OS X has stat64, but it is deprecated since plain stat now
// provides the same 64-bit aware struct when targeting OS X > 10.5
diff --git a/src/Native/System.Net.Security.Native/CMakeLists.txt b/src/Native/System.Net.Security.Native/CMakeLists.txt
new file mode 100644
index 000000000000..86b258ee0709
--- /dev/null
+++ b/src/Native/System.Net.Security.Native/CMakeLists.txt
@@ -0,0 +1,36 @@
+project(System.Net.Security.Native)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+add_definitions(-DPIC=1)
+
+if (HAVE_HEIMNTLM_HEADERS)
+ find_library(LIB_HEIMNTLM NAMES heimntlm PATHS /usr/heimdal/lib)
+ include_directories(/usr/heimdal/include)
+
+ if(LIB_HEIMNTLM STREQUAL LIB_HEIMNTLM-NOTFOUND)
+ message(FATAL_ERROR "Cannot find libheimntlm.so and System.Net.Security.Native cannot build without it. Try installing libheimntlm (or the appropriate package for your platform)")
+ endif()
+
+
+ set(NATIVENTLM_SOURCES
+ pal_ntlmapi.cpp
+ )
+
+ add_library(System.Net.Security.Native
+ SHARED
+ ${NATIVENTLM_SOURCES}
+ )
+
+ target_link_libraries(System.Net.Security.Native
+ ${LIB_HEIMNTLM}
+ ${OPENSSL_CRYPTO_LIBRARY}
+ )
+
+ install (TARGETS System.Net.Security.Native DESTINATION .)
+
+elseif (CMAKE_SYSTEM_NAME STREQUAL Linux)
+ message (FATAL_ERROR "cannot find libheim ntlm headers and System.Net.Security.Native cannot build without it. Try installing libheimntlm (or the appropriate package for your platform)")
+else ()
+ message(STATUS "Heimdal shims not needed for this platform")
+endif()
diff --git a/src/Native/System.Net.Security.Native/pal_ntlmapi.cpp b/src/Native/System.Net.Security.Native/pal_ntlmapi.cpp
new file mode 100644
index 000000000000..6994cbd84322
--- /dev/null
+++ b/src/Native/System.Net.Security.Native/pal_ntlmapi.cpp
@@ -0,0 +1,267 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#include
+#include
+#include
+#include "pal_types.h"
+#include "pal_ntlmapi.h"
+#include "pal_utilities.h"
+#include
+#include
+#include
+#include "heimntlm.h"
+#include "openssl/hmac.h"
+#include "openssl/evp.h"
+
+static_assert(PAL_NTLMSSP_NEGOTIATE_UNICODE == NTLM_NEG_UNICODE, "");
+static_assert(PAL_NTLMSSP_REQUEST_TARGET == NTLM_NEG_TARGET, "");
+static_assert(PAL_NTLMSSP_NEGOTIATE_SIGN == NTLM_NEG_SIGN, "");
+static_assert(PAL_NTLMSSP_NEGOTIATE_SEAL == NTLM_NEG_SEAL, "");
+static_assert(PAL_NTLMSSP_NEGOTIATE_NTLM == NTLM_NEG_NTLM, "");
+static_assert(PAL_NTLMSSP_NEGOTIATE_ALWAYS_SIGN == NTLM_NEG_ALWAYS_SIGN, "");
+static_assert(PAL_NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY == NTLM_NEG_NTLM2_SESSION, "");
+static_assert(PAL_NTLMSSP_NEGOTIATE_128 == NTLM_ENC_128, "");
+static_assert(PAL_NTLMSSP_NEGOTIATE_KEY_EXCH == NTLM_NEG_KEYEX, "");
+
+const int32_t MD5_DIGEST_LENGTH = 16;
+
+static inline int32_t NetSecurity_SetBufferLength(int32_t status, ntlm_buf** bufferHandle, int32_t* outLength)
+{
+ if (status != 0)
+ {
+ delete *bufferHandle;
+ *bufferHandle = nullptr;
+ *outLength = 0;
+ return status;
+ }
+ *outLength = static_cast((*bufferHandle)->length);
+ return status;
+}
+
+extern "C" void NetSecurity_HeimNtlmFreeBuf(ntlm_buf* data)
+{
+ assert(data != nullptr);
+ heim_ntlm_free_buf(data);
+ delete data;
+}
+
+extern "C" void NetSecurity_ExtractNtlmBuffer(const ntlm_buf* bufferHandle, uint8_t* destination, uint32_t capacity, uint32_t offset)
+{
+ if (bufferHandle == nullptr)
+ {
+ return;
+ }
+ assert(destination != nullptr);
+ assert(bufferHandle->length <= (capacity - offset));
+ memcpy(destination + UnsignedCast(offset), bufferHandle->data, bufferHandle->length);
+}
+
+extern "C" int32_t NetSecurity_HeimNtlmEncodeType1(uint32_t flags, ntlm_buf** outBufferHandle, int32_t* outLength)
+{
+ assert(outBufferHandle != nullptr);
+ ntlm_type1 type1;
+ memset(&type1, 0, sizeof(ntlm_type1));
+ type1.flags = flags;
+ *outBufferHandle = new ntlm_buf();
+ return NetSecurity_SetBufferLength(heim_ntlm_encode_type1(&type1, *outBufferHandle), outBufferHandle, outLength);
+}
+
+extern "C" int32_t NetSecurity_HeimNtlmDecodeType2(uint8_t* data, int32_t offset, int32_t count, ntlm_type2** type2)
+{
+ assert(data != nullptr);
+ assert(offset >= 0);
+ assert(type2 != nullptr);
+ ntlm_buf buffer{.length = UnsignedCast(count), .data = data + offset};
+ *type2 = new ntlm_type2();
+ int32_t stat = heim_ntlm_decode_type2(&buffer, *type2);
+ if (stat != 0)
+ {
+ delete *type2;
+ *type2 = nullptr;
+ }
+ return stat;
+}
+
+extern "C" void NetSecurity_HeimNtlmFreeType2(ntlm_type2* type2)
+{
+ assert(type2 != nullptr);
+ heim_ntlm_free_type2(type2);
+ delete type2;
+}
+
+extern "C" int32_t NetSecurity_HeimNtlmNtKey(const char* password, ntlm_buf** outBufferHandle, int32_t* outLength)
+{
+ assert(outBufferHandle != nullptr);
+ *outBufferHandle = new ntlm_buf();
+ return NetSecurity_SetBufferLength(heim_ntlm_nt_key(password, *outBufferHandle), outBufferHandle, outLength);
+}
+
+extern "C" int32_t NetSecurity_HeimNtlmCalculateResponse(int32_t isLM,
+ const ntlm_buf* key,
+ ntlm_type2* type2,
+ char* username,
+ char* target,
+ uint8_t* baseSessionKey,
+ int32_t baseSessionKeyLen,
+ ntlm_buf** outBufferHandle,
+ int32_t* outLength)
+{
+ // reference doc: http://msdn.microsoft.com/en-us/library/cc236700.aspx
+ assert(baseSessionKeyLen == MD5_DIGEST_LENGTH);
+ assert(isLM == 0 || isLM == 1);
+ assert(type2 != nullptr);
+ assert(key != nullptr);
+ assert(outBufferHandle != nullptr);
+ *outBufferHandle = new ntlm_buf();
+ if (isLM)
+ {
+ return NetSecurity_SetBufferLength(
+ heim_ntlm_calculate_lm2(
+ key->data, key->length, username, target, type2->challenge, baseSessionKey, *outBufferHandle),
+ outBufferHandle,
+ outLength);
+ }
+ else
+ {
+ if (type2->targetinfo.length == 0)
+ {
+ return NetSecurity_SetBufferLength(
+ heim_ntlm_calculate_ntlm1(key->data, key->length, type2->challenge, *outBufferHandle),
+ outBufferHandle,
+ outLength);
+ }
+ else
+ {
+ return NetSecurity_SetBufferLength(heim_ntlm_calculate_ntlm2(key->data,
+ key->length,
+ username,
+ target,
+ type2->challenge,
+ &type2->targetinfo,
+ baseSessionKey,
+ *outBufferHandle),
+ outBufferHandle,
+ outLength);
+ }
+ }
+}
+
+static uint8_t* NetSecurity_HMACDigest(uint8_t* key, int32_t keylen, void* input, size_t inputlen)
+{
+ HMAC_CTX ctx;
+ uint8_t* output = new uint8_t[16];
+
+ HMAC_CTX_init(&ctx);
+ HMAC_Init_ex(&ctx, key, keylen, EVP_md5(), nullptr);
+ HMAC_Update(&ctx, static_cast(input), inputlen);
+ uint32_t hashLength;
+ HMAC_Final(&ctx, output, &hashLength);
+ HMAC_CTX_cleanup(&ctx);
+ return output;
+}
+
+static uint8_t* NetSecurity_EVPEncrypt(uint8_t* key, void* input, size_t inputlen)
+{
+ EVP_CIPHER_CTX ctx;
+ EVP_CIPHER_CTX_init(&ctx);
+ EVP_CipherInit_ex(&ctx, EVP_rc4(), nullptr, key, nullptr, 1);
+
+ uint8_t* output = new uint8_t[inputlen];
+ EVP_Cipher(&ctx, output, static_cast(input), static_cast(inputlen));
+
+ EVP_CIPHER_CTX_cleanup(&ctx);
+ return output;
+}
+
+static int32_t
+NetSecurity_build_ntlm2_master(uint8_t* key, int32_t keylen, ntlm_buf* blob, ntlm_buf* sessionKey, ntlm_buf* masterKey)
+{
+ // reference: https://msdn.microsoft.com/en-us/library/cc236709.aspx
+ uint8_t* ntlmv2hash = NetSecurity_HMACDigest(key, keylen, blob->data, blob->length);
+ int32_t status = heim_ntlm_build_ntlm1_master(ntlmv2hash, UnsignedCast(keylen), sessionKey, masterKey);
+ if (status)
+ {
+ delete[] ntlmv2hash;
+ return status;
+ }
+
+ uint8_t* exportKey = NetSecurity_EVPEncrypt(ntlmv2hash, sessionKey->data, sessionKey->length);
+ delete[] ntlmv2hash;
+ masterKey->length = sessionKey->length;
+ masterKey->data = exportKey;
+ return status;
+}
+
+extern "C" int32_t NetSecurity_CreateType3Message(ntlm_buf* key,
+ ntlm_type2* type2,
+ const char* username,
+ const char* domain,
+ uint32_t flags,
+ ntlm_buf* lmResponse,
+ ntlm_buf* ntlmResponse,
+ uint8_t* baseSessionKey,
+ int32_t baseSessionKeyLen,
+ ntlm_buf** outSessionHandle,
+ int* outSessionKeyLen,
+ ntlm_buf** outBufferHandle,
+ int32_t* outLength)
+{
+ assert(key != nullptr);
+ assert(type2 != nullptr);
+ assert(lmResponse != nullptr);
+ assert(ntlmResponse != nullptr);
+ static char* workstation = static_cast(calloc(1, sizeof(char))); // empty string
+ ntlm_type3 type3;
+ memset(&type3, 0, sizeof(ntlm_type3));
+ type3.username = const_cast(username);
+ type3.targetname = const_cast(domain);
+ type3.lm = *lmResponse;
+ type3.ntlm = *ntlmResponse;
+ type3.ws = workstation;
+ type3.flags = flags;
+
+ int32_t status = 0;
+ ntlm_buf masterKey = {.length = 0, .data = nullptr};
+ *outSessionHandle = new ntlm_buf();
+
+ if (type2->targetinfo.length == 0)
+ {
+ status = heim_ntlm_build_ntlm1_master(key->data, key->length, *outSessionHandle, &masterKey);
+ }
+ else
+ {
+ // Only first 16 bytes of the NTLMv2 response should be passed
+ assert(ntlmResponse->length >= MD5_DIGEST_LENGTH);
+ ntlm_buf blob = {.length = MD5_DIGEST_LENGTH, .data = ntlmResponse->data};
+ status =
+ NetSecurity_build_ntlm2_master(baseSessionKey, baseSessionKeyLen, &blob, *outSessionHandle, &masterKey);
+ }
+ status = NetSecurity_SetBufferLength(status, outSessionHandle, outSessionKeyLen);
+ if (status != 0)
+ {
+ return status;
+ }
+
+ *outBufferHandle = new ntlm_buf();
+ type3.sessionkey = masterKey;
+ status = heim_ntlm_encode_type3(&type3, *outBufferHandle);
+ if (status != 0)
+ {
+ heim_ntlm_free_buf(*outSessionHandle);
+ delete *outSessionHandle;
+ *outSessionKeyLen = 0;
+ }
+
+ if (type2->targetinfo.length == 0)
+ {
+ heim_ntlm_free_buf(&masterKey);
+ }
+ else
+ {
+ // in case of v2, masterKey.data is created by NetSecurity_build_ntlm2_master function and free_buf cannot be
+ // called.
+ delete[] static_cast(masterKey.data);
+ }
+ return NetSecurity_SetBufferLength(status, outBufferHandle, outLength);
+}
diff --git a/src/Native/System.Net.Security.Native/pal_ntlmapi.h b/src/Native/System.Net.Security.Native/pal_ntlmapi.h
new file mode 100644
index 000000000000..30a6678ad06f
--- /dev/null
+++ b/src/Native/System.Net.Security.Native/pal_ntlmapi.h
@@ -0,0 +1,81 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#pragma once
+
+enum NtlmFlags : int32_t
+{
+ PAL_NTLMSSP_NEGOTIATE_UNICODE = 0x1,
+ PAL_NTLMSSP_REQUEST_TARGET = 0x4,
+ PAL_NTLMSSP_NEGOTIATE_SIGN = 0x10,
+ PAL_NTLMSSP_NEGOTIATE_SEAL = 0x20,
+ PAL_NTLMSSP_NEGOTIATE_NTLM = 0x200,
+ PAL_NTLMSSP_NEGOTIATE_ALWAYS_SIGN = 0x8000,
+ PAL_NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x80000,
+ PAL_NTLMSSP_NEGOTIATE_128 = 0x20000000,
+ PAL_NTLMSSP_NEGOTIATE_KEY_EXCH = 0x40000000,
+};
+
+struct ntlm_buf;
+struct ntlm_type2;
+
+/*
+Shims heim_ntlm_free_buf method.
+*/
+extern "C" void NetSecurity_HeimNtlmFreeBuf(ntlm_buf* data);
+
+/*
+Copies data from ntlm_buffer into an array of given size, from the offset.
+*/
+extern "C" void
+NetSecurity_ExtractNtlmBuffer(const ntlm_buf* bufferHandle, uint8_t* destination, uint32_t capacity, uint32_t offset);
+
+/*
+Shims heim_ntlm_encode_type1 method.
+*/
+extern "C" int32_t NetSecurity_HeimNtlmEncodeType1(uint32_t flags, ntlm_buf** outBufferHandle, int* outLength);
+
+/*
+Shims heim_ntlm_decode_type2 method.
+*/
+extern "C" int32_t NetSecurity_HeimNtlmDecodeType2(uint8_t* data, int32_t offset, int32_t count, ntlm_type2** type2);
+
+/*
+Shims heim_ntlm_free_type2 method.
+*/
+extern "C" void NetSecurity_HeimNtlmFreeType2(ntlm_type2* type2);
+
+/*
+Shims heim_ntlm_nt_key method.
+*/
+extern "C" int32_t NetSecurity_HeimNtlmNtKey(const char* password, ntlm_buf** outBufferHandle, int* outLength);
+
+/*
+Shims heim_ntlm_calculate_lm2/_ntlm2 methods.
+*/
+extern "C" int32_t NetSecurity_HeimNtlmCalculateResponse(int32_t isLM,
+ const ntlm_buf* key,
+ ntlm_type2* type2,
+ char* username,
+ char* target,
+ uint8_t* baseSessionKey,
+ int32_t baseSessionKeyLen,
+ ntlm_buf** data,
+ int* outLength);
+
+/*
+Implements Type3 msg proccessing logic
+*/
+extern "C" int32_t NetSecurity_CreateType3Message(ntlm_buf* key,
+ ntlm_type2* type2,
+ const char* username,
+ const char* domain,
+ uint32_t flags,
+ ntlm_buf* lmResponse,
+ ntlm_buf* ntlmResponse,
+ uint8_t* baseSessionKey,
+ int32_t baseSessionKeyLen,
+ ntlm_buf** outSessionHandle,
+ int32_t* outSessionKeyLen,
+ ntlm_buf** outBufferHandle,
+ int32_t* outLength);
diff --git a/src/Native/System.Security.Cryptography.Native/pal_evp_cipher.cpp b/src/Native/System.Security.Cryptography.Native/pal_evp_cipher.cpp
index c706fc8596dd..b71a14da0847 100644
--- a/src/Native/System.Security.Cryptography.Native/pal_evp_cipher.cpp
+++ b/src/Native/System.Security.Cryptography.Native/pal_evp_cipher.cpp
@@ -122,3 +122,13 @@ extern "C" const EVP_CIPHER* CryptoNative_EvpDes3Cbc()
{
return EVP_des_ede3_cbc();
}
+
+extern "C" const EVP_CIPHER* CryptoNative_EvpRc4()
+{
+ return EVP_rc4();
+}
+
+extern "C" void CryptoNative_EvpCipher(EVP_CIPHER_CTX* ctx, uint8_t* out, const uint8_t *in, uint32_t inl)
+{
+ EVP_Cipher(ctx, out, in, inl);
+}
diff --git a/src/Native/System.Security.Cryptography.Native/pal_evp_cipher.h b/src/Native/System.Security.Cryptography.Native/pal_evp_cipher.h
index c151b82561aa..34de9cec0d3c 100644
--- a/src/Native/System.Security.Cryptography.Native/pal_evp_cipher.h
+++ b/src/Native/System.Security.Cryptography.Native/pal_evp_cipher.h
@@ -126,3 +126,20 @@ EvpDes3Cbc
Direct shim to EVP_des_ede3_cbc.
*/
extern "C" const EVP_CIPHER* CryptoNative_EvpDes3Cbc();
+
+/*
+Function:
+EvpRc4
+
+Direct shim to EVP_rc4.
+*/
+extern "C" const EVP_CIPHER* CryptoNative_EvpRc4();
+
+/*
+Function:
+EvpCipher
+
+Direct shim to EVP_Cipher.
+*/
+extern "C"
+void CryptoNative_EvpCipher(EVP_CIPHER_CTX* ctx, uint8_t *out, const uint8_t *in, uint32_t inl);
diff --git a/src/Native/configure.cmake b/src/Native/configure.cmake
index 5c91ce05b1ee..a25388fecb96 100644
--- a/src/Native/configure.cmake
+++ b/src/Native/configure.cmake
@@ -317,6 +317,14 @@ check_symbol_exists(
"sys/syslimits.h"
HAVE_OPEN_MAX)
+if (CMAKE_SYSTEM_NAME STREQUAL Linux)
+ check_include_files(
+ heimntlm.h
+ HAVE_HEIMNTLM_HEADERS)
+else ()
+ set(HAVE_HEIMNTLM_HEADERS 0)
+endif ()
+
set (CMAKE_REQUIRED_LIBRARIES)
configure_file(
diff --git a/src/System.Net.Security/src/Resources/Strings.resx b/src/System.Net.Security/src/Resources/Strings.resx
index b1f411c38221..ecccbd203078 100644
--- a/src/System.Net.Security/src/Resources/Strings.resx
+++ b/src/System.Net.Security/src/Resources/Strings.resx
@@ -1,4 +1,4 @@
-
+