diff --git a/src/Common/src/Interop/Linux/Interop.Libraries.cs b/src/Common/src/Interop/Linux/Interop.Libraries.cs new file mode 100644 index 000000000000..dcc397f94b50 --- /dev/null +++ b/src/Common/src/Interop/Linux/Interop.Libraries.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +internal static partial class Interop +{ + private static partial class Libraries + { + // Shims + internal const string NetNtlmNative = "System.Net.Ntlm.Native"; + } +} diff --git a/src/Common/src/Interop/Linux/System.Net.Ntlm.Native/Interop.NetNtlmNative.cs b/src/Common/src/Interop/Linux/System.Net.Ntlm.Native/Interop.NetNtlmNative.cs new file mode 100644 index 000000000000..8a66555ae464 --- /dev/null +++ b/src/Common/src/Interop/Linux/System.Net.Ntlm.Native/Interop.NetNtlmNative.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more 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 NetNtlmNative + { + // The following constant is used in calculation of NTOWF2 + // reference: https://msdn.microsoft.com/en-us/library/cc236700.aspx + public const int MD5DigestLength = 16; + + [DllImport(Interop.Libraries.NetNtlmNative, EntryPoint="NetNtlmNative_ReleaseNtlmBuffer")] + internal static extern int ReleaseNtlmBuffer(IntPtr bufferPtr, UInt64 length); + + [DllImport(Interop.Libraries.NetNtlmNative, EntryPoint="NetNtlmNative_NtlmEncodeType1")] + internal static extern int NtlmEncodeType1(uint flags, ref NtlmBuffer buffer); + + [DllImport(Interop.Libraries.NetNtlmNative, EntryPoint="NetNtlmNative_NtlmDecodeType2")] + internal static extern int NtlmDecodeType2(byte[] data, int offset, int count, out SafeNtlmType2Handle type2Handle); + + [DllImport(Interop.Libraries.NetNtlmNative, EntryPoint="NetNtlmNative_NtlmFreeType2")] + internal static extern int NtlmFreeType2(IntPtr type2Handle); + + [DllImport(Interop.Libraries.NetNtlmNative, EntryPoint="NetNtlmNative_CreateType3Message", CharSet = CharSet.Ansi)] + internal static extern int CreateType3Message( + string password, + SafeNtlmType2Handle type2Handle, + string username, + string domain, + uint flags, + ref NtlmBuffer sessionKey, + ref NtlmBuffer data); + + internal enum NtlmFlags + { + NTLMSSP_NEGOTIATE_UNICODE = 0x1, + NTLMSSP_REQUEST_TARGET = 0x4, + NTLMSSP_NEGOTIATE_SIGN = 0x10, + NTLMSSP_NEGOTIATE_SEAL = 0x20, + NTLMSSP_NEGOTIATE_NTLM = 0x200, + NTLMSSP_NEGOTIATE_ALWAYS_SIGN = 0x8000, + NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x80000, + NTLMSSP_NEGOTIATE_128 = 0x20000000, + NTLMSSP_NEGOTIATE_KEY_EXCH = 0x40000000 + } + } +} diff --git a/src/Common/src/Interop/Linux/System.Net.Ntlm.Native/Interop.Ntlm.cs b/src/Common/src/Interop/Linux/System.Net.Ntlm.Native/Interop.Ntlm.cs new file mode 100644 index 000000000000..8330cf7bb622 --- /dev/null +++ b/src/Common/src/Interop/Linux/System.Net.Ntlm.Native/Interop.Ntlm.cs @@ -0,0 +1,171 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more 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; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class Ntlm + { + private static readonly byte[] ClientToServerSigningMagicKey = Encoding.UTF8.GetBytes("session key to client-to-server signing key magic constant\0"); + private static readonly byte[] ServerToClientSigningMagicKey = Encoding.UTF8.GetBytes("session key to server-to-client signing key magic constant\0"); + private static readonly byte[] ServerToClientSealingMagicKey = Encoding.UTF8.GetBytes("session key to server-to-client sealing key magic constant\0"); + private static readonly byte[] ClientToServerSealingMagicKey = Encoding.UTF8.GetBytes("session key to client-to-server sealing key magic constant\0"); + + internal sealed class SigningKey + { + private uint _sequenceNumber; + private readonly byte[] _digest; + + public SigningKey(byte[] key, byte[] magicKey) + { + using (IncrementalHash incremental = IncrementalHash.CreateHash(HashAlgorithmName.MD5)) + { + incremental.AppendData(key); + incremental.AppendData(magicKey); + _digest = incremental.GetHashAndReset(); + } + } + + public byte[] Sign(SealingKey sealingKey, byte[] buffer, int offset, int count) + { + Debug.Assert(offset >= 0 && offset <= buffer.Length, "cannot sign with invalid offset"); + Debug.Assert(count <= buffer.Length - offset, "cannot sign with invalid 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.NetNtlmNative.MD5DigestLength]; + MarshalUint32(output, 0, Version); // version + MarshalUint32(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(hash, 0, HMacDigestLength); + Array.Copy(cipher, 0, output, ChecksumOffset, cipher.Length); + } + + return output; + } + } + + internal sealed class SealingKey : IDisposable + { + private readonly byte[] _digest; + private SafeEvpCipherCtxHandle _cipherContext; + + public SealingKey(byte[] key, byte[] magicKey) + { + _digest = NtlmKeyDigest(key, magicKey); + _cipherContext = Interop.Crypto.EvpCipherCreate(Interop.Crypto.EvpRc4(), _digest, null, 1); + } + + public byte[] SealOrUnseal(byte[] buffer, int offset, int count) + { + // Message Confidentiality. Reference: https://msdn.microsoft.com/en-us/library/cc236707.aspx + Debug.Assert(offset >= 0 && offset <= buffer.Length, "Cannot sign with invalid offset " + offset); + Debug.Assert(count >= 0, "cannot sign with invalid count"); + Debug.Assert(count <= (buffer.Length - offset), "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; + } + } + } + + public void Dispose() + { + if (_cipherContext != null) + { + _cipherContext.Dispose(); + _cipherContext = null; + } + } + } + + private static byte[] NtlmKeyDigest(byte[] key, byte[] magicKey) + { + using (IncrementalHash incremental = IncrementalHash.CreateHash(HashAlgorithmName.MD5)) + { + incremental.AppendData(key); + incremental.AppendData(magicKey); + return incremental.GetHashAndReset(); + } + } + + private static void MarshalUint32(byte[] ptr, int offset, uint num) + { + for (int i = 0; i < 4; i++) + { + ptr[offset + i] = (byte) (num & 0xff); + num >>= 8; + } + } + + internal static byte[] CreateNegotiateMessage(uint flags) + { + NetNtlmNative.NtlmBuffer buffer = default(NetNtlmNative.NtlmBuffer); + try + { + int status = NetNtlmNative.NtlmEncodeType1(flags, ref buffer); + NetNtlmNative.NtlmException.ThrowIfError(status); + return buffer.ToByteArray(); + } + finally + { + buffer.Dispose(); + } + } + + internal static byte[] CreateAuthenticateMessage(uint flags, string username, string password, string domain, + byte[] type2Data, int offset, int count, out byte[] sessionKey) + { + using (NtlmType3Message challengeMessage = new NtlmType3Message(type2Data, offset, count)) + { + return challengeMessage.GetResponse(flags, username, password, domain, out sessionKey); + } + } + + internal static void CreateKeys(byte[] sessionKey, out SigningKey serverSignKey, out SealingKey serverSealKey, out SigningKey clientSignKey, out SealingKey clientSealKey) + { + serverSignKey = new SigningKey(sessionKey, ServerToClientSigningMagicKey); + serverSealKey = new SealingKey(sessionKey, ServerToClientSealingMagicKey); + clientSignKey = new SigningKey(sessionKey, ClientToServerSigningMagicKey); + clientSealKey = new SealingKey(sessionKey, ClientToServerSealingMagicKey); + } + } +} + diff --git a/src/Common/src/Interop/Linux/System.Net.Ntlm.Native/Interop.NtlmBuffer.cs b/src/Common/src/Interop/Linux/System.Net.Ntlm.Native/Interop.NtlmBuffer.cs new file mode 100644 index 000000000000..4133fddd0dd9 --- /dev/null +++ b/src/Common/src/Interop/Linux/System.Net.Ntlm.Native/Interop.NtlmBuffer.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class NetNtlmNative + { + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct NtlmBuffer : IDisposable + { + internal ulong length; + internal IntPtr data; + + internal int Copy(byte[] destination, int offset) + { + Debug.Assert(destination != null, "target destination cannot be null"); + Debug.Assert(offset >= 0 && offset <= destination.Length, "offset must be valid "); + + if (data == IntPtr.Zero || length == 0) + { + return 0; + } + + int bufferLength = Convert.ToInt32(length); + int available = destination.Length - offset; // amount of space in the given buffer + if (bufferLength > available) + { + throw new NetNtlmNative.NtlmException(SR.Format(SR.net_context_buffer_too_small, bufferLength, available)); + } + + Marshal.Copy(data, destination, offset, bufferLength); + return bufferLength; + } + + internal byte[] ToByteArray() + { + if (data == IntPtr.Zero || length == 0) + { + return Array.Empty(); + } + + int bufferLength = Convert.ToInt32(length); + byte[] destination = new byte[bufferLength]; + Marshal.Copy(data, destination, 0, bufferLength); + return destination; + } + + public void Dispose() + { + if (data != IntPtr.Zero) + { + Interop.NetNtlmNative.ReleaseNtlmBuffer(data, length); + data = IntPtr.Zero; + } + + length = 0; + } + } + } +} + diff --git a/src/Common/src/Interop/Linux/System.Net.Ntlm.Native/Interop.NtlmException.cs b/src/Common/src/Interop/Linux/System.Net.Ntlm.Native/Interop.NtlmException.cs new file mode 100644 index 000000000000..ef9995a0724c --- /dev/null +++ b/src/Common/src/Interop/Linux/System.Net.Ntlm.Native/Interop.NtlmException.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class NetNtlmNative + { + internal sealed class NtlmException : Exception + { + public NtlmException(string message) : base(message) + { + } + + public NtlmException(int error) + : base(SR.Format(SR.net_generic_ntlm_operation_failed, error)) + { + HResult = error; + } + + public static void ThrowIfError(int error) + { + if (error != 0) + { + var ex = new NtlmException(error); + throw ex; + } + } + } + } +} + diff --git a/src/Common/src/Interop/Linux/System.Net.Ntlm.Native/Interop.NtlmType3Message.cs b/src/Common/src/Interop/Linux/System.Net.Ntlm.Native/Interop.NtlmType3Message.cs new file mode 100644 index 000000000000..d66da3d515ec --- /dev/null +++ b/src/Common/src/Interop/Linux/System.Net.Ntlm.Native/Interop.NtlmType3Message.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more 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; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class Ntlm + { + /// + /// Provides ntlm_type3 message + /// + internal sealed class NtlmType3Message : IDisposable + { + private SafeNtlmType2Handle _type2Handle; + + public NtlmType3Message(byte[] type2Data, int offset, int count) + { + Debug.Assert(type2Data != null, "type2Data cannot be null"); + Debug.Assert(offset >= 0 && offset <= type2Data.Length, "offset must be a valid value"); + Debug.Assert(count >= 0 , " count must be a valid value"); + Debug.Assert(type2Data.Length >= offset + count, " count and offset must match the given buffer"); + + int status = Interop.NetNtlmNative.NtlmDecodeType2(type2Data, offset, count, out _type2Handle); + Interop.NetNtlmNative.NtlmException.ThrowIfError(status); + } + + public byte[] GetResponse(uint flags, string username, string password, string domain, + out byte[] sessionKey) + { + Debug.Assert(username != null, "username cannot be null"); + Debug.Assert(password != null, "password cannot be null"); + Debug.Assert(domain != null, "domain cannot be null"); + + sessionKey = null; + Interop.NetNtlmNative.NtlmBuffer sessionKeyBuffer = default(Interop.NetNtlmNative.NtlmBuffer); + Interop.NetNtlmNative.NtlmBuffer outputData = default(Interop.NetNtlmNative.NtlmBuffer); + + try + { + int status = Interop.NetNtlmNative.CreateType3Message(password, _type2Handle, username, domain, flags, + ref sessionKeyBuffer, ref outputData); + Interop.NetNtlmNative.NtlmException.ThrowIfError(status); + + sessionKey = sessionKeyBuffer.ToByteArray(); + return outputData.ToByteArray(); + } + finally + { + sessionKeyBuffer.Dispose(); + outputData.Dispose(); + } + } + + public void Dispose() + { + if (_type2Handle != null) + { + _type2Handle.Dispose(); + _type2Handle = null; + } + } + } + } +} 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 419944a8a98c..1ed6bd4a7c15 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 @@ -67,5 +67,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..7c51b708132b --- /dev/null +++ b/src/Common/src/Microsoft/Win32/SafeHandles/NtlmSecuritySafeHandles.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more 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_type2 handle + /// + internal sealed class SafeNtlmType2Handle : SafeHandle + { + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + + protected override bool ReleaseHandle() + { + Interop.NetNtlmNative.NtlmFreeType2(handle); + SetHandle(IntPtr.Zero); + return true; + } + + private SafeNtlmType2Handle() : base(IntPtr.Zero, true) + { + } + } +} diff --git a/src/Native/CMakeLists.txt b/src/Native/CMakeLists.txt index 78b41f4ac53d..085f0f65de77 100644 --- a/src/Native/CMakeLists.txt +++ b/src/Native/CMakeLists.txt @@ -92,3 +92,4 @@ add_subdirectory(System.Native) add_subdirectory(System.Net.Http.Native) add_subdirectory(System.Security.Cryptography.Native) add_subdirectory(System.Net.Security.Native) +add_subdirectory(System.Net.Ntlm.Native) diff --git a/src/Native/System.Net.Ntlm.Native/CMakeLists.txt b/src/Native/System.Net.Ntlm.Native/CMakeLists.txt new file mode 100644 index 000000000000..a1b5b2bfed4c --- /dev/null +++ b/src/Native/System.Net.Ntlm.Native/CMakeLists.txt @@ -0,0 +1,32 @@ +project(System.Net.Ntlm.Native) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +add_definitions(-DPIC=1 -Wno-padded) + +find_library(LIB_HEIMNTLM NAMES heimntlm PATHS /usr/heimdal/lib /usr/lib/x86_64-linux-gnu/heimdal/) +find_package(OpenSSL REQUIRED) + +if(LIB_HEIMNTLM STREQUAL LIB_HEIMNTLM-NOTFOUND) + message(WARNING "Cannot find libheimntlm.so. System.Net.Security.NegotiateStream will be built without NTLM support. Try installing libheimntlm (or the appropriate package for your platform) for NTLM") +else() + include_directories(/usr/heimdal/include) + include_directories(/usr/include/heimdal) + + set(NATIVENTLM_SOURCES + pal_ntlmapi.cpp + ) + + add_library(System.Net.Ntlm.Native + SHARED + ${NATIVENTLM_SOURCES} + ) + + target_link_libraries(System.Net.Ntlm.Native + ${LIB_HEIMNTLM} + ${OPENSSL_CRYPTO_LIBRARY} + ) + + install (TARGETS System.Net.Ntlm.Native DESTINATION .) +endif() + diff --git a/src/Native/System.Net.Ntlm.Native/pal_ntlmapi.cpp b/src/Native/System.Net.Ntlm.Native/pal_ntlmapi.cpp new file mode 100644 index 000000000000..fd4a634c9213 --- /dev/null +++ b/src/Native/System.Net.Ntlm.Native/pal_ntlmapi.cpp @@ -0,0 +1,312 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more 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 +NetNtlmNative_SetBufferLength(int32_t status, ntlm_buf* ntlmBuffer, struct PAL_NtlmBuffer* targetBuffer) +{ + assert(ntlmBuffer != nullptr); + assert(targetBuffer != nullptr); + + if (status != 0) + { + targetBuffer->length = 0; + targetBuffer->data = nullptr; + } + else + { + assert(targetBuffer->length == 0 || targetBuffer->data != nullptr); + + targetBuffer->length = ntlmBuffer->length; + targetBuffer->data = ntlmBuffer->data; + } + + return status; +} + +extern "C" void NetNtlmNative_ReleaseNtlmBuffer(void* buffer, uint64_t length) +{ + assert(buffer != nullptr); + + ntlm_buf ntlmBuffer{.length = length, .data = buffer}; + heim_ntlm_free_buf(&ntlmBuffer); +} + +extern "C" int32_t NetNtlmNative_NtlmEncodeType1(uint32_t flags, struct PAL_NtlmBuffer* outBuffer) +{ + assert(outBuffer != nullptr); + + ntlm_type1 type1; + ntlm_buf ntlmBuffer{.length = 0, .data = nullptr}; + memset(&type1, 0, sizeof(ntlm_type1)); + type1.flags = flags; + return NetNtlmNative_SetBufferLength(heim_ntlm_encode_type1(&type1, &ntlmBuffer), &ntlmBuffer, outBuffer); +} + +extern "C" int32_t NetNtlmNative_NtlmDecodeType2(uint8_t* data, int32_t offset, int32_t count, ntlm_type2** type2) +{ + assert(data != nullptr); + assert(offset >= 0); + assert(count >= 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 NetNtlmNative_NtlmFreeType2(ntlm_type2* type2) +{ + assert(type2 != nullptr); + + heim_ntlm_free_type2(type2); + delete type2; +} + +static int32_t NetNtlmNative_NtlmNtKey(const char* password, struct PAL_NtlmBuffer* outBuffer) +{ + assert(outBuffer != nullptr); + assert(password != nullptr); + + ntlm_buf ntlmBuffer{.length = 0, .data = nullptr}; + return NetNtlmNative_SetBufferLength(heim_ntlm_nt_key(password, &ntlmBuffer), &ntlmBuffer, outBuffer); +} + +static int32_t NetNtlmNative_NtlmCalculateResponse(int32_t isLM, + const struct PAL_NtlmBuffer* key, + ntlm_type2* type2, + char* username, + char* target, + uint8_t* baseSessionKey, + int32_t baseSessionKeyLen, + struct PAL_NtlmBuffer* outBuffer) +{ + // reference doc: http://msdn.microsoft.com/en-us/library/cc236700.aspx + assert(isLM == 0 || isLM == 1); + assert(key != nullptr); + assert(type2 != nullptr); + assert(username != nullptr); + assert(target != nullptr); + assert(baseSessionKey != nullptr); + assert(baseSessionKeyLen == MD5_DIGEST_LENGTH); + assert(outBuffer != nullptr); + + ntlm_buf ntlmBuffer{.length = 0, .data = nullptr}; + if (isLM) + { + return NetNtlmNative_SetBufferLength( + heim_ntlm_calculate_lm2( + key->data, key->length, username, target, type2->challenge, baseSessionKey, &ntlmBuffer), + &ntlmBuffer, + outBuffer); + } + else + { + if (type2->targetinfo.length == 0) + { + return NetNtlmNative_SetBufferLength( + heim_ntlm_calculate_ntlm1(key->data, key->length, type2->challenge, &ntlmBuffer), + &ntlmBuffer, + outBuffer); + } + else + { + return NetNtlmNative_SetBufferLength(heim_ntlm_calculate_ntlm2(key->data, + key->length, + username, + target, + type2->challenge, + &type2->targetinfo, + baseSessionKey, + &ntlmBuffer), + &ntlmBuffer, + outBuffer); + } + } +} + +static uint8_t* NetNtlmNative_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* NetNtlmNative_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 NetNtlmNative_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 = NetNtlmNative_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; + } + + heim_ntlm_free_buf(masterKey); + uint8_t* exportKey = NetNtlmNative_EVPEncrypt(ntlmv2hash, sessionKey->data, sessionKey->length); + delete[] ntlmv2hash; + masterKey->length = sessionKey->length; + masterKey->data = exportKey; + return status; +} + +extern "C" int32_t NetNtlmNative_CreateType3Message(const char* password, + ntlm_type2* type2, + char* username, + char* domain, + uint32_t flags, + struct PAL_NtlmBuffer* outSessionKey, + struct PAL_NtlmBuffer* outBuffer) +{ + assert(type2 != nullptr); + assert(username != nullptr); + assert(domain != nullptr); + assert(outSessionKey != nullptr); + assert(outBuffer != nullptr); + + outBuffer->length = 0; + outBuffer->data = nullptr; + outSessionKey->length = 0; + outSessionKey->data = nullptr; + + struct PAL_NtlmBuffer key, lmResponse, ntlmResponse; + int32_t status = NetNtlmNative_NtlmNtKey(password, &key); + if (status) + { + return status; + } + + // reference doc: http://msdn.microsoft.com/en-us/library/cc236700.aspx + uint8_t baseSessionKey[MD5_DIGEST_LENGTH]; + int32_t baseSessionKeyLen = static_cast(sizeof(baseSessionKey)); + status = NetNtlmNative_NtlmCalculateResponse( + true, &key, type2, username, domain, baseSessionKey, baseSessionKeyLen, &lmResponse); + if (status) + { + NetNtlmNative_ReleaseNtlmBuffer(key.data, key.length); + return status; + } + + status = NetNtlmNative_NtlmCalculateResponse( + false, &key, type2, username, domain, baseSessionKey, baseSessionKeyLen, &ntlmResponse); + if (status) + { + NetNtlmNative_ReleaseNtlmBuffer(key.data, key.length); + NetNtlmNative_ReleaseNtlmBuffer(lmResponse.data, lmResponse.length); + return status; + } + + 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 = {.length = lmResponse.length, .data = lmResponse.data}; + type3.ntlm = {.length = ntlmResponse.length, .data = ntlmResponse.data}; + type3.ws = workstation; + type3.flags = flags; + + ntlm_buf masterKey{.length = 0, .data = nullptr}; + ntlm_buf ntlmSessionKey{.length = 0, .data = nullptr}; + ntlm_buf ntlmBuffer{.length = 0, .data = nullptr}; + + if (type2->targetinfo.length == 0) + { + status = heim_ntlm_build_ntlm1_master(key.data, key.length, &ntlmSessionKey, &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 = + NetNtlmNative_build_ntlm2_master(baseSessionKey, baseSessionKeyLen, &blob, &ntlmSessionKey, &masterKey); + } + NetNtlmNative_ReleaseNtlmBuffer(key.data, key.length); + + status = NetNtlmNative_SetBufferLength(status, &ntlmSessionKey, outSessionKey); + if (status != 0) + { + NetNtlmNative_ReleaseNtlmBuffer(lmResponse.data, lmResponse.length); + NetNtlmNative_ReleaseNtlmBuffer(ntlmResponse.data, ntlmResponse.length); + return status; + } + + type3.sessionkey = masterKey; + status = heim_ntlm_encode_type3(&type3, &ntlmBuffer); + NetNtlmNative_ReleaseNtlmBuffer(lmResponse.data, lmResponse.length); + NetNtlmNative_ReleaseNtlmBuffer(ntlmResponse.data, ntlmResponse.length); + if (status != 0) + { + heim_ntlm_free_buf(&ntlmBuffer); + return status; + } + + if (type2->targetinfo.length == 0) + { + heim_ntlm_free_buf(&masterKey); + } + else + { + // in case of v2, masterKey.data is created by NetNtlmNative_build_ntlm2_master function and free_buf cannot + // be + // called. + delete[] static_cast(masterKey.data); + } + + return NetNtlmNative_SetBufferLength(status, &ntlmBuffer, outBuffer); +} diff --git a/src/Native/System.Net.Ntlm.Native/pal_ntlmapi.h b/src/Native/System.Net.Ntlm.Native/pal_ntlmapi.h new file mode 100644 index 000000000000..b57bdd46f0e0 --- /dev/null +++ b/src/Native/System.Net.Ntlm.Native/pal_ntlmapi.h @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more 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 PAL_NtlmBuffer +{ + uint64_t length; + void* data; +}; +struct ntlm_type2; + +/* +Shims heim_ntlm_free_buf method. +*/ +extern "C" void NetNtlmNative_ReleaseNtlmBuffer(void* buffer, uint64_t length); + +/* +Shims heim_ntlm_encode_type1 method. +*/ +extern "C" int32_t NetNtlmNative_NtlmEncodeType1(uint32_t flags, struct PAL_NtlmBuffer* outBuffer); + +/* +Shims heim_ntlm_decode_type2 method. +*/ +extern "C" int32_t NetNtlmNative_NtlmDecodeType2(uint8_t* data, int32_t offset, int32_t count, ntlm_type2** type2); + +/* +Shims heim_ntlm_free_type2 method. +*/ +extern "C" void NetNtlmNative_NtlmFreeType2(ntlm_type2* type2); + +/* +Implements Type3 msg proccessing logic +*/ +extern "C" int32_t NetNtlmNative_CreateType3Message(const char* password, + ntlm_type2* type2, + char* username, + char* domain, + uint32_t flags, + struct PAL_NtlmBuffer* outSessionKey, + struct PAL_NtlmBuffer* outBuffer); 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 26ead2bb5698..67f81e270129 100644 --- a/src/Native/System.Security.Cryptography.Native/pal_evp_cipher.cpp +++ b/src/Native/System.Security.Cryptography.Native/pal_evp_cipher.cpp @@ -123,3 +123,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 db822f0d7237..b42215352bac 100644 --- a/src/Native/System.Security.Cryptography.Native/pal_evp_cipher.h +++ b/src/Native/System.Security.Cryptography.Native/pal_evp_cipher.h @@ -127,3 +127,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/System.Net.Security/src/Resources/Strings.resx b/src/System.Net.Security/src/Resources/Strings.resx index 3634df93afa9..066943f48b26 100644 --- a/src/System.Net.Security/src/Resources/Strings.resx +++ b/src/System.Net.Security/src/Resources/Strings.resx @@ -441,4 +441,7 @@ Server implementation is not supported + + NTLM operation failed with status: {0}) +