From 65c01d406281fd1d036a7f9a0db817d69c8f9cc4 Mon Sep 17 00:00:00 2001 From: Hemanth Kapila Date: Mon, 18 Jan 2016 13:39:51 +0530 Subject: [PATCH] Interop Layer for supporting xplat NegotiateStream Introducing native shims for libgssapi in order to support NegotiateStream --- .../src/Interop/Unix/Interop.Libraries.cs | 1 + .../Interop.GssApi.cs | 128 ++++++++ .../Interop.GssApiException.cs | 61 ++++ .../Interop.GssBuffer.cs | 67 ++++ .../Interop.NetSecurityNative.cs | 126 ++++++++ .../Win32/SafeHandles/GssSafeHandles.cs | 160 ++++++++++ src/Native/Common/pal_config.h.in | 2 + .../System.Net.Security.Native/CMakeLists.txt | 30 ++ .../System.Net.Security.Native/pal_gssapi.cpp | 286 ++++++++++++++++++ .../System.Net.Security.Native/pal_gssapi.h | 139 +++++++++ src/Native/configure.cmake | 18 ++ .../src/Resources/Strings.resx | 22 +- 12 files changed, 1038 insertions(+), 2 deletions(-) create mode 100644 src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.GssApi.cs create mode 100644 src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.GssApiException.cs create mode 100644 src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.GssBuffer.cs create mode 100644 src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs create mode 100644 src/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.cs create mode 100644 src/Native/System.Net.Security.Native/CMakeLists.txt create mode 100644 src/Native/System.Net.Security.Native/pal_gssapi.cpp create mode 100644 src/Native/System.Net.Security.Native/pal_gssapi.h diff --git a/src/Common/src/Interop/Unix/Interop.Libraries.cs b/src/Common/src/Interop/Unix/Interop.Libraries.cs index 1a297d2310c8..cbe8fc1f5a8f 100644 --- a/src/Common/src/Interop/Unix/Interop.Libraries.cs +++ b/src/Common/src/Interop/Unix/Interop.Libraries.cs @@ -9,6 +9,7 @@ private static partial class Libraries // Shims internal const string SystemNative = "System.Native"; internal const string HttpNative = "System.Net.Http.Native"; + internal const string NetSecurityNative = "System.Net.Security.Native"; internal const string CryptoNative = "System.Security.Cryptography.Native"; internal const string GlobalizationNative = "System.Globalization.Native"; internal const string CompressionNative = "System.IO.Compression.Native"; diff --git a/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.GssApi.cs b/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.GssApi.cs new file mode 100644 index 000000000000..6fb0780ad120 --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.GssApi.cs @@ -0,0 +1,128 @@ +// 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.Text; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static class GssApi + { + internal static bool EstablishSecurityContext( + ref SafeGssContextHandle context, + SafeGssCredHandle credential, + bool isNtlm, + SafeGssNameHandle targetName, + Interop.NetSecurityNative.GssFlags inFlags, + byte[] buffer, + out byte[] outputBuffer, + out uint outFlags) + { + outputBuffer = null; + outFlags = 0; + + // EstablishSecurityContext is called multiple times in a session. + // In each call, we need to pass the context handle from the previous call. + // For the first call, the context handle will be null. + if (context == null) + { + context = new SafeGssContextHandle(); + } + + Interop.NetSecurityNative.GssBuffer token = default(Interop.NetSecurityNative.GssBuffer); + Interop.NetSecurityNative.Status status; + + try + { + Interop.NetSecurityNative.Status minorStatus; + status = NetSecurityNative.InitSecContext(out minorStatus, + credential, + ref context, + isNtlm, + targetName, + (uint)inFlags, + buffer, + (buffer == null) ? 0 : buffer.Length, + ref token, + out outFlags); + + if ((status != NetSecurityNative.Status.GSS_S_COMPLETE) && (status != NetSecurityNative.Status.GSS_S_CONTINUE_NEEDED)) + { + throw new NetSecurityNative.GssApiException(status, minorStatus); + } + + outputBuffer = token.ToByteArray(); + } + finally + { + token.Dispose(); + } + + return status == NetSecurityNative.Status.GSS_S_COMPLETE; + } + + internal static byte[] Encrypt( + SafeGssContextHandle context, + bool encrypt, + byte[] buffer, + int offset, + int count) + { + 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"); + + Interop.NetSecurityNative.GssBuffer encryptedBuffer = default(Interop.NetSecurityNative.GssBuffer); + try + { + + NetSecurityNative.Status minorStatus; + NetSecurityNative.Status status = NetSecurityNative.Wrap(out minorStatus, context, encrypt, buffer, offset, count, ref encryptedBuffer); + if (status != NetSecurityNative.Status.GSS_S_COMPLETE) + { + throw new NetSecurityNative.GssApiException(status, minorStatus); + } + + return encryptedBuffer.ToByteArray(); + } + finally + { + encryptedBuffer.Dispose(); + } + } + + 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"); + + Interop.NetSecurityNative.GssBuffer decryptedBuffer = default(Interop.NetSecurityNative.GssBuffer); + try + { + NetSecurityNative.Status minorStatus; + NetSecurityNative.Status status = NetSecurityNative.Unwrap(out minorStatus, context, buffer, offset, count, ref decryptedBuffer); + if (status != NetSecurityNative.Status.GSS_S_COMPLETE) + { + throw new NetSecurityNative.GssApiException(status, minorStatus); + } + + return decryptedBuffer.Copy(buffer, offset); + } + finally + { + decryptedBuffer.Dispose(); + } + } + } +} + + diff --git a/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.GssApiException.cs b/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.GssApiException.cs new file mode 100644 index 000000000000..298f90ab4a23 --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.GssApiException.cs @@ -0,0 +1,61 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class NetSecurityNative + { + internal sealed class GssApiException : Exception + { + private readonly Status _minorStatus; + + public Status MinorStatus + { + get { return _minorStatus; } + } + + public GssApiException(string message) : base(message) + { + } + + public GssApiException(Status majorStatus, Status minorStatus) + : base(GetGssApiDisplayStatus(majorStatus, minorStatus)) + { + HResult = (int)majorStatus; + _minorStatus = minorStatus; + } + + private static string GetGssApiDisplayStatus(Status majorStatus, Status minorStatus) + { + string majorError = GetGssApiDisplayStatus(majorStatus, false); + string minorError = GetGssApiDisplayStatus(minorStatus, true); + + return (majorError != null && minorError != null) ? + SR.Format(SR.net_gssapi_operation_failed_detailed, majorError, minorError) : + SR.Format(SR.net_gssapi_operation_failed, majorStatus.ToString("x8"), minorStatus.ToString("x8")); + } + + private static string GetGssApiDisplayStatus(Status status, bool isMinor) + { + GssBuffer displayBuffer = default(GssBuffer); + Interop.NetSecurityNative.Status minStat; + + try + { + Interop.NetSecurityNative.Status displayCallStatus = DisplayStatus(out minStat, status, isMinor, ref displayBuffer); + return (Status.GSS_S_COMPLETE != displayCallStatus) ? null : Marshal.PtrToStringAnsi(displayBuffer.data); + } + finally + { + displayBuffer.Dispose(); + } + } + } + } +} diff --git a/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.GssBuffer.cs b/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.GssBuffer.cs new file mode 100644 index 000000000000..943cbed20097 --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.GssBuffer.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 NetSecurityNative + { + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct GssBuffer : IDisposable + { + internal UInt64 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, "invalid offset " + offset); + + 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 NetSecurityNative.GssApiException(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.NetSecurityNative.ReleaseGssBuffer(data, length); + data = IntPtr.Zero; + } + + length = 0; + } + } + } +} + diff --git a/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs b/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs new file mode 100644 index 000000000000..fdb2133b300a --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs @@ -0,0 +1,126 @@ +// 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.Text; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class NetSecurityNative + { + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_ReleaseGssBuffer")] + internal static extern void ReleaseGssBuffer( + IntPtr bufferPtr, + UInt64 length); + + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_DisplayStatus")] + internal static extern Status DisplayStatus( + out Status minorStatus, + Status statusValue, + bool isGssMechCode, + ref GssBuffer buffer); + + [DllImport(Interop.Libraries.NetSecurityNative, CharSet = CharSet.Ansi, EntryPoint="NetSecurityNative_ImportUserName")] + internal static extern Status ImportUserName( + out Status minorStatus, + string inputName, + int inputNameLen, + out SafeGssNameHandle outputName); + + [DllImport(Interop.Libraries.NetSecurityNative, CharSet = CharSet.Ansi, EntryPoint="NetSecurityNative_ImportPrincipalName")] + internal static extern Status ImportPrincipalName( + out Status minorStatus, + string inputName, + int inputNameLen, + out SafeGssNameHandle outputName); + + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_ReleaseName")] + internal static extern Status ReleaseName( + out Status minorStatus, + ref IntPtr inputName); + + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_InitiateCredSpNego")] + internal static extern Status InitiateCredSpNego( + out Status minorStatus, + SafeGssNameHandle desiredName, + out SafeGssCredHandle outputCredHandle); + + [DllImport(Interop.Libraries.NetSecurityNative, CharSet = CharSet.Ansi, EntryPoint="NetSecurityNative_InitiateCredWithPassword")] + internal static extern Status InitiateCredWithPassword( + out Status minorStatus, + SafeGssNameHandle desiredName, + string password, + int passwordLen, + out SafeGssCredHandle outputCredHandle); + + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_ReleaseCred")] + internal static extern Status ReleaseCred( + out Status minorStatus, + ref IntPtr credHandle); + + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_InitSecContext")] + internal static extern Status InitSecContext( + out Status minorStatus, + SafeGssCredHandle initiatorCredHandle, + ref SafeGssContextHandle contextHandle, + bool isNtlm, + SafeGssNameHandle targetName, + uint reqFlags, + byte[] inputBytes, + int inputLength, + ref GssBuffer token, + out uint retFlags); + + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_DeleteSecContext")] + internal static extern Status DeleteSecContext( + out Status minorStatus, + ref IntPtr contextHandle); + + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_Wrap")] + internal static extern Status Wrap( + out Status minorStatus, + SafeGssContextHandle contextHandle, + bool isEncrypt, + byte[] inputBytes, + int offset, + int count, + ref GssBuffer outBuffer); + + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_Unwrap")] + internal static extern Status Unwrap( + out Status minorStatus, + SafeGssContextHandle contextHandle, + byte[] inputBytes, + int offset, + int count, + ref GssBuffer outBuffer); + + internal enum Status : uint + { + GSS_S_COMPLETE = 0, + GSS_S_CONTINUE_NEEDED = 1 + } + + [Flags] + internal enum GssFlags : uint + { + GSS_C_DELEG_FLAG = 0x1, + GSS_C_MUTUAL_FLAG = 0x2, + GSS_C_REPLAY_FLAG = 0x4, + GSS_C_SEQUENCE_FLAG = 0x8, + GSS_C_CONF_FLAG = 0x10, + GSS_C_INTEG_FLAG = 0x20, + GSS_C_ANON_FLAG = 0x40, + GSS_C_PROT_READY_FLAG = 0x80, + GSS_C_TRANS_FLAG = 0x100, + GSS_C_DCE_STYLE = 0x1000, + GSS_C_IDENTIFY_FLAG = 0x2000, + GSS_C_EXTENDED_ERROR_FLAG = 0x4000, + GSS_C_DELEG_POLICY_FLAG = 0x8000 + } + } +} diff --git a/src/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.cs b/src/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.cs new file mode 100644 index 000000000000..46cf6584a3db --- /dev/null +++ b/src/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.cs @@ -0,0 +1,160 @@ +// 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.Text; + +namespace Microsoft.Win32.SafeHandles +{ + /// + /// Wrapper around a gss_name_t_desc* + /// + internal sealed class SafeGssNameHandle : SafeHandle + { + public static SafeGssNameHandle CreateUser(string name) + { + Debug.Assert(!string.IsNullOrEmpty(name), "Invalid user name passed to SafeGssNameHandle create"); + SafeGssNameHandle retHandle; + Interop.NetSecurityNative.Status minorStatus; + Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.ImportUserName( + out minorStatus, name, Encoding.UTF8.GetByteCount(name), out retHandle); + + if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) + { + if (retHandle != null) + { + retHandle.Dispose(); + } + + throw new Interop.NetSecurityNative.GssApiException(status, minorStatus); + } + + return retHandle; + } + + public static SafeGssNameHandle CreatePrincipal(string name) + { + Debug.Assert(!string.IsNullOrEmpty(name), "Invalid principal passed to SafeGssNameHandle create"); + SafeGssNameHandle retHandle; + Interop.NetSecurityNative.Status minorStatus; + Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.ImportPrincipalName( + out minorStatus, name, Encoding.UTF8.GetByteCount(name), out retHandle); + + if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) + { + if (retHandle != null) + { + retHandle.Dispose(); + } + + throw new Interop.NetSecurityNative.GssApiException(status, minorStatus); + } + + return retHandle; + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + + protected override bool ReleaseHandle() + { + Interop.NetSecurityNative.Status minorStatus; + Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.ReleaseName(out minorStatus, ref handle); + SetHandle(IntPtr.Zero); + return status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE; + } + + private SafeGssNameHandle() + : base(IntPtr.Zero, true) + { + } + } + + /// + /// Wrapper around a gss_cred_id_t_desc_struct* + /// + internal class SafeGssCredHandle : SafeHandle + { + /// + /// returns the handle for the given credentials. + /// The method returns null if the username is null or empty. + /// + public static SafeGssCredHandle Create(string username, string password, string domain) + { + SafeGssCredHandle retHandle = null; + + if (!string.IsNullOrEmpty(username)) + { + using (SafeGssNameHandle userHandle = SafeGssNameHandle.CreateUser(username)) + { + Interop.NetSecurityNative.Status status; + Interop.NetSecurityNative.Status minorStatus; + if (string.IsNullOrEmpty(password)) + { + status = Interop.NetSecurityNative.InitiateCredSpNego(out minorStatus, userHandle, out retHandle); + } + else + { + status = Interop.NetSecurityNative.InitiateCredWithPassword(out minorStatus, userHandle, password, Encoding.UTF8.GetByteCount(password), out retHandle); + } + + if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) + { + if (retHandle != null) + { + retHandle.Dispose(); + } + + throw new Interop.NetSecurityNative.GssApiException(status, minorStatus); + } + } + } + + return retHandle; + } + + private SafeGssCredHandle() + : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + + protected override bool ReleaseHandle() + { + Interop.NetSecurityNative.Status minorStatus; + Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.ReleaseCred(out minorStatus, ref handle); + SetHandle(IntPtr.Zero); + return status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE; + } + } + + 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.NetSecurityNative.Status minorStatus; + Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.DeleteSecContext(out minorStatus, ref handle); + SetHandle(IntPtr.Zero); + return status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE; + } + } +} diff --git a/src/Native/Common/pal_config.h.in b/src/Native/Common/pal_config.h.in index ad7a5cc2869b..e4fcf0d25619 100644 --- a/src/Native/Common/pal_config.h.in +++ b/src/Native/Common/pal_config.h.in @@ -46,6 +46,8 @@ #cmakedefine01 HAVE_TCP_H_TCPSTATE_ENUM #cmakedefine01 HAVE_TCP_FSM_H #cmakedefine01 HAVE_OPEN_MAX +#cmakedefine01 HAVE_GSSFW_HEADERS +#cmakedefine01 HAVE_GSS_SPNEGO_MECHANISM // Mac OS X has stat64, but it is deprecated since plain stat now // provides the same 64-bit aware struct when targeting OS X > 10.5 diff --git a/src/Native/System.Net.Security.Native/CMakeLists.txt b/src/Native/System.Net.Security.Native/CMakeLists.txt new file mode 100644 index 000000000000..29a3e6a5d482 --- /dev/null +++ b/src/Native/System.Net.Security.Native/CMakeLists.txt @@ -0,0 +1,30 @@ +project(System.Net.Security.Native) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +add_definitions(-DPIC=1) + +if (HAVE_GSSFW_HEADERS) + find_library(LIBGSS NAMES GSS) +else() + find_library(LIBGSS NAMES gssapi_krb5) +endif() + +if(LIBGSS STREQUAL LIBGSS-NOTFOUND) + message(FATAL_ERROR "Cannot find libgssapi_krb5 and System.Net.Security.Native cannot build without it. Try installing libkrb5-dev (or the appropriate package for your platform)") +endif() + +set(NATIVEGSS_SOURCES + pal_gssapi.cpp +) + +add_library(System.Net.Security.Native + SHARED + ${NATIVEGSS_SOURCES} +) + +target_link_libraries(System.Net.Security.Native + ${LIBGSS} +) + +install (TARGETS System.Net.Security.Native DESTINATION .) 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..9189a736a61f --- /dev/null +++ b/src/Native/System.Net.Security.Native/pal_gssapi.cpp @@ -0,0 +1,286 @@ +// 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 "pal_types.h" +#include "pal_utilities.h" +#include "pal_gssapi.h" + +#if HAVE_GSSFW_HEADERS +#include +#else +#include +#include +#endif + +#include +#include + +static_assert(PAL_GSS_C_DELEG_FLAG == GSS_C_DELEG_FLAG, ""); +static_assert(PAL_GSS_C_MUTUAL_FLAG == GSS_C_MUTUAL_FLAG, ""); +static_assert(PAL_GSS_C_REPLAY_FLAG == GSS_C_REPLAY_FLAG, ""); +static_assert(PAL_GSS_C_SEQUENCE_FLAG == GSS_C_SEQUENCE_FLAG, ""); +static_assert(PAL_GSS_C_CONF_FLAG == GSS_C_CONF_FLAG, ""); +static_assert(PAL_GSS_C_INTEG_FLAG == GSS_C_INTEG_FLAG, ""); +static_assert(PAL_GSS_C_ANON_FLAG == GSS_C_ANON_FLAG, ""); +static_assert(PAL_GSS_C_PROT_READY_FLAG == GSS_C_PROT_READY_FLAG, ""); +static_assert(PAL_GSS_C_TRANS_FLAG == GSS_C_TRANS_FLAG, ""); +static_assert(PAL_GSS_C_DCE_STYLE == GSS_C_DCE_STYLE, ""); +static_assert(PAL_GSS_C_IDENTIFY_FLAG == GSS_C_IDENTIFY_FLAG, ""); +static_assert(PAL_GSS_C_EXTENDED_ERROR_FLAG == GSS_C_EXTENDED_ERROR_FLAG, ""); +static_assert(PAL_GSS_C_DELEG_POLICY_FLAG == GSS_C_DELEG_POLICY_FLAG, ""); + +static_assert(PAL_GSS_COMPLETE == GSS_S_COMPLETE, ""); +static_assert(PAL_GSS_CONTINUE_NEEDED == GSS_S_CONTINUE_NEEDED, ""); + +#if !HAVE_GSS_SPNEGO_MECHANISM +static char gss_mech_value[] = "\x2b\x06\x01\x05\x05\x02"; // Binary representation of SPNEGO Oid (RFC 4178) +#endif + +static uint32_t NetSecurityNative_HandleError(uint32_t majorStatus, + gss_buffer_t gssBuffer, + struct PAL_GssBuffer *targetBuffer) +{ + assert(targetBuffer != nullptr); + assert(gssBuffer != nullptr); + if (GSS_ERROR(majorStatus)) + { + assert (gssBuffer->value == nullptr); + targetBuffer->length = 0; + targetBuffer->data = nullptr; + } + else + { + assert(gssBuffer->value != nullptr || gssBuffer->length == 0); + targetBuffer->length = gssBuffer->length; + targetBuffer->data = static_cast(gssBuffer->value); + } + + return majorStatus; +} + +static uint32_t NetSecurityNative_AcquireCredSpNego(uint32_t* minorStatus, + GssName* desiredName, + gss_cred_usage_t credUsage, + GssCredId** outputCredHandle) +{ + assert(minorStatus != nullptr); + assert(desiredName != nullptr); + assert(outputCredHandle != nullptr); +#if HAVE_GSS_SPNEGO_MECHANISM + gss_OID_set_desc gss_mech_spnego_OID_set_desc = {.count = 1, .elements = GSS_SPNEGO_MECHANISM}; +#else + gss_OID_desc gss_mech_spnego_OID_desc = {.length = 6, .elements = static_cast(gss_mech_value)}; + gss_OID_set_desc gss_mech_spnego_OID_set_desc = {.count = 1, .elements = &gss_mech_spnego_OID_desc}; +#endif + return gss_acquire_cred( + minorStatus, desiredName, 0, &gss_mech_spnego_OID_set_desc, credUsage, outputCredHandle, nullptr, nullptr); +} + +extern "C" uint32_t NetSecurityNative_InitiateCredSpNego(uint32_t* minorStatus, + GssName* desiredName, + GssCredId** outputCredHandle) +{ + return NetSecurityNative_AcquireCredSpNego(minorStatus, desiredName, GSS_C_INITIATE, outputCredHandle); +} + +extern "C" uint32_t NetSecurityNative_DeleteSecContext(uint32_t* minorStatus, GssCtxId** contextHandle) +{ + assert(minorStatus != nullptr); + assert(contextHandle != nullptr); + + return gss_delete_sec_context(minorStatus, contextHandle, GSS_C_NO_BUFFER); +} + +extern "C" uint32_t NetSecurityNative_DisplayStatus(uint32_t* minorStatus, + uint32_t statusValue, + int32_t isGssMechCode, + struct PAL_GssBuffer* outBuffer) +{ + assert(minorStatus != nullptr); + assert(isGssMechCode == 0 || isGssMechCode == 1); + assert(outBuffer != nullptr); + + int statusType = isGssMechCode ? GSS_C_MECH_CODE : GSS_C_GSS_CODE; + GssBuffer gssBuffer {.length = 0, .value = nullptr}; + uint32_t majorStatus = + gss_display_status(minorStatus, statusValue, statusType, GSS_C_NO_OID, nullptr, &gssBuffer); + return NetSecurityNative_HandleError(majorStatus, &gssBuffer, outBuffer); +} + +extern "C" uint32_t +NetSecurityNative_ImportUserName(uint32_t* minorStatus, char* inputName, uint32_t inputNameLen, GssName** outputName) +{ + assert(minorStatus != nullptr); + assert(outputName != nullptr); + assert(inputName != nullptr); + assert(outputName != nullptr); + + GssBuffer inputNameBuffer{.length = inputNameLen, .value = inputName}; + gss_OID nameType = const_cast(GSS_C_NT_USER_NAME); + return gss_import_name(minorStatus, &inputNameBuffer, nameType, outputName); +} + +extern "C" uint32_t NetSecurityNative_ImportPrincipalName(uint32_t* minorStatus, + char* inputName, + uint32_t inputNameLen, + GssName** outputName) +{ + assert(minorStatus != nullptr); + assert(inputName != nullptr); + assert(outputName != nullptr); + + GssBuffer inputNameBuffer{.length = inputNameLen, .value = inputName}; + gss_OID nameType = const_cast(GSS_KRB5_NT_PRINCIPAL_NAME); + return gss_import_name(minorStatus, &inputNameBuffer, nameType, outputName); +} + +extern "C" uint32_t NetSecurityNative_InitSecContext(uint32_t* minorStatus, + GssCredId* claimantCredHandle, + GssCtxId** contextHandle, + uint32_t isNtlm, + GssName* targetName, + uint32_t reqFlags, + uint8_t* inputBytes, + uint32_t inputLength, + struct PAL_GssBuffer* outBuffer, + uint32_t* retFlags) +{ + assert(minorStatus != nullptr); + assert(claimantCredHandle != nullptr); + assert(contextHandle != nullptr); + assert(isNtlm == 0 || isNtlm == 1); + assert(targetName != nullptr); + assert(contextHandle != nullptr); + assert(outBuffer != nullptr); + assert(retFlags != nullptr); + assert (inputBytes != nullptr || inputLength == 0); + +#if HAVE_GSS_SPNEGO_MECHANISM + gss_OID desiredMech = isNtlm ? GSS_NTLM_MECHANISM : GSS_SPNEGO_MECHANISM; +#else + assert(!isNtlm && "NTLM is not supported by MIT libgssapi_krb5"); + gss_OID_desc gss_mech_spnego_OID_desc = {.length = 6, .elements = static_cast(gss_mech_value)}; + gss_OID desiredMech = &gss_mech_spnego_OID_desc; +#endif + + GssBuffer inputToken {.length = UnsignedCast(inputLength), .value = inputBytes}; + GssBuffer gssBuffer { .length = 0, .value = nullptr }; + + uint32_t majorStatus = gss_init_sec_context(minorStatus, + claimantCredHandle, + contextHandle, + targetName, + desiredMech, + reqFlags, + 0, + GSS_C_NO_CHANNEL_BINDINGS, + &inputToken, + nullptr, + &gssBuffer, + retFlags, + nullptr); + + return NetSecurityNative_HandleError(majorStatus, &gssBuffer, outBuffer); +} + +extern "C" uint32_t NetSecurityNative_ReleaseCred(uint32_t* minorStatus, GssCredId** credHandle) +{ + assert(minorStatus != nullptr); + assert(credHandle != nullptr); + + return gss_release_cred(minorStatus, credHandle); +} + +extern "C" void NetSecurityNative_ReleaseGssBuffer(void* buffer, uint64_t length) +{ + assert(buffer != nullptr); + + uint32_t minorStatus; + GssBuffer gssBuffer {.length = length, .value = buffer}; + gss_release_buffer(&minorStatus, &gssBuffer); +} + +extern "C" uint32_t NetSecurityNative_ReleaseName(uint32_t* minorStatus, GssName** inputName) +{ + assert(minorStatus != nullptr); + assert(inputName != nullptr); + + return gss_release_name(minorStatus, inputName); +} + +extern "C" uint32_t NetSecurityNative_Wrap(uint32_t* minorStatus, + GssCtxId* contextHandle, + int32_t isEncrypt, + uint8_t* inputBytes, + int32_t offset, + int32_t count, + struct PAL_GssBuffer* outBuffer) +{ + assert(minorStatus != nullptr); + assert(contextHandle != nullptr); + assert(isEncrypt == 1 || isEncrypt == 0); + assert(inputBytes != nullptr); + assert(offset >= 0); + assert(count >= 0); + assert(outBuffer != nullptr); + // count refers to the length of the input message. That is, number of bytes of inputBytes + // starting at offset + // that need to be wrapped. + + int confState; + GssBuffer inputMessageBuffer{.length = UnsignedCast(count), .value = inputBytes + offset}; + GssBuffer gssBuffer; + uint32_t majorStatus = gss_wrap( + minorStatus, contextHandle, isEncrypt, GSS_C_QOP_DEFAULT, &inputMessageBuffer, &confState, &gssBuffer); + return NetSecurityNative_HandleError(majorStatus, &gssBuffer, outBuffer); +} + +extern "C" uint32_t NetSecurityNative_Unwrap(uint32_t* minorStatus, + GssCtxId* contextHandle, + uint8_t* inputBytes, + int32_t offset, + int32_t count, + struct PAL_GssBuffer* outBuffer) +{ + assert(minorStatus != nullptr); + assert(contextHandle != nullptr); + assert(inputBytes != nullptr); + assert(offset >= 0); + assert(count >= 0); + assert(outBuffer != nullptr); + + // count refers to the length of the input message. That is, the number of bytes of inputBytes + // starting at offset that need to be wrapped. + GssBuffer inputMessageBuffer{.length = UnsignedCast(count), .value = inputBytes + offset}; + GssBuffer gssBuffer {.length = 0, .value = nullptr}; + uint32_t majorStatus = + gss_unwrap(minorStatus, contextHandle, &inputMessageBuffer, &gssBuffer, nullptr, nullptr); + return NetSecurityNative_HandleError(majorStatus, &gssBuffer, outBuffer); +} + +static uint32_t NetSecurityNative_AcquireCredWithPassword(uint32_t* minorStatus, + GssName* desiredName, + char* password, + uint32_t passwdLen, + gss_cred_usage_t credUsage, + GssCredId** outputCredHandle) +{ + assert(minorStatus != nullptr); + assert(desiredName != nullptr); + assert(password != nullptr); + assert(outputCredHandle != nullptr); + + GssBuffer passwordBuffer{.length = passwdLen, .value = password}; + return gss_acquire_cred_with_password( + minorStatus, desiredName, &passwordBuffer, 0, nullptr, credUsage, outputCredHandle, nullptr, nullptr); +} + +extern "C" uint32_t NetSecurityNative_InitiateCredWithPassword(uint32_t* minorStatus, + GssName* desiredName, + char* password, + uint32_t passwdLen, + GssCredId** outputCredHandle) +{ + return NetSecurityNative_AcquireCredWithPassword(minorStatus, desiredName, password, passwdLen, GSS_C_INITIATE, outputCredHandle); +} 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..9619196949b7 --- /dev/null +++ b/src/Native/System.Net.Security.Native/pal_gssapi.h @@ -0,0 +1,139 @@ +// 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 + +#if HAVE_GSSFW_HEADERS +typedef struct gss_name_t_desc_struct GssName; +typedef struct gss_ctx_id_t_desc_struct GssCtxId; +typedef struct gss_cred_id_t_desc_struct GssCredId; +typedef struct gss_buffer_desc_struct GssBuffer; +#else +typedef struct gss_name_struct GssName; +typedef struct gss_ctx_id_struct GssCtxId; +typedef struct gss_cred_id_struct GssCredId; +typedef struct gss_buffer_desc_struct GssBuffer; +#endif + +enum PAL_GssStatus : uint32_t +{ + PAL_GSS_COMPLETE = 0, + PAL_GSS_CONTINUE_NEEDED = 1 +}; + +enum PAL_GssFlags : uint32_t +{ + PAL_GSS_C_DELEG_FLAG = 0x1, + PAL_GSS_C_MUTUAL_FLAG = 0x2, + PAL_GSS_C_REPLAY_FLAG = 0x4, + PAL_GSS_C_SEQUENCE_FLAG = 0x8, + PAL_GSS_C_CONF_FLAG = 0x10, + PAL_GSS_C_INTEG_FLAG = 0x20, + PAL_GSS_C_ANON_FLAG = 0x40, + PAL_GSS_C_PROT_READY_FLAG = 0x80, + PAL_GSS_C_TRANS_FLAG = 0x100, + PAL_GSS_C_DCE_STYLE = 0x1000, + PAL_GSS_C_IDENTIFY_FLAG = 0x2000, + PAL_GSS_C_EXTENDED_ERROR_FLAG = 0x4000, + PAL_GSS_C_DELEG_POLICY_FLAG = 0x8000 +}; + +struct PAL_GssBuffer +{ + uint64_t length; + uint8_t* data; +}; + +/* +Shims the gss_release_buffer +*/ +extern "C" void NetSecurityNative_ReleaseGssBuffer(void* buffer, uint64_t length); + +/* +Shims the gss_display_status method. +*/ +extern "C" uint32_t NetSecurityNative_DisplayStatus(uint32_t* minorStatus, + uint32_t statusValue, + int32_t isGssMechCode, + struct PAL_GssBuffer* outBuffer); + +/* +Shims the gss_import_name method with nametype = GSS_C_NT_USER_NAME. +*/ +extern "C" uint32_t +NetSecurityNative_ImportUserName(uint32_t* minorStatus, char* inputName, uint32_t inputNameLen, GssName** outputName); + +/* +Shims the gss_import_name method with nametype = GSS_C_NT_USER_NAME. +*/ +extern "C" uint32_t NetSecurityNative_ImportPrincipalName(uint32_t* minorStatus, + char* inputName, + uint32_t inputNameLen, + GssName** outputName); + +/* +Shims the gss_release_name method. +*/ +extern "C" uint32_t NetSecurityNative_ReleaseName(uint32_t* minorStatus, GssName** inputName); + +/* +Shims the gss_acquire_cred method with SPNEGO oids with GSS_C_INITIATE +*/ +extern "C" uint32_t NetSecurityNative_InitiateCredSpNego(uint32_t* minorStatus, + GssName* desiredName, + GssCredId** outputCredHandle); + +/* +Shims the gss_release_cred method. +*/ +extern "C" uint32_t NetSecurityNative_ReleaseCred(uint32_t* minorStatus, GssCredId** credHandle); + +/* +Shims the gss_init_sec_context method with SPNEGO oids. +*/ +extern "C" uint32_t NetSecurityNative_InitSecContext(uint32_t* minorStatus, + GssCredId* claimantCredHandle, + GssCtxId** contextHandle, + uint32_t isNtlm, + GssName* targetName, + uint32_t reqFlags, + uint8_t* inputBytes, + uint32_t inputLength, + struct PAL_GssBuffer* outBuffer, + uint32_t* retFlags); + +/* +Shims the gss_delete_sec_context method. +*/ +extern "C" uint32_t NetSecurityNative_DeleteSecContext(uint32_t* minorStatus, GssCtxId** contextHandle); + +/* +Shims the gss_wrap method. +*/ +extern "C" uint32_t NetSecurityNative_Wrap(uint32_t* minorStatus, + GssCtxId* contextHandle, + int32_t isEncrypt, + uint8_t* inputBytes, + int32_t offset, + int32_t count, + struct PAL_GssBuffer* outBuffer); + +/* +Shims the gss_unwrap method. +*/ +extern "C" uint32_t NetSecurityNative_Unwrap(uint32_t* minorStatus, + GssCtxId* contextHandle, + uint8_t* inputBytes, + int32_t offset, + int32_t count, + struct PAL_GssBuffer* outBuffer); + +/* +Shims the gss_acquire_cred_with_password method with GSS_C_INITIATE +*/ +extern "C" uint32_t NetSecurityNative_InitiateCredWithPassword(uint32_t* minorStatus, + GssName* desiredName, + char* password, + uint32_t passwdLen, + GssCredId** outputCredHandle); diff --git a/src/Native/configure.cmake b/src/Native/configure.cmake index 5c91ce05b1ee..2131ac3e31d0 100644 --- a/src/Native/configure.cmake +++ b/src/Native/configure.cmake @@ -317,6 +317,24 @@ check_symbol_exists( "sys/syslimits.h" HAVE_OPEN_MAX) +check_include_files( + GSS/GSS.h + HAVE_GSSFW_HEADERS) + +if (HAVE_GSSFW_HEADERS) + check_symbol_exists( + GSS_SPNEGO_MECHANISM + "GSS/GSS.h" + HAVE_GSS_SPNEGO_MECHANISM) +else () + check_symbol_exists( + GSS_SPNEGO_MECHANISM + "gssapi/gssapi.h" + HAVE_GSS_SPNEGO_MECHANISM) +endif () + + + set (CMAKE_REQUIRED_LIBRARIES) configure_file( diff --git a/src/System.Net.Security/src/Resources/Strings.resx b/src/System.Net.Security/src/Resources/Strings.resx index b1f411c38221..378e4ea45c62 100644 --- a/src/System.Net.Security/src/Resources/Strings.resx +++ b/src/System.Net.Security/src/Resources/Strings.resx @@ -1,4 +1,4 @@ - +