From c6619e191e51a6d1400ff1237a971a6277db13ce Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Tue, 12 Jul 2022 09:06:50 +0000 Subject: [PATCH] Add support for specifying Kerberos package on Linux/macOS (in addition to NTLM and Negotiate) --- .../Interop.NetSecurityNative.PackageType.cs | 17 ++++++ .../Interop.NetSecurityNative.cs | 14 ++--- .../Win32/SafeHandles/GssSafeHandles.cs | 6 +- .../Net/Security/NegotiateStreamPal.Unix.cs | 48 ++++++++++----- .../Security/Unix/SafeFreeNegoCredentials.cs | 14 ++--- .../src/System.Net.Security.csproj | 2 + .../System.Net.Security.Unit.Tests.csproj | 2 + .../System.Net.Security.Native/pal_gssapi.c | 59 +++++++++++-------- .../System.Net.Security.Native/pal_gssapi.h | 13 +++- 9 files changed, 117 insertions(+), 58 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.PackageType.cs diff --git a/src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.PackageType.cs b/src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.PackageType.cs new file mode 100644 index 00000000000000..d8d6073c3d04a7 --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.PackageType.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +internal static partial class Interop +{ + internal static partial class NetSecurityNative + { + internal enum PackageType : uint + { + Negotiate = 0, + NTLM = 1, + Kerberos = 2, + } + } +} diff --git a/src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs b/src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs index 3ce98eb5d30769..84a31968d79378 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs @@ -61,7 +61,7 @@ internal static partial Status InitiateCredSpNego( [LibraryImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_InitiateCredWithPassword", StringMarshalling = StringMarshalling.Utf8)] internal static partial Status InitiateCredWithPassword( out Status minorStatus, - [MarshalAs(UnmanagedType.Bool)] bool isNtlm, + PackageType packageType, SafeGssNameHandle desiredName, string password, int passwordLen, @@ -77,7 +77,7 @@ private static partial Status InitSecContext( out Status minorStatus, SafeGssCredHandle initiatorCredHandle, ref SafeGssContextHandle contextHandle, - [MarshalAs(UnmanagedType.Bool)] bool isNtlmOnly, + PackageType packageType, SafeGssNameHandle? targetName, uint reqFlags, ref byte inputBytes, @@ -91,7 +91,7 @@ private static partial Status InitSecContext( out Status minorStatus, SafeGssCredHandle initiatorCredHandle, ref SafeGssContextHandle contextHandle, - [MarshalAs(UnmanagedType.Bool)] bool isNtlmOnly, + PackageType packageType, IntPtr cbt, int cbtSize, SafeGssNameHandle? targetName, @@ -106,7 +106,7 @@ internal static Status InitSecContext( out Status minorStatus, SafeGssCredHandle initiatorCredHandle, ref SafeGssContextHandle contextHandle, - bool isNtlmOnly, + PackageType packageType, SafeGssNameHandle? targetName, uint reqFlags, ReadOnlySpan inputBytes, @@ -118,7 +118,7 @@ internal static Status InitSecContext( out minorStatus, initiatorCredHandle, ref contextHandle, - isNtlmOnly, + packageType, targetName, reqFlags, ref MemoryMarshal.GetReference(inputBytes), @@ -132,7 +132,7 @@ internal static Status InitSecContext( out Status minorStatus, SafeGssCredHandle initiatorCredHandle, ref SafeGssContextHandle contextHandle, - bool isNtlmOnly, + PackageType packageType, IntPtr cbt, int cbtSize, SafeGssNameHandle? targetName, @@ -146,7 +146,7 @@ internal static Status InitSecContext( out minorStatus, initiatorCredHandle, ref contextHandle, - isNtlmOnly, + packageType, cbt, cbtSize, targetName, diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.cs index 253e0548dea9d9..acc95913c863c5 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.cs @@ -91,9 +91,9 @@ public static SafeGssCredHandle CreateAcceptor() /// returns the handle for the given credentials. /// The method returns an invalid handle if the username is null or empty. /// - public static SafeGssCredHandle Create(string username, string password, bool isNtlmOnly) + public static SafeGssCredHandle Create(string username, string password, Interop.NetSecurityNative.PackageType packageType) { - if (isNtlmOnly && !s_IsNtlmInstalled.Value) + if (packageType == Interop.NetSecurityNative.PackageType.NTLM && !s_IsNtlmInstalled.Value) { throw new Interop.NetSecurityNative.GssApiException( Interop.NetSecurityNative.Status.GSS_S_BAD_MECH, @@ -117,7 +117,7 @@ public static SafeGssCredHandle Create(string username, string password, bool is } else { - status = Interop.NetSecurityNative.InitiateCredWithPassword(out minorStatus, isNtlmOnly, userHandle, password, Encoding.UTF8.GetByteCount(password), out retHandle); + status = Interop.NetSecurityNative.InitiateCredWithPassword(out minorStatus, packageType, userHandle, password, Encoding.UTF8.GetByteCount(password), out retHandle); } if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) diff --git a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs index 73043391901aef..702167758da69c 100644 --- a/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs +++ b/src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs @@ -134,7 +134,7 @@ private static SecurityStatusPal EstablishSecurityContext( out byte[]? resultBuffer, ref ContextFlagsPal outFlags) { - bool isNtlmOnly = credential.IsNtlmOnly; + Interop.NetSecurityNative.PackageType packageType = credential.PackageType; resultBuffer = null; @@ -142,7 +142,11 @@ private static SecurityStatusPal EstablishSecurityContext( { if (NetEventSource.Log.IsEnabled()) { - string protocol = isNtlmOnly ? "NTLM" : "SPNEGO"; + string protocol = packageType switch { + Interop.NetSecurityNative.PackageType.NTLM => "NTLM", + Interop.NetSecurityNative.PackageType.Kerberos => "Kerberos", + _ => "SPNEGO" + }; NetEventSource.Info(context, $"requested protocol = {protocol}, target = {targetName}"); } @@ -172,7 +176,7 @@ private static SecurityStatusPal EstablishSecurityContext( status = Interop.NetSecurityNative.InitSecContext(out minorStatus, credential.GssCredential, ref contextHandle, - isNtlmOnly, + packageType, cbtAppData, cbtAppDataSize, negoContext.TargetName, @@ -187,7 +191,7 @@ private static SecurityStatusPal EstablishSecurityContext( status = Interop.NetSecurityNative.InitSecContext(out minorStatus, credential.GssCredential, ref contextHandle, - isNtlmOnly, + packageType, negoContext.TargetName, (uint)inputFlags, incomingBlob, @@ -216,7 +220,11 @@ private static SecurityStatusPal EstablishSecurityContext( { if (NetEventSource.Log.IsEnabled()) { - string protocol = isNtlmOnly ? "NTLM" : isNtlmUsed ? "SPNEGO-NTLM" : "SPNEGO-Kerberos"; + string protocol = packageType switch { + Interop.NetSecurityNative.PackageType.NTLM => "NTLM", + Interop.NetSecurityNative.PackageType.Kerberos => "Kerberos", + _ => isNtlmUsed ? "SPNEGO-NTLM" : "SPNEGO-Kerberos" + }; NetEventSource.Info(context, $"actual protocol = {protocol}"); } @@ -441,24 +449,36 @@ internal static SafeFreeCredentials AcquireCredentialsHandle(string package, boo { bool isEmptyCredential = string.IsNullOrWhiteSpace(credential.UserName) || string.IsNullOrWhiteSpace(credential.Password); - bool ntlmOnly = string.Equals(package, NegotiationInfoClass.NTLM, StringComparison.OrdinalIgnoreCase); - if (ntlmOnly && isEmptyCredential && !isServer) + Interop.NetSecurityNative.PackageType packageType; + + if (string.Equals(package, NegotiationInfoClass.Negotiate, StringComparison.OrdinalIgnoreCase)) { - // NTLM authentication is not possible with default credentials which are no-op - throw new PlatformNotSupportedException(SR.net_ntlm_not_possible_default_cred); + packageType = Interop.NetSecurityNative.PackageType.Negotiate; } - - if (!ntlmOnly && !string.Equals(package, NegotiationInfoClass.Negotiate)) + else if (string.Equals(package, NegotiationInfoClass.NTLM, StringComparison.OrdinalIgnoreCase)) + { + packageType = Interop.NetSecurityNative.PackageType.NTLM; + if (isEmptyCredential && !isServer) + { + // NTLM authentication is not possible with default credentials which are no-op + throw new PlatformNotSupportedException(SR.net_ntlm_not_possible_default_cred); + } + } + else if (string.Equals(package, NegotiationInfoClass.Kerberos, StringComparison.OrdinalIgnoreCase)) + { + packageType = Interop.NetSecurityNative.PackageType.Kerberos; + } + else { - // Native shim currently supports only NTLM and Negotiate + // Native shim currently supports only NTLM, Negotiate and Kerberos throw new PlatformNotSupportedException(SR.net_securitypackagesupport); } try { return isEmptyCredential ? - new SafeFreeNegoCredentials(ntlmOnly, string.Empty, string.Empty, string.Empty) : - new SafeFreeNegoCredentials(ntlmOnly, credential.UserName, credential.Password, credential.Domain); + new SafeFreeNegoCredentials(packageType, string.Empty, string.Empty, string.Empty) : + new SafeFreeNegoCredentials(packageType, credential.UserName, credential.Password, credential.Domain); } catch (Exception ex) { diff --git a/src/libraries/Common/src/System/Net/Security/Unix/SafeFreeNegoCredentials.cs b/src/libraries/Common/src/System/Net/Security/Unix/SafeFreeNegoCredentials.cs index 58d1942829e524..ffbd46db32aaf4 100644 --- a/src/libraries/Common/src/System/Net/Security/Unix/SafeFreeNegoCredentials.cs +++ b/src/libraries/Common/src/System/Net/Security/Unix/SafeFreeNegoCredentials.cs @@ -12,7 +12,7 @@ namespace System.Net.Security internal sealed class SafeFreeNegoCredentials : SafeFreeCredentials { private SafeGssCredHandle _credential; - private readonly bool _isNtlmOnly; + private readonly Interop.NetSecurityNative.PackageType _packageType; private readonly string _userName; private readonly bool _isDefault; @@ -21,10 +21,10 @@ public SafeGssCredHandle GssCredential get { return _credential; } } - // Property represents if Ntlm Protocol is specfied or not. - public bool IsNtlmOnly + // Property represents which protocol is specfied (Negotiate, Ntlm or Kerberos). + public Interop.NetSecurityNative.PackageType PackageType { - get { return _isNtlmOnly; } + get { return _packageType; } } public string UserName @@ -37,7 +37,7 @@ public bool IsDefault get { return _isDefault; } } - public SafeFreeNegoCredentials(bool isNtlmOnly, string username, string password, string domain) + public SafeFreeNegoCredentials(Interop.NetSecurityNative.PackageType packageType, string username, string password, string domain) : base(IntPtr.Zero, true) { Debug.Assert(username != null && password != null, "Username and Password can not be null"); @@ -66,10 +66,10 @@ public SafeFreeNegoCredentials(bool isNtlmOnly, string username, string password } bool ignore = false; - _isNtlmOnly = isNtlmOnly; + _packageType = packageType; _userName = username; _isDefault = string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password); - _credential = SafeGssCredHandle.Create(username, password, isNtlmOnly); + _credential = SafeGssCredHandle.Create(username, password, packageType); _credential.DangerousAddRef(ref ignore); } diff --git a/src/libraries/System.Net.Security/src/System.Net.Security.csproj b/src/libraries/System.Net.Security/src/System.Net.Security.csproj index 139ea7ce4ba064..55d140a914f553 100644 --- a/src/libraries/System.Net.Security/src/System.Net.Security.csproj +++ b/src/libraries/System.Net.Security/src/System.Net.Security.csproj @@ -252,6 +252,8 @@ Link="Common\Interop\Unix\Interop.Errors.cs" /> + +