diff --git a/src/Common/src/Interop/Unix/Interop.Libraries.cs b/src/Common/src/Interop/Unix/Interop.Libraries.cs index 8e5de4df736b..bd16abaec2bb 100644 --- a/src/Common/src/Interop/Unix/Interop.Libraries.cs +++ b/src/Common/src/Interop/Unix/Interop.Libraries.cs @@ -8,6 +8,7 @@ private static partial class Libraries // Shims internal const string SystemNative = "System.Native"; internal const string HttpNative = "System.Net.Http.Native"; + internal const string NetSecurityNative = "System.Net.Security.Native"; internal const string CryptoNative = "System.Security.Cryptography.Native"; internal const string GlobalizationNative = "System.Globalization.Native"; internal const string CompressionNative = "System.IO.Compression.Native"; diff --git a/src/Common/src/Interop/Unix/System.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..798e310f737d --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.GssApi.cs @@ -0,0 +1,125 @@ +// 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, + bool isNtlm, + SafeGssNameHandle targetName, + Interop.NetSecurity.GssFlags inFlags, + byte[] buffer, + out byte[] outputBuffer, + out uint outFlags) + { + outputBuffer = null; + outFlags = 0; + + if (context == null) + { + context = new SafeGssContextHandle(); + } + + SafeGssBufferHandle outputToken; + NetSecurity.Status status; + NetSecurity.Status minorStatus; + int outputLength; + status = NetSecurity.InitSecContext(out minorStatus, + credential, + ref context, + isNtlm, + targetName, + (uint)inFlags, + buffer, + (buffer == null) ? 0 : buffer.Length, + out outputToken, + out outputLength, + out outFlags); + + using (outputToken) + { + if ((status != NetSecurity.Status.GSS_S_COMPLETE) && (status != NetSecurity.Status.GSS_S_CONTINUE_NEEDED)) + { + throw new NetSecurity.GssApiException(SR.net_context_establishment_failed, status, minorStatus); + } + + outputBuffer = new byte[outputLength]; + outputToken.Copy(outputBuffer, 0); + } + return status == NetSecurity.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"); + + SafeGssBufferHandle outputToken; + int msgLength; + NetSecurity.Status minorStatus; + NetSecurity.Status status = NetSecurity.Wrap(out minorStatus, context, encrypt, buffer, offset, count, out outputToken, out msgLength); + using (outputToken) + { + if (status != NetSecurity.Status.GSS_S_COMPLETE) + { + throw new NetSecurity.GssApiException(SR.net_context_wrap_failed, status, minorStatus); + } + + byte[] outputBuffer = new byte[msgLength]; + outputToken.Copy(outputBuffer, 0); + return outputBuffer; + } + } + + 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"); + + SafeGssBufferHandle outputToken; + int msgLength; + NetSecurity.Status minorStatus; + NetSecurity.Status status = NetSecurity.Unwrap(out minorStatus, context, buffer, offset, count, out outputToken, out msgLength); + + using (outputToken) + { + if (status != NetSecurity.Status.GSS_S_COMPLETE) + { + throw new NetSecurity.GssApiException(SR.net_context_unwrap_failed, status, minorStatus); + } + + int length = msgLength - offset; + if (msgLength > length) + { + throw new NetSecurity.GssApiException(SR.Format(SR.net_context_buffer_too_small, msgLength, length)); + } + + outputToken.Copy(buffer, offset); + } + + return msgLength; + } + } +} + + 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..e9df622da4bf --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.GssApiException.cs @@ -0,0 +1,84 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class NetSecurity + { + 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 void AssertOrThrowIfError(string message, Status majorStatus, Status minorStatus) + { + if (majorStatus != Status.GSS_S_COMPLETE) + { + GssApiException ex = new GssApiException(majorStatus, minorStatus); + 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) + { + KeyValuePair[] statusArr = { new KeyValuePair(majorStatus, false), new KeyValuePair(minorStatus, true) }; + int length = statusArr.Length; + string[] msgStrings = new string[length]; + + for (int i = 0; i < length; i++) + { + SafeGssBufferHandle msgBuffer; + Interop.NetSecurity.Status minStat; + int statusLength; + if (Status.GSS_S_COMPLETE != DisplayStatus(out minStat, statusArr[i].Key, statusArr[i].Value, out msgBuffer, out statusLength)) + { + msgStrings[i] = "Unknown Error"; + continue; + } + using (msgBuffer) + { + byte[] statusBytes = new byte[statusLength]; + msgBuffer.Copy(statusBytes, 0); + var encoding = new System.Text.ASCIIEncoding(); + msgStrings[i] = encoding.GetString(statusBytes); + } + } + + return msgStrings[0] + " (" + msgStrings[1] + ") \nStatus: " + majorStatus.ToString("x8") + " (" + minorStatus.ToString("x8") + ")"; + } + } + } +#endif +} diff --git a/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurity.cs b/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurity.cs new file mode 100644 index 000000000000..0d77318a0f4e --- /dev/null +++ b/src/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurity.cs @@ -0,0 +1,138 @@ +// 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 partial class NetSecurity + { + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_ReleaseBuffer")] + internal static extern Status ReleaseBuffer( + out Status minorStatus, + IntPtr bufferHandle); + + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_CopyBuffer")] + internal static extern Status CopyBuffer( + SafeGssBufferHandle handle, + byte[] buffer, + int capacity, + int offset); + + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_DisplayStatus")] + internal static extern Status DisplayStatus( + out Status minorStatus, + Status statusValue, + bool isGssMechCode, + out SafeGssBufferHandle bufferHandle, + out int statusLength); + + [DllImport(Interop.Libraries.NetSecurityNative, CharSet = CharSet.Ansi, EntryPoint="NetSecurity_ImportUserName")] + internal static extern Status ImportUserName( + out Status minorStatus, + string inputName, + int inputNameLen, + out SafeGssNameHandle outputName); + + [DllImport(Interop.Libraries.NetSecurityNative, CharSet = CharSet.Ansi, EntryPoint="NetSecurity_ImportPrincipalName")] + internal static extern Status ImportPrincipalName( + out Status minorStatus, + string inputName, + int inputNameLen, + out SafeGssNameHandle outputName); + + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_ReleaseName")] + internal static extern Status ReleaseName( + out Status minorStatus, + ref IntPtr inputName); + + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_AcquireCredSpNego")] + internal static extern Status AcquireCredSpNego( + out Status minorStatus, + SafeGssNameHandle desiredName, + bool isInitiate, + out SafeGssCredHandle outputCredHandle); + + [DllImport(Interop.Libraries.NetSecurityNative, CharSet = CharSet.Ansi, EntryPoint="NetSecurity_AcquireCredWithPassword")] + internal static extern Status AcquireCredWithPassword( + out Status minorStatus, + SafeGssNameHandle desiredName, + string password, + int passwordLen, + bool isInitiate, + out SafeGssCredHandle outputCredHandle); + + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_ReleaseCred")] + internal static extern Status ReleaseCred( + out Status minorStatus, + ref IntPtr credHandle); + + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_InitSecContext")] + internal static extern Status InitSecContext( + out Status minorStatus, + SafeGssCredHandle initiatorCredHandle, + ref SafeGssContextHandle contextHandle, + bool isNtlm, + SafeGssNameHandle targetName, + uint reqFlags, + byte[] inputBytes, + int inputLength, + out SafeGssBufferHandle outputToken, + out int outputLength, + out uint retFlags); + + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_DeleteSecContext")] + internal static extern Status DeleteSecContext( + out Status minorStatus, + ref IntPtr contextHandle); + + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_Wrap")] + internal static extern Status Wrap( + out Status minorStatus, + SafeGssContextHandle contextHandle, + bool isEncrypt, + byte[] inputBytes, + int offset, + int count, + out SafeGssBufferHandle outputMessageBuffer, + out int outMsgLength); + + [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurity_Unwrap")] + internal static extern Status Unwrap( + out Status minorStatus, + SafeGssContextHandle contextHandle, + byte[] inputBytes, + int offset, + int count, + out SafeGssBufferHandle outputMessageBuffer, + out int outMsgLength); + + 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..d34dfbb5d8f3 --- /dev/null +++ b/src/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.cs @@ -0,0 +1,158 @@ +// 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; + +namespace Microsoft.Win32.SafeHandles +{ + /// + /// Wrapper around an output gss_buffer_desc* + /// + internal sealed class SafeGssBufferHandle : SafeHandle + { + private SafeGssBufferHandle() : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + + internal void Copy(byte[] destination, int offset) + { + Debug.Assert(destination != null, "destination buffer cannot be null"); + Debug.Assert(offset <= destination.Length , "offset in the destination buffer must be valid"); + if (destination.Length > 0) + { + Interop.NetSecurity.CopyBuffer(this, destination, capacity: destination.Length, offset: offset); + } + } + + protected override bool ReleaseHandle() + { + Interop.NetSecurity.Status minorStatus; + Interop.NetSecurity.Status status = Interop.NetSecurity.ReleaseBuffer(out minorStatus, handle); + SetHandle(IntPtr.Zero); + return status == Interop.NetSecurity.Status.GSS_S_COMPLETE; + } + } + + /// + /// Wrapper around a gss_name_t_desc* + /// + internal sealed class SafeGssNameHandle : SafeHandle + { + public static SafeGssNameHandle Create(string name, bool isUser) + { + Debug.Assert(!string.IsNullOrEmpty(name), "Invalid name passed to SafeGssNameHandle create"); + SafeGssNameHandle retHandle; + Interop.NetSecurity.Status minorStatus; + Interop.NetSecurity.Status status = isUser? + Interop.NetSecurity.ImportUserName(out minorStatus, name, name.Length, out retHandle) : + Interop.NetSecurity.ImportPrincipalName(out minorStatus, name, name.Length, out retHandle); + if (status != Interop.NetSecurity.Status.GSS_S_COMPLETE) + { + throw new Interop.NetSecurity.GssApiException(status, minorStatus); + } + + return retHandle; + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + + protected override bool ReleaseHandle() + { + Interop.NetSecurity.Status minorStatus; + Interop.NetSecurity.Status status = Interop.NetSecurity.ReleaseName(out minorStatus, ref handle); + SetHandle(IntPtr.Zero); + return status == Interop.NetSecurity.Status.GSS_S_COMPLETE; + } + + 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, true)) + { + Interop.NetSecurity.Status status; + Interop.NetSecurity.Status minorStatus; + if (string.IsNullOrEmpty(password)) + { + status = Interop.NetSecurity.AcquireCredSpNego(out minorStatus, userHandle, true, out retHandle); + } + else + { + status = Interop.NetSecurity.AcquireCredWithPassword(out minorStatus, userHandle, password, password.Length, true, out retHandle); + } + + if (status != Interop.NetSecurity.Status.GSS_S_COMPLETE) + { + throw new Interop.NetSecurity.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.NetSecurity.Status minorStatus; + Interop.NetSecurity.Status status = Interop.NetSecurity.ReleaseCred(out minorStatus, ref handle); + SetHandle(IntPtr.Zero); + return status == Interop.NetSecurity.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.NetSecurity.Status minorStatus; + Interop.NetSecurity.Status status = Interop.NetSecurity.DeleteSecContext(out minorStatus, ref handle); + SetHandle(IntPtr.Zero); + return status == Interop.NetSecurity.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..6b3c28c11b9c --- /dev/null +++ b/src/Native/System.Net.Security.Native/pal_gssapi.cpp @@ -0,0 +1,261 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license 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 void NetSecurity_HandleError(uint32_t majorStatus, GssBuffer** outBufferHandle, uint32_t* outBufferLength) +{ + assert(outBufferHandle != nullptr); + assert(outBufferLength != nullptr); + if (GSS_ERROR(majorStatus) || (*outBufferHandle)->length > UnsignedCast(std::numeric_limits::max())) + { + uint32_t relBufferStatus; + gss_release_buffer(&relBufferStatus, *outBufferHandle); + *outBufferHandle = nullptr; + *outBufferLength = 0; + } + else + { + *outBufferLength = static_cast((*outBufferHandle)->length); + } +} + +extern "C" uint32_t NetSecurity_AcquireCredSpNego(uint32_t* minorStatus, + GssName* desiredName, + int32_t isInitiate, + GssCredId** outputCredHandle) +{ + assert(minorStatus != nullptr); + assert(outputCredHandle != nullptr); + assert(isInitiate == 0 || isInitiate == 1); +#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 + gss_cred_usage_t credUsage = isInitiate ? GSS_C_INITIATE : GSS_C_ACCEPT; + return gss_acquire_cred( + minorStatus, desiredName, 0, &gss_mech_spnego_OID_set_desc, credUsage, outputCredHandle, nullptr, nullptr); +} + +extern "C" uint32_t NetSecurity_DeleteSecContext(uint32_t* minorStatus, GssCtxId** contextHandle) +{ + assert(contextHandle != nullptr); + assert(minorStatus != nullptr); + return gss_delete_sec_context(minorStatus, contextHandle, GSS_C_NO_BUFFER); +} + +extern "C" uint32_t NetSecurity_DisplayStatus(uint32_t* minorStatus, + uint32_t statusValue, + int32_t isGssMechCode, + GssBuffer** outBufferHandle, + uint32_t* statusLength) +{ + assert(minorStatus != nullptr); + assert(outBufferHandle != nullptr); + assert(statusLength != nullptr); + assert(isGssMechCode == 0 || isGssMechCode == 1); + int statusType = isGssMechCode ? GSS_C_MECH_CODE : GSS_C_GSS_CODE; + *outBufferHandle = new GssBuffer(); + uint32_t majorStatus = + gss_display_status(minorStatus, statusValue, statusType, GSS_C_NO_OID, nullptr, *outBufferHandle); + NetSecurity_HandleError(majorStatus, outBufferHandle, statusLength); + return majorStatus; +} + +extern "C" uint32_t +NetSecurity_ImportUserName(uint32_t* minorStatus, char* inputName, uint32_t inputNameLen, GssName** outputName) +{ + assert(outputName != nullptr); + assert(minorStatus != nullptr); + assert(inputName != nullptr); + assert(outputName != nullptr); + gss_buffer_desc 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 +NetSecurity_ImportPrincipalName(uint32_t* minorStatus, char* inputName, uint32_t inputNameLen, GssName** outputName) +{ + assert(minorStatus != nullptr); + assert(inputNameLen >= 0); + assert(outputName != nullptr); + gss_buffer_desc 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 NetSecurity_InitSecContext(uint32_t* minorStatus, + GssCredId* claimantCredHandle, + GssCtxId** contextHandle, + uint32_t isNtlm, + GssName* targetName, + uint32_t reqFlags, + uint8_t* inputBytes, + uint32_t inputLength, + GssBuffer** outBufferHandle, + uint32_t* outTokenLength, + uint32_t* retFlags) +{ + assert(isNtlm == 0 || isNtlm == 1); + assert(minorStatus != nullptr); + assert(contextHandle != nullptr); + assert(outBufferHandle != nullptr); + assert(outTokenLength != nullptr); + assert(retFlags != nullptr); + +#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 + + gss_buffer_desc inputToken{.length = UnsignedCast(inputLength), .value = inputBytes}; + *outBufferHandle = new GssBuffer(); + + uint32_t majorStatus = gss_init_sec_context(minorStatus, + claimantCredHandle, + contextHandle, + targetName, + desiredMech, + reqFlags, + 0, + GSS_C_NO_CHANNEL_BINDINGS, + &inputToken, + nullptr, + *outBufferHandle, + retFlags, + nullptr); + + NetSecurity_HandleError(majorStatus, outBufferHandle, outTokenLength); + return majorStatus; +} + +extern "C" uint32_t NetSecurity_ReleaseCred(uint32_t* minorStatus, GssCredId** credHandle) +{ + assert(minorStatus != nullptr); + assert(credHandle != nullptr); + return gss_release_cred(minorStatus, credHandle); +} + +extern "C" uint32_t NetSecurity_ReleaseBuffer(uint32_t* minorStatus, GssBuffer* buffer) +{ + assert(minorStatus != nullptr); + assert(buffer != nullptr); + uint32_t status = gss_release_buffer(minorStatus, buffer); + delete buffer; + return status; +} + +extern "C" void NetSecurity_CopyBuffer(GssBuffer* bufferHandle, uint8_t* bytes, uint32_t capacity, uint32_t offset) +{ + assert(bufferHandle != nullptr); + printf("capacity = %d, offset = %d, length = %zu \n", capacity, offset, bufferHandle->length); + assert(bufferHandle->length <= (capacity - offset)); + memcpy(bytes + offset, bufferHandle->value, bufferHandle->length); +} + +extern "C" uint32_t NetSecurity_ReleaseName(uint32_t* minorStatus, GssName** inputName) +{ + assert(minorStatus != nullptr); + assert(inputName != nullptr); + return gss_release_name(minorStatus, inputName); +} + +extern "C" uint32_t NetSecurity_Wrap(uint32_t* minorStatus, + GssCtxId* contextHandle, + int32_t isEncrypt, + uint8_t* inputBytes, + int32_t offset, + int32_t count, + GssBuffer** outBufferHandle, + uint32_t* outMsgLength) +{ + assert(minorStatus != nullptr); + assert(contextHandle != nullptr); + assert(isEncrypt == 1 || isEncrypt == 0); + int confState; + gss_buffer_desc inputMessageBuffer{.length = UnsignedCast(count), .value = inputBytes + offset}; + *outBufferHandle = new GssBuffer(); + uint32_t majorStatus = gss_wrap( + minorStatus, contextHandle, isEncrypt, GSS_C_QOP_DEFAULT, &inputMessageBuffer, &confState, *outBufferHandle); + NetSecurity_HandleError(majorStatus, outBufferHandle, outMsgLength); + return majorStatus; +} + +extern "C" uint32_t NetSecurity_Unwrap(uint32_t* minorStatus, + GssCtxId* contextHandle, + uint8_t* inputBytes, + int32_t offset, + int32_t count, + GssBuffer** outBufferHandle, + uint32_t* outMsgLength) +{ + assert(minorStatus != nullptr); + assert(contextHandle != nullptr); + assert(inputBytes != nullptr); + assert(outBufferHandle != nullptr); + assert(outMsgLength != nullptr); + + gss_buffer_desc inputMessageBuffer{.length = UnsignedCast(count), .value = inputBytes + offset}; + *outBufferHandle = new GssBuffer(); + uint32_t majorStatus = + gss_unwrap(minorStatus, contextHandle, &inputMessageBuffer, *outBufferHandle, nullptr, nullptr); + NetSecurity_HandleError(majorStatus, outBufferHandle, outMsgLength); + return majorStatus; +} + +extern "C" uint32_t NetSecurity_AcquireCredWithPassword(uint32_t* minorStatus, + GssName* desiredName, + char* password, + uint32_t passwdLen, + int32_t isInitiate, + GssCredId** outputCredHandle) +{ + assert(minorStatus != nullptr); + assert(desiredName != nullptr); + assert(outputCredHandle != nullptr); + assert(isInitiate == 0 || isInitiate == 1); + gss_cred_usage_t credUsage = isInitiate ? GSS_C_INITIATE : GSS_C_ACCEPT; + gss_buffer_desc passwordBuffer{.length = passwdLen, .value = password}; + + return gss_acquire_cred_with_password( + minorStatus, desiredName, &passwordBuffer, 0, nullptr, credUsage, outputCredHandle, nullptr, nullptr); +} 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..ed599ec86ac9 --- /dev/null +++ b/src/Native/System.Net.Security.Native/pal_gssapi.h @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license 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 +}; + +/* +Shims the gss_release_buffer method. +*/ +extern "C" uint32_t NetSecurity_ReleaseBuffer(uint32_t* minorStatus, GssBuffer* buffer); + +/* +Copies gss_buffer_t->value to the given byte array +*/ +extern "C" void NetSecurity_CopyBuffer(GssBuffer* bufferHandle, uint8_t* bytes, uint32_t capacity, uint32_t offset); + +/* +Shims the gss_display_status method. +*/ +extern "C" uint32_t NetSecurity_DisplayStatus(uint32_t* minorStatus, + uint32_t statusValue, + int32_t isGssMechCode, + GssBuffer** outBufferHandle, + uint32_t* statusLength); + +/* +Shims the gss_import_name method with nametype = GSS_C_NT_USER_NAME. +*/ +extern "C" uint32_t +NetSecurity_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 +NetSecurity_ImportPrincipalName(uint32_t* minorStatus, char* inputName, uint32_t inputNameLen, GssName** outputName); + +/* +Shims the gss_release_name method. +*/ +extern "C" uint32_t NetSecurity_ReleaseName(uint32_t* minorStatus, GssName** inputName); + +/* +Shims the gss_acquire_cred method with SPNEGO oids. +*/ +extern "C" uint32_t NetSecurity_AcquireCredSpNego(uint32_t* minorStatus, + GssName* desiredName, + int32_t isInitiate, + GssCredId** outputCredHandle); + +/* +Shims the gss_release_cred method. +*/ +extern "C" uint32_t NetSecurity_ReleaseCred(uint32_t* minorStatus, GssCredId** credHandle); + +/* +Shims the gss_init_sec_context method with SPNEGO oids. +*/ +extern "C" uint32_t NetSecurity_InitSecContext(uint32_t* minorStatus, + GssCredId* claimantCredHandle, + GssCtxId** contextHandle, + uint32_t isNtlm, + GssName* targetName, + uint32_t reqFlags, + uint8_t* inputBytes, + uint32_t inputLength, + GssBuffer** outBufferHandle, + uint32_t* outTokenLength, + uint32_t* retFlags); + +/* +Shims the gss_delete_sec_context method. +*/ +extern "C" uint32_t NetSecurity_DeleteSecContext(uint32_t* minorStatus, GssCtxId** contextHandle); + +/* +Shims the gss_wrap method. +*/ +extern "C" uint32_t NetSecurity_Wrap(uint32_t* minorStatus, + GssCtxId* contextHandle, + int32_t isEncrypt, + uint8_t* inputBytes, + int32_t offset, + int32_t count, + GssBuffer** outBufferHandle, + uint32_t* outMsgLength); + +/* +Shims the gss_unwrap method. +*/ +extern "C" uint32_t NetSecurity_Unwrap(uint32_t* minorStatus, + GssCtxId* contextHandle, + uint8_t* inputBytes, + int32_t offset, + int32_t count, + GssBuffer** outBufferHandle, + uint32_t* outMsgLength); + +/* +Shims the gss_acquire_cred_with_password method. +*/ +extern "C" uint32_t NetSecurity_AcquireCredWithPassword(uint32_t* minorStatus, + GssName* desiredName, + char* password, + uint32_t passwdLen, + int32_t isInitiate, + 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..f44c99178ddd 100644 --- a/src/System.Net.Security/src/Resources/Strings.resx +++ b/src/System.Net.Security/src/Resources/Strings.resx @@ -1,4 +1,4 @@ - +