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.Net.Security.Native/Interop.Gss.cs b/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.Gss.cs new file mode 100644 index 000000000000..20fb2155aea6 --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.Gss.cs @@ -0,0 +1,357 @@ +// 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.Text; +using Microsoft.Win32.SafeHandles; +using size_t = System.IntPtr; + +internal static partial class Interop +{ + internal static partial class libgssapi + { + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern Status GssReleaseBuffer( + out Status minorStatus, + ref gss_buffer_desc buffer); + + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern Status GssDisplayStatus( + out Status minorStatus, + Status statusValue, + bool isGssMechCode, + SafeGssBufferHandle statusString); + + [DllImport(Interop.Libraries.SecurityNative, CharSet = CharSet.Ansi)] + internal static extern Status GssImportNtUserName( + out Status minorStatus, + string inputName, + out SafeGssNameHandle outputName); + + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern Status GssReleaseName( + out Status minorStatus, + ref IntPtr inputName); + + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern Status GssAcquireCredSpNego( + out Status minorStatus, + SafeGssNameHandle desiredName, + bool isInitiate, + out SafeGssCredHandle outputCredHandle); + + [DllImport(Interop.Libraries.SecurityNative, CharSet = CharSet.Ansi)] + internal static extern Status GssAcquireCredWithPasswordSpNego( + out Status minorStatus, + SafeGssNameHandle desiredName, + string password, + bool isInitiate, + out SafeGssCredHandle outputCredHandle); + + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern Status GssReleaseCred( + out Status minorStatus, + ref IntPtr credHandle); + + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern Status GssInitSecContextSpNego( + out Status minorStatus, + SafeGssCredHandle initiatorCredHandle, + ref SafeGssContextHandle contextHandle, + SafeGssNameHandle targetName, + uint reqFlags, + SafeGssBufferHandle inputToken, + SafeGssBufferHandle outputToken, + out uint retFlags); + + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern Status GssAcceptSecContext( + out Status minorStatus, + ref SafeGssContextHandle contextHandle, + SafeGssCredHandle initiatorCredHandle, + SafeGssBufferHandle inputToken, + SafeGssBufferHandle outputToken, + out uint retFlags); + + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern Status GssDeleteSecContext( + out Status minorStatus, + ref IntPtr contextHandle); + + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern Status GssWrap( + out Status minorStatus, + SafeGssContextHandle contextHandle, + bool isEncrypt, + SafeGssBufferHandle inputMessageBuffer, + SafeGssBufferHandle outputMessageBuffer); + + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern Status GssUnwrap( + out Status minorStatus, + SafeGssContextHandle contextHandle, + SafeGssBufferHandle inputMessageBuffer, + SafeGssBufferHandle outputMessageBuffer); + + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern Status GssInquireSourceName( + out Status minorStatus, + SafeGssContextHandle contextHandle, + out SafeGssNameHandle srcName); + + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern Status GssDisplayName( + out Status minorStatus, + SafeGssNameHandle inputName, + SafeGssBufferHandle outputNameBuffer); + + [StructLayout(LayoutKind.Sequential)] + internal struct gss_buffer_desc + { + internal size_t length; + internal IntPtr value; + } + + internal enum Status : uint + { + GSS_S_COMPLETE = 0, + GSS_S_CONTINUE_NEEDED = 1 + } + + [FlagsAttribute] + internal enum GssFlags : uint + { + GSS_C_DELEG_FLAG = 1, + GSS_C_MUTUAL_FLAG = 2, + GSS_C_REPLAY_FLAG = 4, + GSS_C_SEQUENCE_FLAG = 8, + GSS_C_CONF_FLAG = 16, + GSS_C_INTEG_FLAG = 32, + GSS_C_ANON_FLAG = 64, + GSS_C_PROT_READY_FLAG = 128, + GSS_C_TRANS_FLAG = 256, + GSS_C_DCE_STYLE = 4096, + GSS_C_IDENTIFY_FLAG = 8192, + GSS_C_EXTENDED_ERROR_FLAG = 16384, + GSS_C_DELEG_POLICY_FLAG = 32768 + } + } +} + +namespace Microsoft.Win32.SafeHandles +{ + /// + /// Wrapper around a gss_buffer_desc* + /// + internal sealed class SafeGssBufferHandle : SafeHandle + { + private GCHandle _gch; + private GCHandle _arrayGcHandle = new GCHandle(); + + // Return the buffer size + public int Length + { + get + { + if (IsInvalid) + { + return 0; + } + return (int)((Interop.libgssapi.gss_buffer_desc)_gch.Target).length; + } + } + + // Return a pointer to where data resides + public IntPtr Value + { + get + { + if (IsInvalid) + { + return IntPtr.Zero; + } + return ((Interop.libgssapi.gss_buffer_desc)_gch.Target).value; + } + } + + public SafeGssBufferHandle() + : this(0, IntPtr.Zero) + { + } + + public SafeGssBufferHandle(byte[] data) + : this(data, 0, (data == null) ? 0 : data.Length) + { + } + + public SafeGssBufferHandle(byte[] data, int offset, int count) + : this(count, IntPtr.Zero) + { + if (data == null) return; + _arrayGcHandle = GCHandle.Alloc(data, GCHandleType.Pinned); + IntPtr address = new IntPtr(_arrayGcHandle.AddrOfPinnedObject().ToInt64() + offset); + Marshal.WriteIntPtr(handle, (int)Marshal.OffsetOf("value"), address); + } + + private SafeGssBufferHandle(int length, IntPtr ptrValue) + : base(IntPtr.Zero, true) + { + Interop.libgssapi.gss_buffer_desc buffer = new Interop.libgssapi.gss_buffer_desc + { + length = (size_t)length, + value = ptrValue, + }; + + _gch = GCHandle.Alloc(buffer, GCHandleType.Pinned); + handle = _gch.AddrOfPinnedObject(); + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + + // Note that _value should never be freed directly. For input + // buffer, it is owned by the caller and for output buffer, + // it is owned by libgssapi + protected override bool ReleaseHandle() + { + Interop.libgssapi.gss_buffer_desc buffer = (Interop.libgssapi.gss_buffer_desc)_gch.Target; + if (buffer.value != IntPtr.Zero) + { + if (_arrayGcHandle.IsAllocated) + { + _arrayGcHandle.Free(); + } + else + { + Interop.libgssapi.Status minorStatus; + Interop.libgssapi.Status status = Interop.libgssapi.GssReleaseBuffer(out minorStatus, ref buffer); + Interop.libgssapi.GssApiException.AssertOrThrowIfError("GssReleaseBuffer failed", status, minorStatus); + } + } + _gch.Free(); + SetHandle(IntPtr.Zero); + return true; + } + } + + /// + /// Wrapper around a gss_name_t_desc* + /// + internal sealed class SafeGssNameHandle : SafeHandle + { + public static SafeGssNameHandle Create(string name) + { + Debug.Assert(!String.IsNullOrEmpty(name), "Invalid name passed to SafeGssNameHandle create"); + SafeGssNameHandle retHandle; + Interop.libgssapi.Status minorStatus; + Interop.libgssapi.Status status = Interop.libgssapi.GssImportNtUserName(out minorStatus, name, out retHandle); + if (status != Interop.libgssapi.Status.GSS_S_COMPLETE) + { + throw Interop.libgssapi.GssApiException.Create(status, minorStatus); + } + + return retHandle; + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + + protected override bool ReleaseHandle() + { + Interop.libgssapi.Status minorStatus; + Interop.libgssapi.Status status = Interop.libgssapi.GssReleaseName(out minorStatus, ref handle); + Interop.libgssapi.GssApiException.AssertOrThrowIfError("GssReleaseName failed", status, minorStatus); + SetHandle(IntPtr.Zero); + return true; + } + + private SafeGssNameHandle() + : base(IntPtr.Zero, true) + { + } + } + + /// + /// Wrapper around a gss_cred_id_t_desc_struct* + /// + internal class SafeGssCredHandle : SafeHandle + { + public static SafeGssCredHandle Create(string username, string password, string domain) + { + SafeGssCredHandle retHandle = null; + + // Empty username is OK if Kerberos ticket was already obtained + if (!String.IsNullOrEmpty(username)) + { + using (SafeGssNameHandle userHandle = SafeGssNameHandle.Create(username)) + { + Interop.libgssapi.Status status; + Interop.libgssapi.Status minorStatus; + if (String.IsNullOrEmpty(password)) + { + status = Interop.libgssapi.GssAcquireCredSpNego(out minorStatus, userHandle, true, out retHandle); + } + else + { + status = Interop.libgssapi.GssAcquireCredWithPasswordSpNego(out minorStatus, userHandle, password, true, out retHandle); + } + + if (status != Interop.libgssapi.Status.GSS_S_COMPLETE) + { + throw Interop.libgssapi.GssApiException.Create(status, minorStatus); + } + } + } + + return retHandle; + } + + protected SafeGssCredHandle() + : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + + protected override bool ReleaseHandle() + { + Interop.libgssapi.Status minorStatus; + Interop.libgssapi.Status status = Interop.libgssapi.GssReleaseCred(out minorStatus, ref handle); + Interop.libgssapi.GssApiException.AssertOrThrowIfError("GssReleaseCred failed", status, minorStatus); + SetHandle(IntPtr.Zero); + return true; + } + } + + internal sealed class SafeGssContextHandle : SafeHandle + { + public SafeGssContextHandle() + : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + + protected override bool ReleaseHandle() + { + Interop.libgssapi.Status minorStatus; + Interop.libgssapi.Status status = Interop.libgssapi.GssDeleteSecContext(out minorStatus, ref handle); + Interop.libgssapi.GssApiException.AssertOrThrowIfError("GssDeleteSecContext failed", status, minorStatus); + SetHandle(IntPtr.Zero); + return true; + } + } +} + diff --git a/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.Ntlm.cs b/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.Ntlm.cs new file mode 100644 index 000000000000..e7244d37cf4e --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.Ntlm.cs @@ -0,0 +1,558 @@ +// 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; +using size_t = System.IntPtr; + +internal static partial class Interop +{ + internal static partial class libheimntlm + { + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern int HeimNtlmFreeBuf(ntlm_buf data); + + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern int HeimNtlmEncodeType1(uint flags, SafeNtlmBufferHandle data); + + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern int HeimNtlmDecodeType2(SafeNtlmBufferHandle data, SafeNtlmType2Handle type2); + + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern int HeimNtlmFreeType2(IntPtr type2); + + [DllImport(Interop.Libraries.SecurityNative, CharSet = CharSet.Ansi)] + internal static extern unsafe int HeimNtlmNtKey(string password, SafeNtlmBufferHandle key); + + [DllImport(Interop.Libraries.SecurityNative, CharSet = CharSet.Ansi)] + internal static extern unsafe int HeimNtlmCalculateLm2( + IntPtr key, + size_t len, + string username, + string target, + SafeNtlmType2Handle type2, + byte[] ntlmv2, + SafeNtlmBufferHandle answer); + + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern unsafe int HeimNtlmCalculateNtlm1( + IntPtr key, + size_t len, + SafeNtlmType2Handle type2, + SafeNtlmBufferHandle answer, + string username, + string target, + byte[] ntlmv2 + ); + + + [DllImport(Interop.Libraries.SecurityNative, CharSet = CharSet.Ansi)] + internal static extern unsafe int HeimNtlmCalculateNtlm2( + IntPtr key, + size_t len, + string username, + string target, + byte* serverchallenge, + SafeNtlmBufferHandle infotarget, + byte [] ntlmv2, + SafeNtlmBufferHandle answer); + + //[DllImport(Interop.Libraries.SecurityNative)] + //internal static extern int HeimNtlmEncodeType3(ref ntlm_type3 type3, SafeNtlmBufferHandle data, + // ref size_t mic_offset); + + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern unsafe int HeimNtlmBuildNtlm1Master( + byte* key, + size_t len, + SafeNtlmBufferHandle session, + SafeNtlmBufferHandle master); + + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern unsafe void HeimNtlmBuildNtlm2Master( + byte* key, + size_t len, + SafeNtlmBufferHandle blob, + SafeNtlmBufferHandle session, + out SafeNtlmBufferHandle master); + + [DllImport(Interop.Libraries.SecurityNative)] + internal static extern unsafe void ProcessType3Message( + IntPtr username, + IntPtr domian, + uint flags, + SafeNtlmBufferHandle lm, + SafeNtlmBufferHandle ntlm, + SafeNtlmType2Handle type2Handle, + IntPtr key, + size_t len, + SafeNtlmBufferHandle ntResponse, + SafeNtlmBufferHandle session, + out SafeNtlmBufferHandle master, + byte [] baseSessionKey, + uint baseSeesionKeyLen, + SafeNtlmBufferHandle outputData + ); + + [DllImport(Interop.Libraries.CryptoNative)] + internal static extern IntPtr EvpMdCtxDestroy(SafeEvpMdCtxHandle ctx); + + [DllImport(Interop.Libraries.CryptoNative)] + private static extern void HmacDestroy(SafeHmacCtxHandle ctx); + + [DllImport(Interop.Libraries.CryptoNative)] + private static unsafe extern void RC4SetKey(ref RC4_KEY key, int len, byte[] data); + + [DllImport(Interop.Libraries.CryptoNative)] + private static unsafe extern void Rc4(ref RC4_KEY key, ulong len, byte* indata, byte* outdata); + + [DllImport(Interop.Libraries.CryptoNative)] + public static extern IntPtr EvpRc4(); + + [DllImport(Interop.Libraries.CryptoNative)] + private static extern int EvpCipherDestroy(SafeEvpCipherCtxHandle ctx); + + [DllImport(Interop.Libraries.CryptoNative)] + public static unsafe extern int EvpCipher(ref SafeEvpCipherCtxHandle ctx, byte[] output, byte* input, int inl); + + 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; + } + + + [StructLayout(LayoutKind.Sequential)] + internal struct ntlm_buf + { + internal size_t length; + internal IntPtr data; + } + + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct RC4_KEY + { + public int x; + public int y; + public fixed int data[256]; + } + + + internal static unsafe byte[] EVPDigest(SafeNtlmBufferHandle key, int keylen, byte[] input, int inputlen, out uint outputlen) + { + byte[] output = new byte[Interop.Crypto.EVP_MAX_MD_SIZE]; + outputlen = 0; + SafeEvpMdCtxHandle ctx = Interop.Crypto.EvpMdCtxCreate(Crypto.EvpMd5()); + try + { + Interop.Crypto.EvpDigestUpdate(ctx, (byte*)key.Value.ToPointer(), keylen); + fixed (byte* inPtr = input) + { + Interop.Crypto.EvpDigestUpdate(ctx, inPtr, inputlen); + } + fixed (byte* outPtr = output) + { + Interop.Crypto.EvpDigestFinalEx(ctx, outPtr, ref outputlen); + } + } + finally + { + EvpMdCtxDestroy(ctx); + } + return output; + } + + internal static unsafe byte[] EVPEncryptOrDecrypt(int x, bool encrypt, byte[] key, int keylen, byte* input, int inputlen) + { + if (x > 0) + { + SafeEvpCipherCtxHandle ctx = Interop.Crypto.EvpCipherCreate(EvpRc4(), key, null, encrypt? 1: 0); + try + { + byte[] output = new byte[inputlen]; + EvpCipher(ref ctx, output, input, output.Length); + return output; + } + finally + { + EVPFreeContext(ctx); + } + } + else + { + byte[] output = new byte[inputlen]; + RC4_KEY k = new RC4_KEY(); + RC4SetKey(ref k, keylen, key); + fixed (byte* outPtr = output) + { + Rc4(ref k, (ulong)inputlen, input, outPtr); + } + return output; + } + } + + internal static void EVPFreeContext(SafeEvpCipherCtxHandle ctx) + { + EvpCipherDestroy(ctx); + } + + internal static unsafe byte[] EVPEncryptOrDecrypt(SafeEvpCipherCtxHandle ctx, byte* input, int inputlen) + { + byte[] output = new byte[inputlen]; + + EvpCipher(ref ctx, output, input, output.Length); + return output; + } + + internal static unsafe byte[] HMACDigest(byte* key, int keylen, byte* input, int inputlen, byte* prefix, int prefixlen) + { + + SafeHmacCtxHandle ctx = null; + byte[] output = new byte[Interop.Crypto.EVP_MAX_MD_SIZE]; + try + { + ctx = Interop.Crypto.HmacCreate(key, keylen, Crypto.EvpMd5()); + if (prefixlen > 0) + { + Crypto.HmacUpdate(ctx, prefix, prefixlen); + } + Crypto.HmacUpdate(ctx, input, inputlen); + fixed (byte* hashPtr = output) + { + int hashLength = 0; + Crypto.HmacFinal(ctx, hashPtr, ref hashLength); + } + } + finally + { + HmacDestroy(ctx); + } + return output; + } + } + +} + +namespace Microsoft.Win32.SafeHandles +{ + /// + /// Wrapper around a ntlm_buf* + /// + internal sealed class SafeNtlmBufferHandle : SafeHandle + { + private readonly bool _isOutputBuffer; + private readonly GCHandle _gch; + private readonly GCHandle _arrayGcHandle = new GCHandle(); + + // Return the buffer size + public size_t Length + { + get + { + if (IsInvalid) + { + return (size_t) 0; + } + return ((Interop.libheimntlm.ntlm_buf) _gch.Target).length; + } + } + + // Return a pointer to where data resides + public IntPtr Value + { + get + { + if (IsInvalid) + { + return IntPtr.Zero; + } + return ((Interop.libheimntlm.ntlm_buf) _gch.Target).data; + } + } + + public SafeNtlmBufferHandle() + : this(0, IntPtr.Zero) + { + _isOutputBuffer = true; + } + + public SafeNtlmBufferHandle(byte[] data) : this(data, 0, (data == null) ? 0 : data.Length) + { + } + + public SafeNtlmBufferHandle(byte[] data, int offset, int count) : this(count, IntPtr.Zero) + { + if (data != null) + { + _arrayGcHandle = GCHandle.Alloc(data, GCHandleType.Pinned); + IntPtr address = new IntPtr(_arrayGcHandle.AddrOfPinnedObject().ToInt64() + offset); + Marshal.WriteIntPtr(handle, (int) Marshal.OffsetOf("data"), address); + } + } + + public SafeNtlmBufferHandle(int length, IntPtr value) + : base(IntPtr.Zero, true) + { + Interop.libheimntlm.ntlm_buf buffer = new Interop.libheimntlm.ntlm_buf + { + length = (size_t) length, + data = value, + }; + _gch = GCHandle.Alloc(buffer, GCHandleType.Pinned); + handle = _gch.AddrOfPinnedObject(); + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + + //public Interop.libheimntlm.ntlm_buf ToBuffer() + //{ + // return (Interop.libheimntlm.ntlm_buf) _gch.Target; + //} + + // Note that _value should never be freed directly. For input + // buffer, it is owned by the caller and for output buffer, + // it is a by-product of some other allocation + protected override bool ReleaseHandle() + { + Interop.libheimntlm.ntlm_buf buffer = (Interop.libheimntlm.ntlm_buf) _gch.Target; + + if (_isOutputBuffer && (buffer.data != IntPtr.Zero)) + { + buffer.data = IntPtr.Zero; + } + + if (_arrayGcHandle.IsAllocated) + { + _arrayGcHandle.Free(); + } + else + { + Interop.libheimntlm.HeimNtlmFreeBuf(buffer); + } + + _gch.Free(); + 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(SafeNtlmBufferHandle 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.libheimntlm.EVPDigest(key, (int) key.Length, magic, magic.Length, out _digestLength); + _isSealingKey = isSealingKey; + if (_isSealingKey) + { + _cipherContext = Interop.Crypto.EvpCipherCreate(Interop.libheimntlm.EvpRc4(), digest, null, 1); + } + _gch = GCHandle.Alloc(digest, GCHandleType.Pinned); + handle = _gch.AddrOfPinnedObject(); + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + + 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"); + byte[] output = new byte[16]; + Array.Clear(output, 0, output.Length); + byte[] hash; + unsafe + { + fixed (byte* outPtr = output) + fixed (byte* bytePtr = buffer) + { + MarshalUint(outPtr, 0x00000001); // version + MarshalUint(outPtr + 12, _sequenceNumber); + hash = Interop.libheimntlm.HMACDigest((byte*) handle.ToPointer(), (int)_digestLength, (bytePtr + offset), count, + outPtr + 12, 4); + _sequenceNumber++; + } + } + if ((sealingKey == null) || sealingKey.IsInvalid) + { + Array.Copy(hash, 0, output, 4, 8); + } + else + { + byte[] cipher = sealingKey.SealOrUnseal(true, hash, 0, 8); + Array.Copy(cipher, 0, output, 4, cipher.Length); + } + return output; + } + + public byte[] SealOrUnseal(bool seal, byte[] buffer, int offset, int count) + { + Debug.Assert(_isSealingKey, "Cannot seal or unseal with signing key"); + unsafe + { + fixed (byte* bytePtr = buffer) + { + // Since RC4 is XOR-based, encrypt or decrypt is relative to input data + byte[] output = new byte[count]; + + Interop.libheimntlm.EvpCipher(ref _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 SafeNtlmType2Handle() : base(IntPtr.Zero, true) + { + //do something + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + + protected override bool ReleaseHandle() + { + Interop.libheimntlm.HeimNtlmFreeType2(handle); + SetHandle(IntPtr.Zero); + return true; + } + } + + /// + /// Wrapper around a ntlm_type3* + /// + internal sealed class SafeNtlmType3Handle : SafeHandle + { + SafeNtlmType2Handle type2handle = new SafeNtlmType2Handle(); + public SafeNtlmType3Handle(SafeNtlmBufferHandle type2Data) : base(IntPtr.Zero, true) + { + Console.WriteLine("inside SafeNtlmType3Handle"); + int status = Interop.libheimntlm.HeimNtlmDecodeType2(type2Data, type2handle); + Console.WriteLine("status: "+ status); + Interop.libheimntlm.HeimdalNtlmException.AssertOrThrowIfError("heim_ntlm_decode_type2 failed", status); + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + + public SafeNtlmBufferHandle GetResponse(uint flags, string username, string password, string domain, + out SafeNtlmBufferHandle sessionKey) + { + SafeNtlmBufferHandle outputData = new SafeNtlmBufferHandle(); + sessionKey = null; + // Interop.libheimntlm.ntlm_type2 type2Message = (Interop.libheimntlm.ntlm_type2) _gch.Target; + + using (SafeNtlmBufferHandle key = new SafeNtlmBufferHandle()) + using (SafeNtlmBufferHandle lmResponse = new SafeNtlmBufferHandle()) + using (SafeNtlmBufferHandle ntResponse = new SafeNtlmBufferHandle()) + { + int status = Interop.libheimntlm.HeimNtlmNtKey(password, key); + Interop.libheimntlm.HeimdalNtlmException.AssertOrThrowIfError("heim_ntlm_nt_key failed", status); + byte[] baseSessionKey = new byte[16]; + status = Interop.libheimntlm.HeimNtlmCalculateLm2(key.Value, key.Length, username, domain,type2handle,baseSessionKey, lmResponse); + Interop.libheimntlm.HeimdalNtlmException.AssertOrThrowIfError("heim_ntlm_calculate_lm2 failed",status); + + status = Interop.libheimntlm.HeimNtlmCalculateNtlm1(key.Value, key.Length, type2handle, ntResponse, + username, domain, baseSessionKey); + Interop.libheimntlm.HeimdalNtlmException.AssertOrThrowIfError("heim_ntlm_calculate_ntlm1 failed", status); + SafeNtlmBufferHandle masterKey = null; + Interop.libheimntlm.ProcessType3Message(Marshal.StringToHGlobalAnsi(username), + Marshal.StringToHGlobalAnsi(domain), + flags, + lmResponse, + ntResponse, + type2handle, + key.Value, + key.Length, + ntResponse, + sessionKey, //TODO check who is setting this + out masterKey, + baseSessionKey, + (uint) baseSessionKey.Length, + outputData + ); + + + Interop.libheimntlm.HeimdalNtlmException.AssertOrThrowIfError( + "heim_ntlm_build_ntlm1_master failed", status); + + return outputData; + } + } + + + protected override bool ReleaseHandle() + { + Interop.libheimntlm.HeimNtlmFreeType2(handle); + SetHandle(IntPtr.Zero); + return true; + } + } +} + + + + + diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Hmac.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Hmac.cs index cfdd81cf52bf..d19d5a3ecb5e 100644 --- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Hmac.cs +++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Hmac.cs @@ -15,6 +15,9 @@ internal static partial class Crypto [DllImport(Libraries.CryptoNative)] internal extern static void HmacDestroy(IntPtr ctx); + [DllImport(Libraries.CryptoNative)] + internal extern static void HmacDestroy(SafeHmacCtxHandle ctx); + [DllImport(Libraries.CryptoNative)] internal extern static int HmacReset(SafeHmacCtxHandle ctx); diff --git a/src/Common/src/Interop/Unix/libgss/Interop.GssApi.cs b/src/Common/src/Interop/Unix/libgss/Interop.GssApi.cs new file mode 100644 index 000000000000..ffcb1e141367 --- /dev/null +++ b/src/Common/src/Interop/Unix/libgss/Interop.GssApi.cs @@ -0,0 +1,172 @@ +// 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.Text; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static class GssApi + { + internal static bool EstablishSecurityContext( + ref SafeGssContextHandle context, + SafeGssCredHandle credential, + SafeGssNameHandle targetName, + uint inFlags, + byte[] buffer, + out byte[] outputBuffer, + out uint outFlags) + { + outputBuffer = null; + outFlags = 0; + + if (context == null) + { + context = new SafeGssContextHandle(); + } + + using (SafeGssBufferHandle inputToken = new SafeGssBufferHandle(buffer)) + using (SafeGssBufferHandle outputToken = new SafeGssBufferHandle()) + { + libgssapi.Status status; + libgssapi.Status minorStatus; + + if (targetName == null) + { + status = libgssapi.GssAcceptSecContext( + out minorStatus, + ref context, + credential, + inputToken, + outputToken, + out outFlags); + } + else + { + status = libgssapi.GssInitSecContextSpNego( + out minorStatus, + credential, + ref context, + targetName, + inFlags, + inputToken, + outputToken, + out outFlags); + } + + if ((status != libgssapi.Status.GSS_S_COMPLETE) && (status != libgssapi.Status.GSS_S_CONTINUE_NEEDED)) + { + throw libgssapi.GssApiException.Create(SR.net_context_establishment_failed, status, minorStatus); + } + + outputBuffer = new byte[outputToken.Length]; // Always return non-null + if (outputToken.Length > 0) + { + Marshal.Copy(outputToken.Value, outputBuffer, 0, outputToken.Length); + } + return (status == libgssapi.Status.GSS_S_COMPLETE) ? true : false; + } + } + + internal static int Encrypt( + SafeGssContextHandle context, + bool encrypt, + byte[] buffer, + int offset, + int count, + out byte[] outputBuffer) + { + outputBuffer = null; + Debug.Assert((buffer != null) && (buffer.Length > 0), "Invalid input buffer passed to Encrypt"); + Debug.Assert((offset >= 0) && (offset < buffer.Length), "Invalid input offset passed to Encrypt"); + Debug.Assert((count > 0) && (count <= (buffer.Length - offset)), "Invalid input count passed to Encrypt"); + + using (SafeGssBufferHandle inputToken = new SafeGssBufferHandle(buffer, offset, count)) + using (SafeGssBufferHandle outputToken = new SafeGssBufferHandle()) + { + libgssapi.Status minorStatus; + libgssapi.Status status = libgssapi.GssWrap(out minorStatus, context, encrypt, inputToken, outputToken); + if (status != libgssapi.Status.GSS_S_COMPLETE) + { + throw libgssapi.GssApiException.Create(SR.net_context_wrap_failed, status, minorStatus); + } + outputBuffer = new byte[outputToken.Length]; // Always return non-null + if (outputToken.Length > 0) + { + Marshal.Copy(outputToken.Value, outputBuffer, 0, outputToken.Length); + } + return outputBuffer.Length; + } + } + + internal static int Decrypt( + SafeGssContextHandle context, + byte[] buffer, + int offset, + int count) + { + Debug.Assert((buffer != null) && (buffer.Length > 0), "Invalid input buffer passed to Decrypt"); + Debug.Assert((offset >= 0) && (offset < buffer.Length), "Invalid input offset passed to Decrypt"); + Debug.Assert((count > 0) && (count <= (buffer.Length - offset)), "Invalid input count passed to Decrypt"); + + using (SafeGssBufferHandle inputToken = new SafeGssBufferHandle(buffer, offset, count)) + using (SafeGssBufferHandle outputToken = new SafeGssBufferHandle()) + { + libgssapi.Status minorStatus; + libgssapi.Status status = libgssapi.GssUnwrap(out minorStatus, context, inputToken, outputToken); + if (status != libgssapi.Status.GSS_S_COMPLETE) + { + throw libgssapi.GssApiException.Create(SR.net_context_unwrap_failed, status, minorStatus); + } + + int length = buffer.Length - offset; + + if (outputToken.Length > length) + { + throw libgssapi.GssApiException.Create(SR.Format(SR.net_context_buffer_too_small, outputToken.Length, length)); + } + + if (outputToken.Length > 0) + { + Marshal.Copy(outputToken.Value, buffer, offset, outputToken.Length); + } + return outputToken.Length; + } + } + + internal static string GetSourceName(SafeGssContextHandle context) + { + libgssapi.Status minorStatus; + SafeGssNameHandle sourceName; + libgssapi.Status status = libgssapi.GssInquireSourceName( + out minorStatus, + context, + out sourceName); + + if (status != libgssapi.Status.GSS_S_COMPLETE) + { + throw libgssapi.GssApiException.Create(status, minorStatus); + } + + using (sourceName) + using (SafeGssBufferHandle outputBuffer = new SafeGssBufferHandle()) + { + status = libgssapi.GssDisplayName(out minorStatus, sourceName, outputBuffer); + if (status != libgssapi.Status.GSS_S_COMPLETE) + { + throw libgssapi.GssApiException.Create(status, minorStatus); + } + + // String may not be NULL terminated so PtrToStringAnsi cannot be used + unsafe + { + return Encoding.UTF8.GetString((byte*)outputBuffer.Value.ToPointer(), outputBuffer.Length); + } + } + } + } +} + diff --git a/src/Common/src/Interop/Unix/libgss/Interop.GssApiException.cs b/src/Common/src/Interop/Unix/libgss/Interop.GssApiException.cs new file mode 100644 index 000000000000..eaa66f1ca5a3 --- /dev/null +++ b/src/Common/src/Interop/Unix/libgss/Interop.GssApiException.cs @@ -0,0 +1,94 @@ +// 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 Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class libgssapi + { + internal sealed class GssApiException : Exception + { + private Status _minorStatus; + + public Status MinorStatus + { + get { return _minorStatus; } + } + + public GssApiException(string message) : base(message) + { + } + + public GssApiException(Status majorStatus, Status minorStatus) + : this(SR.Format(SR.net_gssapi_operation_failed, majorStatus, minorStatus), majorStatus, minorStatus) + { + } + + public GssApiException(string message, Status majorStatus, Status minorStatus) + : this(message) + { + HResult = (int)majorStatus; + _minorStatus = minorStatus; + } + + + public static GssApiException Create(string message) + { + return new GssApiException(message); + } + + public static GssApiException Create(Status majorStatus, Status minorStatus) + { + return new GssApiException(majorStatus, minorStatus); + } + + public static GssApiException Create(string message, Status majorStatus, Status minorStatus) + { + return new GssApiException(SR.Format(message, majorStatus, minorStatus), majorStatus, minorStatus); + } + + public static void AssertOrThrowIfError(string message, Status majorStatus, Status minorStatus) + { + if (majorStatus != Status.GSS_S_COMPLETE) + { + GssApiException ex = Create(majorStatus, minorStatus); + Debug.Fail(message + ": " + ex); + throw ex; + } + } + +#if DEBUG + public override string ToString() + { + return Message + "\n GSSAPI status: " + GetGssApiDisplayStatus((Status)HResult, _minorStatus); + } + + private static string GetGssApiDisplayStatus(Status majorStatus, Status minorStatus) + { + Tuple[] statusArr = { Tuple.Create(majorStatus, false), Tuple.Create(minorStatus, true) }; + int length = statusArr.Length; + string[] msgStrings = new string[length]; + + for (int i = 0; i < length; i++) + { + using (SafeGssBufferHandle msgBuffer = new SafeGssBufferHandle()) + { + Interop.libgssapi.Status minStat; + if (Status.GSS_S_COMPLETE != GssDisplayStatus(out minStat, statusArr[i].Item1, statusArr[i].Item2, msgBuffer)) + { + continue; + } + msgStrings[i] = Marshal.PtrToStringAnsi(msgBuffer.Value); + } + } + return msgStrings[0] + " (" + msgStrings[1] + ") \nStatus: " + majorStatus.ToString("x8") + " (" + minorStatus.ToString("x8") + ")"; + } + } + } +#endif + } + diff --git a/src/Common/src/Interop/Unix/libgss/SecuritySafeHandles.cs b/src/Common/src/Interop/Unix/libgss/SecuritySafeHandles.cs new file mode 100644 index 000000000000..37fcb0f07d8c --- /dev/null +++ b/src/Common/src/Interop/Unix/libgss/SecuritySafeHandles.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Win32.SafeHandles; + +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; + +namespace System.Net.Security +{ + + + internal sealed class SafeFreeGssCredentials :SafeGssCredHandle + { + public SafeFreeGssCredentials(string username, string password, string domain) + { + Create(username, password, domain); + } + } + + internal sealed class SafeDeleteGssContext : SafeHandle + { + private readonly SafeGssNameHandle _targetName; + private SafeFreeGssCredentials _credential; + private SafeGssContextHandle _context; + private bool _encryptAndSign; + + public SafeGssNameHandle TargetName + { + get { return _targetName; } + } + + public SafeGssContextHandle GssContext + { + get { return _context; } + } + + public bool NeedsEncryption + { + get { return _encryptAndSign; } + } + + public SafeDeleteGssContext(string targetName, uint flags) : base(IntPtr.Zero, true) + { + // In server case, targetName can be null or empty + if (!String.IsNullOrEmpty(targetName)) + { + _targetName = SafeGssNameHandle.Create(targetName); + } + + _encryptAndSign = (flags & (uint)Interop.libgssapi.GssFlags.GSS_C_CONF_FLAG) != 0; + } + + public override bool IsInvalid + { + get { return (null == _context) || _context.IsInvalid; } + } + + public void SetHandle(SafeFreeGssCredentials credential, SafeGssContextHandle context) + { + Debug.Assert(!context.IsInvalid, "Invalid context passed to SafeDeleteGssContext"); + _context = context; + + // After context establishment is initiated, callers expect SafeDeleteGssContext + // to bump up the ref count. + // NOTE: When using default credentials, the credential handle may be invalid + if ((null != credential) && !credential.IsInvalid) + { + bool ignore = false; + _credential = credential; + _credential.DangerousAddRef(ref ignore); + } + } + + protected override bool ReleaseHandle() + { + if ((null != _credential) && !_credential.IsInvalid) + { + _credential.DangerousRelease(); + } + _context.Dispose(); + if (_targetName != null) + { + _targetName.Dispose(); + } + return true; + } + } + +} diff --git a/src/Common/src/Interop/Unix/libheimntlm/Interop.HeimdalNtlm.cs b/src/Common/src/Interop/Unix/libheimntlm/Interop.HeimdalNtlm.cs new file mode 100644 index 000000000000..ebe7100a7768 --- /dev/null +++ b/src/Common/src/Interop/Unix/libheimntlm/Interop.HeimdalNtlm.cs @@ -0,0 +1,60 @@ +// 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) + { + using (SafeNtlmBufferHandle data = new SafeNtlmBufferHandle()) + { + int status = libheimntlm.HeimNtlmEncodeType1(flags, data); + libheimntlm.HeimdalNtlmException.AssertOrThrowIfError("heim_ntlm_encode_type1 failed", status); + + byte[] outputBuffer = new byte[(int)data.Length]; // Always return non-null + if (outputBuffer.Length > 0) + { + Marshal.Copy(data.Value, outputBuffer, 0, outputBuffer.Length); + } + + return outputBuffer; + } + } + + internal static byte[] CreateAuthenticateMessage(uint flags, string username, string password, string domain, + byte[] type2Data, int offset, int count, out SafeNtlmBufferHandle sessionKey) + { + using (SafeNtlmBufferHandle inputData = new SafeNtlmBufferHandle(type2Data, offset, count)) + { + using (SafeNtlmType3Handle outputMessage = new SafeNtlmType3Handle(inputData)) + { + using ( + SafeNtlmBufferHandle outputData = outputMessage.GetResponse(flags, username, password, domain, + out sessionKey)) + { + byte[] outputBuffer = new byte[(int) outputData.Length]; // Always return non-null + if (outputBuffer.Length > 0) + { + Marshal.Copy(outputData.Value, outputBuffer, 0, outputBuffer.Length); + } + return outputBuffer; + } + } + } + } + + internal static void CreateKeys(SafeNtlmBufferHandle sessionKey, out SafeNtlmKeyHandle serverSignKey, out SafeNtlmKeyHandle serverSealKey, out SafeNtlmKeyHandle clientSignKey, out SafeNtlmKeyHandle clientSealKey) + { + serverSignKey = new SafeNtlmKeyHandle(sessionKey, false, true); + serverSealKey = new SafeNtlmKeyHandle(sessionKey, false, false); + clientSignKey = new SafeNtlmKeyHandle(sessionKey, true, true); + clientSealKey = new SafeNtlmKeyHandle(sessionKey, true, false); + } + } +} + diff --git a/src/Common/src/Interop/Unix/libheimntlm/Interop.HeimdalNtlmException.cs b/src/Common/src/Interop/Unix/libheimntlm/Interop.HeimdalNtlmException.cs new file mode 100644 index 000000000000..25075bed71b8 --- /dev/null +++ b/src/Common/src/Interop/Unix/libheimntlm/Interop.HeimdalNtlmException.cs @@ -0,0 +1,47 @@ +// 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 libheimntlm + { + 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 HeimdalNtlmException Create(string message) + { + return new HeimdalNtlmException(message); + } + + public static HeimdalNtlmException Create(int error) + { + return new HeimdalNtlmException(error); + } + + public static void AssertOrThrowIfError(string message, int error) + { + if (error != 0) + { + var ex = Create(error); + Debug.Fail(message + ": " + ex); + throw ex; + } + } + } + } +} + diff --git a/src/Common/src/Interop/Unix/libheimntlm/SecuritySafeHandles.cs b/src/Common/src/Interop/Unix/libheimntlm/SecuritySafeHandles.cs new file mode 100644 index 000000000000..7800cc8b6c94 --- /dev/null +++ b/src/Common/src/Interop/Unix/libheimntlm/SecuritySafeHandles.cs @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Win32.SafeHandles; + +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; + +namespace System.Net.Security +{ + +internal sealed class SafeFreeNtlmCredentials : SafeHandle + { + private readonly string _username; + private readonly string _password; + private readonly string _domain; + + public string UserName + { + get { return _username; } + } + + public string Password + { + get { return _password; } + } + + public string Domain + { + get { return _domain; } + } + + public SafeFreeNtlmCredentials(string username, string password, string domain) + : base(IntPtr.Zero, false) + { + _username = username; + _password = password; + _domain = domain; + } + + public override bool IsInvalid + { + get { return false; } + } + + protected override bool ReleaseHandle() + { + return true; + } + } + + internal sealed class SafeDeleteNtlmContext : SafeHandle + { + private readonly SafeFreeNtlmCredentials _credential; + private readonly uint _flags; + private SafeNtlmKeyHandle _serverSignKey; + private SafeNtlmKeyHandle _serverSealKey; + private SafeNtlmKeyHandle _clientSignKey; + private SafeNtlmKeyHandle _clientSealKey; + + public uint Flags + { + get { return _flags; } + } + + public SafeDeleteNtlmContext(SafeFreeNtlmCredentials credential, uint flags) + : base(IntPtr.Zero, true) + { + bool ignore = false; + credential.DangerousAddRef(ref ignore); + _credential = credential; + _flags = flags; + } + + public override bool IsInvalid + { + get { return (null == _credential) || _credential.IsInvalid; } + } + + public void SetKeys(SafeNtlmBufferHandle sessionKey) + { + Interop.HeimdalNtlm.CreateKeys(sessionKey, out _serverSignKey, out _serverSealKey, out _clientSignKey, out _clientSealKey); + } + + public byte[] MakeSignature(bool isSend, byte[] buffer, int offset, int count) + { + if (isSend) + { + return _clientSignKey.Sign(_clientSealKey, buffer, offset, count); + } + else + { + return _serverSignKey.Sign(_serverSealKey, buffer, offset, count); + } + } + + public byte[] EncryptOrDecrypt(bool isEncrypt, byte[] buffer, int offset, int count) + { + if (isEncrypt) + { + return _clientSealKey.SealOrUnseal(true, buffer, offset, count); + } + else + { + return _serverSealKey.SealOrUnseal(false, buffer, offset, count); + } + } + + protected override bool ReleaseHandle() + { + _credential.DangerousRelease(); + if ((null != _clientSignKey) && !_clientSignKey.IsInvalid) + { + _clientSignKey.Dispose(); + } + if ((null != _clientSealKey) && !_clientSealKey.IsInvalid) + { + _clientSealKey.Dispose(); + } + if ((null != _serverSignKey) && !_serverSignKey.IsInvalid) + { + _serverSignKey.Dispose(); + } + if ((null != _serverSealKey) && !_serverSealKey.IsInvalid) + { + _serverSealKey.Dispose(); + } + return true; + } + } +} diff --git a/src/Native/CMakeLists.txt b/src/Native/CMakeLists.txt index a03cfea95ba0..82d6076d1840 100644 --- a/src/Native/CMakeLists.txt +++ b/src/Native/CMakeLists.txt @@ -82,7 +82,8 @@ endif () include(configure.cmake) -add_subdirectory(System.IO.Compression.Native) + add_subdirectory(System.Native) add_subdirectory(System.Net.Http.Native) +add_subdirectory(System.Net.Security.Native) add_subdirectory(System.Security.Cryptography.Native) 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..8cf87d646985 --- /dev/null +++ b/src/Native/System.Net.Security.Native/CMakeLists.txt @@ -0,0 +1,55 @@ +project(System.Net.Security.Native) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +add_definitions(-DPIC=1) + +if(APPLE) + add_definitions(-D__APPLE_USE_RFC_3542) + add_definitions(-DTARGET_OS_MAC=1) +else() + add_definitions(-DTARGET_OS_MAC=0) +endif() + + + +find_library(LIBGSS NAMES gssapi_krb5 PATHS /usr/lib/x86_64-linux-gnu/mit-krb5) + +if(LIBGSS STREQUAL LIBGSS-NOTFOUND) + + message(SEND_ERROR "!!! Cannot find libkrb5-dev and System.Net.Security.Native cannot build without it. Try installing libkrb5-dev (or the appropriate package for your platform) !!!") + return() +endif() + +find_library(HEIMDAL NAMES heimntlm) + +if(HEIMDAL STREQUAL HEIMDAL-NOTFOUND) + message(SEND_ERROR "!!! Cannot find heimdal-dev and System.Net.Security.Native cannot build without it. Try installing heimdal-dev (or the appropriate package for your platform) !!!") + return() +endif() + +set(NATIVENTLM_SOURCES + pal_ntlmapi.cpp +) + +set(NATIVEGSS_SOURCES + pal_gssapi.cpp +) + +include_directories(/usr/include) +include_directories(/usr/include/mit-krb5) +include_directories(/usr/include/heimdal) + +add_library(System.Net.Security.Native + SHARED + ${NATIVENTLM_SOURCES} + ${NATIVEGSS_SOURCES} +) + + +target_link_libraries(System.Net.Security.Native + ${HEIMDAL} + ${LIBGSS} +) + +install (TARGETS System.Net.Security.Native DESTINATION .) \ No newline at end of file diff --git a/src/Native/System.Net.Security.Native/pal_gssapi.cpp b/src/Native/System.Net.Security.Native/pal_gssapi.cpp new file mode 100644 index 000000000000..c5021cf5f420 --- /dev/null +++ b/src/Native/System.Net.Security.Native/pal_gssapi.cpp @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include "pal_gssapi.h" +#include +#include + +static_assert(PAL_GSS_COMPLETE == GSS_S_COMPLETE, ""); +static_assert(PAL_GSS_CONTINUE_NEEDED == GSS_S_CONTINUE_NEEDED, ""); + +static char gss_mech_value[] = "\x2b\x06\x01\x05\x05\x02"; + +static gss_OID_desc gss_mech_spnego_OID_desc = { 6, static_cast(gss_mech_value) }; + +static gss_OID gss_mech_spnego_OID = &gss_mech_spnego_OID_desc; + +static gss_OID_set_desc gss_mech_spnego_OID_set_desc = { 1, &gss_mech_spnego_OID_desc }; + +static gss_OID_set gss_mech_spnego_OID_set = &gss_mech_spnego_OID_set_desc; + +extern "C" uint32_t GssAcceptSecContext(uint32_t* minorStatus, gss_ctx_id_t* contextHandle, gss_cred_id_t acceptorCredHandle, + gss_buffer_t inputToken, gss_buffer_t outputToken, uint32_t* retFlags) +{ + return gss_accept_sec_context(minorStatus, contextHandle, acceptorCredHandle, inputToken, 0, NULL, NULL, outputToken, retFlags, NULL, NULL); +} + +extern "C" uint32_t GssAcquireCredSpNego(uint32_t* minorStatus, gss_name_t desiredName, bool isInitiate, gss_cred_id_t* outputCredHandle) +{ + gss_cred_usage_t credUsage = isInitiate ? GSS_C_INITIATE : GSS_C_ACCEPT; + return gss_acquire_cred(minorStatus, desiredName, 0, gss_mech_spnego_OID_set, credUsage, outputCredHandle, NULL, NULL); +} + +extern "C" uint32_t GssDeleteSecContext(uint32_t* minorStatus, gss_ctx_id_t* contextHandle) +{ + return gss_delete_sec_context(minorStatus, contextHandle, 0); +} + +extern "C" uint32_t GssDisplayName(uint32_t* minorStatus, gss_name_t inputName, gss_buffer_t outputNameBuffer) +{ + return gss_display_name(minorStatus, inputName, outputNameBuffer, NULL); +} + +extern "C" uint32_t GssDisplayStatus(uint32_t* minorStatus, uint32_t statusValue, bool isGssMechCode, gss_buffer_t statusString) +{ + int statusType = isGssMechCode ? GSS_C_MECH_CODE : GSS_C_GSS_CODE; + return gss_display_status(minorStatus, statusValue, statusType, 0, NULL, statusString); +} + +extern "C" uint32_t GssImportNtUserName(uint32_t* minorStatus, char* inputName, gss_name_t* outputName) +{ + gss_buffer_desc inputNameBuffer{ strlen(inputName), inputName }; + return gss_import_name(minorStatus, &inputNameBuffer, GSS_C_NT_USER_NAME, outputName); +} + +extern "C" uint32_t GssInitSecContextSpNego(uint32_t* minorStatus, gss_cred_id_t claimantCredHandle, gss_ctx_id_t* contextHandle, + gss_name_t targetName, uint32_t reqFlags, gss_buffer_t inputToken, gss_buffer_t outputToken, uint32_t* retFlags) +{ + return gss_init_sec_context(minorStatus, claimantCredHandle, contextHandle, targetName, gss_mech_spnego_OID, reqFlags, + 0, 0, inputToken, NULL, outputToken, retFlags, NULL); +} + +extern "C" uint32_t GssInquireSourceName(uint32_t* minorStatus, gss_ctx_id_t contextHandle, gss_name_t* srcName) +{ + return gss_inquire_context(minorStatus, contextHandle, srcName, NULL, NULL, NULL, NULL, NULL, NULL); +} + +extern "C" uint32_t GssReleaseCred(uint32_t* minorStatus, gss_cred_id_t* credHandle) +{ + return gss_release_cred(minorStatus, credHandle); +} + +extern "C" uint32_t GssReleaseBuffer(uint32_t* minor_status, gss_buffer_t buffer) +{ + return gss_release_buffer(minor_status, buffer); +} + +extern "C" uint32_t GssReleaseName(uint32_t* minorStatus, gss_name_t* inputName) +{ + return gss_release_name(minorStatus, inputName); +} + +extern "C" uint32_t GssWrap(uint32_t* minorStatus, gss_ctx_id_t contextHandle, bool isEncrypt, gss_buffer_t inputMessageBuffer, + gss_buffer_t outputMessageBuffer) +{ + int confState; + int confReqFlag = isEncrypt ? 1 : 0; + uint32_t retVal = gss_wrap(minorStatus, contextHandle, confReqFlag, GSS_C_QOP_DEFAULT, inputMessageBuffer, &confState, outputMessageBuffer); + assert((confState == 0) == (confReqFlag == 0)); + return retVal; +} + +extern "C" uint32_t GssUnwrap(uint32_t* minorStatus, gss_ctx_id_t contextHandle, gss_buffer_t inputMessageBuffer, + gss_buffer_t outputMessageBuffer) +{ + return gss_unwrap(minorStatus, contextHandle, inputMessageBuffer, outputMessageBuffer, NULL, NULL); +} + +extern "C" uint32_t GssAcquireCredWithPasswordSpNego(uint32_t* minorStatus, const gss_name_t desiredName, char* password, bool isInitiate, + gss_cred_id_t* outputCredHandle) +{ + gss_cred_usage_t credUsage = isInitiate ? GSS_C_INITIATE : GSS_C_ACCEPT; + gss_buffer_desc passwordBuffer{ strlen(password), password }; + return gss_acquire_cred_with_password(minorStatus, desiredName, &passwordBuffer, 0, gss_mech_spnego_OID_set, + credUsage, outputCredHandle, NULL, NULL); +} + diff --git a/src/Native/System.Net.Security.Native/pal_gssapi.h b/src/Native/System.Net.Security.Native/pal_gssapi.h new file mode 100644 index 000000000000..8edf6c675a74 --- /dev/null +++ b/src/Native/System.Net.Security.Native/pal_gssapi.h @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once +#include "pal_types.h" +#include + +enum GssStatus : uint32_t +{ + PAL_GSS_COMPLETE = 0, + PAL_GSS_CONTINUE_NEEDED = 1 +}; + +/* +Shims the gss_release_buffer method. +*/ +extern "C" uint32_t GssReleaseBuffer(uint32_t* minor_status, gss_buffer_t buffer); + +/* +Shims the gss_display_status method. +*/ +extern "C" uint32_t GssDisplayStatus(uint32_t* minorStatus, uint32_t statusValue, bool isGssMechCode, gss_buffer_t statusString); + +/* +Shims the gss_display_name method. +*/ +extern "C" uint32_t GssDisplayName(uint32_t* minorStatus, gss_name_t inputName, gss_buffer_t outputNameBuffer); + +/* +Shims the gss_import_name method. +This method will import NT type usernames only. +*/ +extern "C" uint32_t GssImportNtUserName(uint32_t* minorStatus, char* inputName, gss_name_t* outputName); + +/* +Shims the gss_release_name method. +*/ +extern "C" uint32_t GssReleaseName(uint32_t* minorStatus, gss_name_t* inputName); + +/* +Shims the gss_acquire_cred method with SPNEGO oids. +*/ +extern "C" uint32_t GssAcquireCredSpNego(uint32_t* minorStatus, gss_name_t desiredName, bool isInitiate, gss_cred_id_t* outputCredHandle); + +/* +Shims the gss_release_cred method. +*/ +extern "C" uint32_t GssReleaseCred(uint32_t* minorStatus, gss_cred_id_t* credHandle); + +/* +Shims the gss_init_sec_context method with SPNEGO oids. +*/ +extern "C" uint32_t GssInitSecContextSpNego(uint32_t* minorStatus, gss_cred_id_t claimantCredHandle, gss_ctx_id_t* contextHandle, + gss_name_t targetName, uint32_t reqFlags, gss_buffer_t inputToken, gss_buffer_t outputToken, uint32_t* retFlags); + +/* +Shims the gss_accept_sec_context method. +*/ +extern "C" uint32_t GssAcceptSecContext(uint32_t* minorStatus, gss_ctx_id_t* contextHandle, gss_cred_id_t acceptorCredHandle, + gss_buffer_t inputToken, gss_buffer_t outputToken, uint32_t* retFlags); + +/* +Shims the gss_delete_sec_context method. +*/ +extern "C" uint32_t GssDeleteSecContext(uint32_t* minorStatus, gss_ctx_id_t* contextHandle); + +/* +Shims the gss_wrap method. +*/ +extern "C" uint32_t GssWrap(uint32_t* minorStatus, gss_ctx_id_t contextHandle, bool isEncrypt, gss_buffer_t inputMessageBuffer, + gss_buffer_t outputMessageBuffer); + +/* +Shims the gss_unwrap method. +*/ +extern "C" uint32_t GssUnwrap(uint32_t* minorStatus, gss_ctx_id_t contextHandle, gss_buffer_t inputMessageBuffer, + gss_buffer_t outputMessageBuffer); + +/* +Shims the gss_inquire_context method. +*/ +extern "C" uint32_t GssInquireSourceName(uint32_t* minorStatus, gss_ctx_id_t contextHandle, gss_name_t* srcName); + + +/* +Shims the gss_acquire_cred_with_password method. +*/ +extern "C" uint32_t GssAcquireCredWithPasswordSpNego(uint32_t* minorStatus, const gss_name_t desiredName, char* password, bool isInitiate, + gss_cred_id_t* outputCredHandle); + 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..cb38b6dc925c --- /dev/null +++ b/src/Native/System.Net.Security.Native/pal_ntlmapi.cpp @@ -0,0 +1,120 @@ +// 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 "pal_ntlmapi.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, ""); + +extern "C" void HeimNtlmFreeBuf(ntlm_buf* data) +{ + heim_ntlm_free_buf(data); +} + +extern "C" int32_t HeimNtlmEncodeType1(uint32_t flags, ntlm_buf* data) +{ + + ntlm_type1 type1 = { 0 }; + type1.flags = flags; + return heim_ntlm_encode_type1(&type1, data); + +} + +extern "C" int32_t HeimNtlmEncodeType3(ntlm_type3* type3, ntlm_buf* data, size_t* size) +{ + return heim_ntlm_encode_type3(type3, data, size); +} + +extern "C" int32_t HeimNtlmDecodeType2(ntlm_buf* data, ntlm_type2* type2) +{ + return heim_ntlm_decode_type2(data, type2); +} + +extern "C" void HeimNtlmFreeType2(ntlm_type2* type2) +{ + heim_ntlm_free_type2(type2); +} + +extern "C" int32_t HeimNtlmCalculateLm2(const void * key , size_t len, const char* username, const char* target, ntlm_type2* type2, unsigned char* ntlmv2 , ntlm_buf * data) +{ + return heim_ntlm_calculate_lm2(key, len, username, target, type2->challenge, ntlmv2, data); +} + +extern "C" int32_t HeimNtlmCalculateNtlm1(void * key , size_t len, ntlm_type2* type2, ntlm_buf* data, const char* username, const char* target, unsigned char* ntlmv2) +{ + if (type2->targetinfo.length == 0) + { + return heim_ntlm_calculate_ntlm1(key, len, type2->challenge, data); + } + else + { + return heim_ntlm_calculate_ntlm2(key, len, username, target, type2->challenge, &type2->targetinfo, ntlmv2, data); + } + +} + +extern "C" int32_t HeimNtlmCalculateNtlm2(const void * key, size_t len, const char* username, const char* target, ntlm_type2* type2, unsigned char* ntlmv2, ntlm_buf* data) +{ + return heim_ntlm_calculate_ntlm2(key, len, username, target, type2->challenge, &type2->targetinfo, ntlmv2, data); +} + +extern "C" int32_t HeimNtlmBuildNtlm1Master(void * key, size_t size, ntlm_buf* session, ntlm_buf* master) +{ + return heim_ntlm_build_ntlm1_master(key, size, session, master); +} + +extern "C" int32_t HeimNtlmBuildNtlm2Master(void * key, size_t size, ntlm_buf* blob, ntlm_buf* session, ntlm_buf* master) +{ + return heim_ntlm_build_ntlm2_master(key, size, blob, session, master); +} + +extern "C" int32_t ProcessType3Message(char* username, char* domain, uint32_t flags, ntlm_buf* lm, ntlm_buf* ntlm, ntlm_type2* type2, void * key, size_t size, ntlm_buf* ntResponse, ntlm_buf* session, ntlm_buf* master, void * baseSessionKey, size_t baseSessionKeyLen, ntlm_buf* outputData) +{ + char* ws = nullptr; + ntlm_type3 type3 = { 0 }; + type3.username = username; + type3.targetname = domain; + type3.lm = *lm; + type3.ntlm = *ntlm; + type3.ws = ws; + type3.flags = flags; + + int32_t status = 0; + + if (type2->targetinfo.length == 0) + { + ntlm_buf* masterKey = (ntlm_buf *)malloc(sizeof(ntlm_buf)); + status = heim_ntlm_build_ntlm1_master(key, size, session, masterKey); + heim_ntlm_free_buf(masterKey); + heim_ntlm_free_buf(session); + if (status != 0) + return status; + } + else + { + // Only first 16 bytes of the NTLMv2 response should be passed + ntlm_buf blob = { 0 }; + blob.length = 16; + blob.data = ntResponse->data; + status = HeimNtlmBuildNtlm2Master(baseSessionKey, baseSessionKeyLen, &blob, session, master); + if (status != 0) + return status; + } + + type3.sessionkey = *master; + size_t micOffset = 0; + status = heim_ntlm_encode_type3(&type3, outputData, &micOffset); + if (status != 0) + return status; + + return 0; + } 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..e39c2bde2406 --- /dev/null +++ b/src/Native/System.Net.Security.Native/pal_ntlmapi.h @@ -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. + +#pragma once + +#include +#include +#include "pal_types.h" +#include "heimdal/heimntlm.h" + + +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, +}; + +/* +Shims heim_ntlm_free_buf method. +*/ +extern "C" void HeimNtlmFreeBuf(ntlm_buf* data); + +/* +Shims heim_ntlm_encode_type1 method. +*/ +extern "C" int32_t HeimNtlmEncodeType1(uint32_t flags, ntlm_buf* data); + +/* +Shims heim_ntlm_encode_type3 method. +*/ +extern "C" int32_t HeimNtlmEncodeType3(ntlm_type3* type3, ntlm_buf* data, size_t* size); + +/* +Shims heim_ntlm_decode_type2 method. +*/ +extern "C" int32_t HeimNtlmDecodeType2(ntlm_buf* data, ntlm_type2* type2); + +/* +Shims heim_ntlm_free_type2 method. +*/ +extern "C" void HeimNtlmFreeType2(ntlm_type2* type2); + +/* +Shims heim_ntlm_calculate_lm2 method. +*/ +extern "C" int32_t HeimNtlmCalculateLm2(const void * key, size_t len, const char* username, const char* target, ntlm_type2* type2, unsigned char* ntlmv2, ntlm_buf * data); + +/* +Shims heim_ntlm_calculate_ntlm1 method. +*/ +extern "C" int32_t HeimNtlmCalculateNtlm1(void * key, size_t len, ntlm_type2* type2, ntlm_buf* data, const char* username, const char* target, unsigned char* ntlmv2); + +/* +Shims the heim_ntlm_calculate_ntlm2 method. +*/ +extern "C" int32_t HeimNtlmCalculateNtlm2(const void * key, size_t len, const char* username, const char* target, ntlm_type2* type2, unsigned char* ntlmv2, ntlm_buf* data); + +/* +Shims heim_ntlm_build_ntlm1_master method. +*/ +extern "C" int32_t HeimNtlmBuildNtlm1Master(void * key, size_t size, ntlm_buf* session, ntlm_buf* master); + +/* +Shims heim_ntlm_build_ntlm2_master method. +*/ +extern "C" int32_t HeimNtlmBuildNtlm2Master(void * key, size_t size, ntlm_buf* blob, ntlm_buf* session, ntlm_buf* master); + +/* +Implements Typ3 msg proccessing logic +*/ +extern "C" int32_t ProcessType3Message(char* username, char* domain, uint32_t flags, ntlm_buf* lm, ntlm_buf* ntlm, ntlm_type2* type2, void * key, size_t size, ntlm_buf* ntResponse, ntlm_buf* session, ntlm_buf* master, void * baseSessionKey, size_t baseSessionKeyLen, ntlm_buf* outputData); 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 58156b1b06f2..252beb9123b3 100644 --- a/src/Native/System.Security.Cryptography.Native/pal_evp_cipher.cpp +++ b/src/Native/System.Security.Cryptography.Native/pal_evp_cipher.cpp @@ -9,6 +9,16 @@ #define SUCCESS 1 #define KEEP_CURRENT_DIRECTION -1 +extern "C" void EvpCipherInit(EVP_CIPHER_CTX* ctx) +{ + EVP_CIPHER_CTX_init(ctx); +} + +extern "C" void EvpCipher(EVP_CIPHER_CTX* ctx, unsigned char* out, const unsigned char *in, uint32_t inl) +{ + EVP_Cipher(ctx, out, in, inl); +} + extern "C" EVP_CIPHER_CTX* EvpCipherCreate(const EVP_CIPHER* type, uint8_t* key, unsigned char* iv, int32_t enc) { std::unique_ptr ctx(new (std::nothrow) EVP_CIPHER_CTX); @@ -110,3 +120,8 @@ extern "C" const EVP_CIPHER* EvpAes256Cbc() { return EVP_aes_256_cbc(); } + +extern "C" const EVP_CIPHER* EvpRc4() +{ + return EVP_rc4(); +} 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 4b39e7dd1aea..8f6a7c51fa82 100644 --- a/src/Native/System.Security.Cryptography.Native/pal_evp_cipher.h +++ b/src/Native/System.Security.Cryptography.Native/pal_evp_cipher.h @@ -3,6 +3,24 @@ #include "pal_types.h" #include +#include + +/* +Function: +EvpCipherInit + +Direct shim to EVP_CIPHER_CTX_init. +*/ +extern "C" void EvpCipherInit(EVP_CIPHER_CTX* ctx); + +/* +Function: +EvpCipher + +Direct shim to EVP_Cipher. +*/ +extern "C" void EvpCipher(EVP_CIPHER_CTX* ctx, unsigned char* out, const unsigned char *in, uint32_t inl); + /* Creates and initializes an EVP_CIPHER_CTX with the given args. @@ -108,3 +126,11 @@ EvpAes256Cbc Direct shim to EVP_aes_256_cbc. */ extern "C" const EVP_CIPHER* EvpAes256Cbc(); + +/* +Function: +EvpRc4 + +Direct shim to EVP_rc4. +*/ +extern "C" const EVP_CIPHER* EvpRc4(); diff --git a/src/System.Net.Security/src/Resources/Strings.resx b/src/System.Net.Security/src/Resources/Strings.resx index 56d698c86d86..3f73a2568b9c 100644 --- a/src/System.Net.Security/src/Resources/Strings.resx +++ b/src/System.Net.Security/src/Resources/Strings.resx @@ -375,4 +375,22 @@ SSL certificate returned is invalid, OpenSSL error - {0}. + + GSSAPI operation failed with status: {0} (Minor status: {1}) + + + GSSAPI security context establishment failed with status: {0} (Minor status: {1}) + + + GSSAPI encryption or signing failed with status: {0} (Minor status: {1}) + + + GSSAPI decryption or signature verification failed with status: {0} (Minor status: {1}) + + + Insufficient buffer space. Required: {0} Actual: {1} + + + Heimntlm operation failed with status: {0}) + diff --git a/src/System.Net.Security/src/System.Net.Security.csproj b/src/System.Net.Security/src/System.Net.Security.csproj index f2827a9c9e23..19efb0a93830 100644 --- a/src/System.Net.Security/src/System.Net.Security.csproj +++ b/src/System.Net.Security/src/System.Net.Security.csproj @@ -4,12 +4,14 @@ System.Net.Security 4.0.0.0 - Library + Exe {89F37791-6254-4D60-AB96-ACD3CCA0E771} true $(DefineConstants);FEATURE_CORECLR dnxcore50 true + v4.5 + Client win\project.json @@ -34,6 +36,9 @@ + + + @@ -209,6 +214,7 @@ + @@ -226,6 +232,23 @@ Common\Interop\Unix\libssl\Interop.OpenSsl.cs + + Common\Interop\Unix\System.Net.Security.Native\Interop.Ntlm.cs + + + + Common\Interop\Unix\libheimntlm\Interop.HeimdalNtlm.cs + + + Common\Interop\Unix\libheimntlm\Interop.HeimdalNtlmException.cs + + + Common\Interop\Unix\libheimntlm\SecuritySafeHandles.cs + + + Common\Interop\Unix\libgss\SecuritySafeHandles.cs + + Common\Interop\Unix\libssl\Interop.X509ChannelBindingHash.cs @@ -268,6 +291,19 @@ Common\Interop\Unix\System.Security.Cryptography.Native\Interop.X509StoreCtx.cs + + Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Hmac.cs + + + Common\Interop\Unix\System.Security.Cryptography.Native\Interop.EVP.cs + + + Microsoft\Win32\SafeHandles\SafeEvpMdCtxHandle.Unix.cs + + + + Common\Interop\Unix\System.Security.Cryptography.Native\Interop.EVP.Cipher.cs + Common\Interop\Unix\System.Net.Security.Native\Interop.Initialization.cs @@ -280,6 +316,9 @@ Common\Microsoft\Win32\SafeHandles\X509ExtensionSafeHandles.Unix.cs + + Common\Microsoft\Win32\SafeHandles\SafeEvpCipherCtxHandle.Unix.cs + Common\Microsoft\Win32\SafeHandles\SafeInteriorHandle.cs @@ -289,6 +328,20 @@ Common\Microsoft\Win32\SafeHandles\Asn1SafeHandles.Unix.cs + + Common\Microsoft\Win32\SafeHandles\SafeHmacCtxHandle.Unix.cs + + + + Common\Interop\Unix\System.Net.Security.Native\Interop.Gss.cs + + + Common\Interop\Unix\libgss\Interop.GssApi.cs + + + Common\Interop\Unix\libgss\Interop.GssApiException.cs + + diff --git a/src/System.Net.Security/src/System/Net/NegotiateStreamPal.Unix.cs b/src/System.Net.Security/src/System/Net/NegotiateStreamPal.Unix.cs new file mode 100644 index 000000000000..139e8581f84f --- /dev/null +++ b/src/System.Net.Security/src/System/Net/NegotiateStreamPal.Unix.cs @@ -0,0 +1,251 @@ +// 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.Net.Security; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + +namespace System.Net +{ + // Depending on PAL refactoring, this will either be part of a class that implements + // SSPIInterfaceNego or Unix-specific files (eg. _NTAuthenticationPal.Unix.cs) will + // call into methods of this class + internal static class NegotiateStreamPal + { + public static SecurityStatusPal AcquireCredentialsHandle( + string moduleName, + bool isInBoundCred, + string username, + string password, + string domain, + out SafeHandle outCredential) + { + if (isInBoundCred || string.IsNullOrEmpty(username)) + { + // In server case, only the keytab file (eg. /etc/krb5.keytab) is used + // In client case, equivalent of default credentials is to use previous, + // unexpired Kerberos TGT to get service-specific ticket. + outCredential = new SafeFreeGssCredentials(string.Empty, string.Empty, string.Empty); + } + else if (string.Equals(moduleName, "NTLM")) + { + outCredential = new SafeFreeNtlmCredentials(username, password, domain); + } + else + { + outCredential = new SafeFreeGssCredentials(username, password, domain); + // TODO (Issue #3717): Fall back to NTLM if Kerberos ticket cannot be obtained + } + return SecurityStatusPal.OK; + } + + public static SecurityStatusPal AcquireDefaultCredential(string moduleName, bool isInBoundCred, out SafeHandle outCredential) + { + return AcquireCredentialsHandle(moduleName, isInBoundCred, string.Empty, string.Empty, string.Empty, out outCredential); + } + + public static SecurityStatusPal AcceptSecurityContext( + SafeHandle credential, + ref SafeHandle context, + SecurityBuffer inputBuffer, + uint inFlags, + uint endianNess, + SecurityBuffer outputBuffer, + ref uint outFlags) + { + return EstablishSecurityContext((SafeFreeGssCredentials)credential, ref context, string.Empty, inFlags, inputBuffer, outputBuffer, ref outFlags); + } + + public static SecurityStatusPal InitializeSecurityContext( + SafeHandle credential, + ref SafeHandle context, + string targetName, + uint inFlags, + uint endianNess, + SecurityBuffer[] inputBuffers, + SecurityBuffer outputBuffer, + ref uint outFlags) + { + // TODO (Issue #3718): The second buffer can contain a channel binding which is not yet supported + if (inputBuffers.Length > 1) + { + throw new NotImplementedException("No support for channel binding on non-Windows"); + } + + if (IsNtlmClient(targetName, credential)) + { + return InitializeNtlmSecurityContext((SafeFreeNtlmCredentials)credential, ref context, inFlags, inputBuffers[0], outputBuffer); + } + return EstablishSecurityContext((SafeFreeGssCredentials)credential, ref context, targetName, inFlags, inputBuffers[0], outputBuffer, ref outFlags); + } + + public static int Encrypt(SafeHandle securityContext, byte[] buffer, int offset, int count, ref byte[] output, uint sequenceNumber) + { + if (securityContext is SafeDeleteGssContext) + { + // Sequence number is not used by libgssapi + SafeDeleteGssContext gssContext = securityContext as SafeDeleteGssContext; + return Interop.GssApi.Encrypt(gssContext.GssContext, gssContext.NeedsEncryption, buffer, offset, count, out output); + } + + // TODO (Issue# 3717): Figure out why sign verification on peer fails + SafeDeleteNtlmContext context = securityContext as SafeDeleteNtlmContext; + byte[] cipher = context.EncryptOrDecrypt(true, buffer, offset, count); + byte[] signature = context.MakeSignature(true, buffer, offset, count); + output = new byte[cipher.Length + signature.Length]; + Array.Copy(signature, 0, output, 0, signature.Length); + Array.Copy(cipher, 0, output, signature.Length, cipher.Length); + return output.Length; + } + + public static int Decrypt(SafeHandle securityContext, byte[] buffer, int offset, int count, out int newOffset, uint sequenceNumber) + { + if (securityContext is SafeDeleteGssContext) + { + // Sequence number is not used by libgssapi + newOffset = offset; + return Interop.GssApi.Decrypt(((SafeDeleteGssContext)securityContext).GssContext, buffer, offset, count); + } + SafeDeleteNtlmContext context = securityContext as SafeDeleteNtlmContext; + byte[] message = context.EncryptOrDecrypt(false, buffer, (offset + 16), (count - 16)); + Array.Copy(message, 0, buffer, (offset + 16), message.Length); + return VerifySignature(securityContext, buffer, offset, count, out newOffset, sequenceNumber); + } + + public static int MakeSignature(SafeHandle securityContext, byte[] buffer, int offset, int count, ref byte[] output, uint sequenceNumber) + { + if (securityContext is SafeDeleteGssContext) + { + // Sequence number is not used by libgssapi + SafeDeleteGssContext context = ((SafeDeleteGssContext)securityContext); + return Interop.GssApi.Encrypt(context.GssContext, context.NeedsEncryption, buffer, offset, count, out output); + } + byte[] signature = ((SafeDeleteNtlmContext) securityContext).MakeSignature(true, buffer, offset, count); + output = new byte[signature.Length + count]; + Array.Copy(signature, 0, output, 0, signature.Length); + Array.Copy(buffer, offset, output, signature.Length, count); + return output.Length; + } + + public static int VerifySignature(SafeHandle securityContext, byte[] buffer, int offset, int count, out int newOffset, uint sequenceNumber) + { + if (securityContext is SafeDeleteGssContext) + { + // Sequence number is not used by libgssapi + newOffset = offset; + return Interop.GssApi.Decrypt(((SafeDeleteGssContext)securityContext).GssContext, buffer, offset, count); + } + newOffset = offset + 16; + count -= 16; + byte[] signature = ((SafeDeleteNtlmContext) securityContext).MakeSignature(false, buffer, newOffset, count); + for (int i = 0; i < signature.Length; i++) + { + if (buffer[offset + i] != signature[i]) throw new Exception("Invalid signature"); + } + return count; + } + + public static object QueryContextAttributes(SafeDeleteGssContext context, uint attribute, out SecurityStatusPal errorCode) + { + errorCode = SecurityStatusPal.OK; + switch (attribute) + { + case 0x01: // Names + return Interop.GssApi.GetSourceName(context.GssContext); + case 0x0C: // NegotiationInfo + //NegotiationInfoClass negotiationInfoClass = new NegotiationInfoClass(context, Int32.MaxValue); + //negotiationInfoClass.AuthenticationPackage = NegotiationInfoClass.Kerberos; + //return negotiationInfoClass; + case 0: // Sizes + // Used only in the Encrypt/Decrypt logic + case 0x1B: // ClientSpecifiedSpn + // Required only in NTLM case with ExtendedProtection + default: + errorCode = SecurityStatusPal.Unsupported; + return null; + } + } + + private static bool IsNtlmClient(string targetName, SafeHandle credential) + { + return string.IsNullOrEmpty(targetName) || (credential is SafeFreeNtlmCredentials); + } + + private static SecurityStatusPal InitializeNtlmSecurityContext( + SafeFreeNtlmCredentials credential, + ref SafeHandle context, + uint inFlags, + SecurityBuffer inputBuffer, + SecurityBuffer outputBuffer) + { + SecurityStatusPal retVal; + + if (null == context) + { + context = new SafeDeleteNtlmContext(credential, inFlags); + outputBuffer.token = Interop.HeimdalNtlm.CreateNegotiateMessage(inFlags); + retVal = SecurityStatusPal.ContinueNeeded; + } + else + { + uint flags = ((SafeDeleteNtlmContext)context).Flags; + SafeNtlmBufferHandle sessionKey; + outputBuffer.token = Interop.HeimdalNtlm.CreateAuthenticateMessage(flags, credential.UserName, + credential.Password, credential.Domain, inputBuffer.token, inputBuffer.offset, inputBuffer.size, out sessionKey); + using (sessionKey) + { + ((SafeDeleteNtlmContext)context).SetKeys(sessionKey); + } + retVal = SecurityStatusPal.OK; + } + outputBuffer.size = outputBuffer.token.Length; + return retVal; + } + + private static SecurityStatusPal EstablishSecurityContext( + SafeFreeGssCredentials credential, + ref SafeHandle context, + string targetName, + uint inFlags, + SecurityBuffer inputBuffer, + SecurityBuffer outputBuffer, + ref uint outFlags) + { + if (context == null) + { + context = new SafeDeleteGssContext(targetName, inFlags); + } + + SafeDeleteGssContext gssContext = (SafeDeleteGssContext) context; + try + { + SafeGssContextHandle contextHandle = gssContext.GssContext; + bool done = Interop.GssApi.EstablishSecurityContext( + ref contextHandle, + credential, + gssContext.TargetName, + inFlags, + inputBuffer.token, + out outputBuffer.token, + out outFlags); + + Debug.Assert(outputBuffer.token != null, "Unexpected null buffer returned by GssApi"); + outputBuffer.size = outputBuffer.token.Length; + outputBuffer.offset = 0; + + // Save the inner context handle for further calls to libgssapi + if (gssContext.IsInvalid) + { + gssContext.SetHandle(credential, contextHandle); + } + return done ? SecurityStatusPal.OK : SecurityStatusPal.ContinueNeeded; + } + catch (Exception) + { + return SecurityStatusPal.InternalError; + } + } + } +}