From 8b2eb3dc0c5e8d61f005b0089d86dad9cca8d66a Mon Sep 17 00:00:00 2001 From: Hemanth Kapila Date: Wed, 27 Jan 2016 17:43:27 +0530 Subject: [PATCH 1/2] Native shims for libheimntlm Introducing native shims for libheimntlm inorder to support NTLM --- .../Interop.HeimdalNtlm.cs | 42 +++ .../Interop.HeimdalNtlmException.cs | 36 +++ .../Interop.Ntlm.cs | 124 ++++++++ .../src/Interop/Unix/Interop.Libraries.cs | 1 + .../Interop.EVP.Cipher.cs | 11 + .../SafeHandles/NtlmSecuritySafeHandles.cs | 278 ++++++++++++++++++ src/Native/Common/pal_config.h.in | 1 + .../System.Net.Security.Native/CMakeLists.txt | 35 +++ .../pal_ntlmapi.cpp | 266 +++++++++++++++++ .../System.Net.Security.Native/pal_ntlmapi.h | 81 +++++ .../pal_evp_cipher.cpp | 10 + .../pal_evp_cipher.h | 17 ++ src/Native/configure.cmake | 8 + .../src/Resources/Strings.resx | 7 +- 14 files changed, 915 insertions(+), 2 deletions(-) create mode 100644 src/Common/src/Interop/Linux/System.Net.Security.Native/Interop.HeimdalNtlm.cs create mode 100644 src/Common/src/Interop/Linux/System.Net.Security.Native/Interop.HeimdalNtlmException.cs create mode 100644 src/Common/src/Interop/Linux/System.Net.Security.Native/Interop.Ntlm.cs create mode 100644 src/Common/src/Microsoft/Win32/SafeHandles/NtlmSecuritySafeHandles.cs create mode 100644 src/Native/System.Net.Security.Native/CMakeLists.txt create mode 100644 src/Native/System.Net.Security.Native/pal_ntlmapi.cpp create mode 100644 src/Native/System.Net.Security.Native/pal_ntlmapi.h 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..6b04be61f6d6 --- /dev/null +++ b/src/Common/src/Interop/Linux/System.Net.Security.Native/Interop.Ntlm.cs @@ -0,0 +1,124 @@ +// 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 System.Text; +using System.Diagnostics; +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.SecurityNative, EntryPoint="NetSecurity_HeimNtlmFreeBuf")] + internal static extern int HeimNtlmFreeBuf(IntPtr bufferHandle); + + [DllImport(Interop.Libraries.SecurityNative, EntryPoint="NetSecurity_CopyBuffer")] + internal static extern int CopyBuffer(SafeNtlmBufferHandle data, byte[] buffer, int capacity, int offset); + + [DllImport(Interop.Libraries.SecurityNative, EntryPoint="NetSecurity_HeimNtlmEncodeType1")] + internal static extern int HeimNtlmEncodeType1(uint flags, out SafeNtlmBufferHandle data, out int length); + + [DllImport(Interop.Libraries.SecurityNative, EntryPoint="NetSecurity_HeimNtlmDecodeType2")] + internal static extern int HeimNtlmDecodeType2(byte[] data, int offset, int count, out SafeNtlmType2Handle type2Handle); + + [DllImport(Interop.Libraries.SecurityNative, EntryPoint="NetSecurity_HeimNtlmFreeType2")] + internal static extern int HeimNtlmFreeType2(IntPtr type2Handle); + + [DllImport(Interop.Libraries.SecurityNative, EntryPoint="NetSecurity_HeimNtlmNtKey", CharSet = CharSet.Ansi)] + internal static extern int HeimNtlmNtKey(string password, out SafeNtlmBufferHandle key, out int length); + + [DllImport(Interop.Libraries.SecurityNative, 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.SecurityNative, 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; + } + + internal static byte[] EVPDigest(byte[] key, byte[] input, int inputlen, out uint outputlen) + { + //reference doc: http://msdn.microsoft.com/en-us/library/cc236700.aspx + byte[] output = new byte[Interop.Crypto.EVP_MAX_MD_SIZE]; + outputlen = 0; + using (SafeEvpMdCtxHandle ctx = Interop.Crypto.EvpMdCtxCreate(Interop.Crypto.EvpMd5())) + unsafe + { + fixed (byte *keyPtr = key, inPtr = input, outPtr = output) + { + Check(Interop.Crypto.EvpDigestUpdate(ctx, keyPtr, key.Length)); + Check(Interop.Crypto.EvpDigestUpdate(ctx, inPtr, inputlen)); + Check(Interop.Crypto.EvpDigestFinalEx(ctx, outPtr, ref outputlen)); + } + } + return output; + } + + internal static unsafe byte[] HMACDigest(byte* key, int keylen, byte* input, int inputlen, byte* prefix, int prefixlen, out int hashLength) + { + hashLength = 0; + byte[] output = new byte[Interop.Crypto.EVP_MAX_MD_SIZE]; + using (SafeHmacCtxHandle ctx = Interop.Crypto.HmacCreate(key, keylen, Interop.Crypto.EvpMd5())) + { + if (prefixlen > 0) + { + Check(Interop.Crypto.HmacUpdate(ctx, prefix, prefixlen)); + } + Check(Interop.Crypto.HmacUpdate(ctx, input, inputlen)); + fixed (byte* hashPtr = output) + { + Check(Interop.Crypto.HmacFinal(ctx, hashPtr, ref hashLength)); + } + } + return output; + } + + private static void Check(int result) + { + const int Success = 1; + if (result != Success) + { + throw Interop.Crypto.CreateOpenSslCryptographicException(); + } + } + } + +} diff --git a/src/Common/src/Interop/Unix/Interop.Libraries.cs b/src/Common/src/Interop/Unix/Interop.Libraries.cs index 8e5de4df736b..fc0ca0bc982a 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 SecurityNative = "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..a60fef25ed1d --- /dev/null +++ b/src/Common/src/Microsoft/Win32/SafeHandles/NtlmSecuritySafeHandles.cs @@ -0,0 +1,278 @@ +// 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.CopyBuffer(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 GCHandle _gch; + private uint _digestLength; + private uint _sequenceNumber; + private bool _isSealingKey; + 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); + + byte[] magic = Encoding.UTF8.GetBytes(keyMagic); + + byte[] digest = Interop.NetSecurity.EVPDigest(key, magic, magic.Length, out _digestLength); + _isSealingKey = isSealingKey; + if (_isSealingKey) + { + _cipherContext = Interop.Crypto.EvpCipherCreate(Interop.Crypto.EvpRc4(), digest, null, 1); + } + _gch = GCHandle.Alloc(digest, GCHandleType.Pinned); + handle = _gch.AddrOfPinnedObject(); + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + + protected override void Dispose(bool disposing) + { + if (disposing && (null != _cipherContext)) + { + _cipherContext.Dispose(); + _cipherContext = null; + } + base.Dispose(disposing); + } + + protected override bool ReleaseHandle() + { + _gch.Free(); + SetHandle(IntPtr.Zero); + 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); + byte[] hash; + unsafe + { + + fixed (byte* outPtr = output) + fixed (byte* bytePtr = buffer) + { + MarshalUint(outPtr, Version); // version + MarshalUint(outPtr + SequenceNumberOffset, _sequenceNumber); + int hashLength; + hash = Interop.NetSecurity.HMACDigest((byte*) handle.ToPointer(), (int)_digestLength, (bytePtr + offset), count, + outPtr + SequenceNumberOffset, ChecksumOffset, out hashLength); + Debug.Assert(hash != null && hashLength >= HMacDigestLength, "HMACDigest has a length of at least " + HMacDigestLength); + _sequenceNumber++; + } + } + + if ((sealingKey == null) || sealingKey.IsInvalid) + { + 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 unsafe void MarshalUint(byte* ptr, uint num) + { + for (int i = 0; i < 4; i++) + { + ptr[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..e7bc1d991216 --- /dev/null +++ b/src/Native/System.Net.Security.Native/CMakeLists.txt @@ -0,0 +1,35 @@ +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} + ) + + 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..3f5361c66650 --- /dev/null +++ b/src/Native/System.Net.Security.Native/pal_ntlmapi.cpp @@ -0,0 +1,266 @@ +// 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_CopyBuffer(const ntlm_buf* bufferHandle, uint8_t* bytes, uint32_t capacity, uint32_t offset) +{ + if (bufferHandle == nullptr) + { + return; + } + assert(bufferHandle->length <= (capacity - offset)); + memcpy(bytes + 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..125e1eeecfea --- /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_CopyBuffer(const ntlm_buf* bufferHandle, uint8_t* bytes, 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 @@ - +