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 6d9816dfeaec93..31096a74856cf9 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 @@ -2,8 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Diagnostics; +using System.Net.Security; using System.Runtime.InteropServices; +using System.Security.Authentication.ExtendedProtection; using System.Text; using Microsoft.Win32.SafeHandles; @@ -133,8 +134,7 @@ internal static Status InitSecContext( SafeGssCredHandle initiatorCredHandle, ref SafeGssContextHandle contextHandle, PackageType packageType, - IntPtr cbt, - int cbtSize, + ChannelBinding channelBinding, SafeGssNameHandle? targetName, uint reqFlags, ReadOnlySpan inputBytes, @@ -142,20 +142,73 @@ internal static Status InitSecContext( out uint retFlags, out bool isNtlmUsed) { - return InitSecContext( - out minorStatus, - initiatorCredHandle, - ref contextHandle, - packageType, - cbt, - cbtSize, - targetName, - reqFlags, - ref MemoryMarshal.GetReference(inputBytes), - inputBytes.Length, - ref token, - out retFlags, - out isNtlmUsed); + // Ref-count the channel binding handle so it cannot be released while the native + // call, which receives a raw pointer into the application-specific data, is in flight. + bool refAdded = false; + try + { + channelBinding.DangerousAddRef(ref refAdded); + if (!TryGetChannelBindingApplicationData(channelBinding, out IntPtr cbtAppData, out int cbtAppDataSize)) + { + // Invalid or malformed channel binding; fail rather than passing a bogus pointer to interop. + minorStatus = Status.GSS_S_COMPLETE; + retFlags = 0; + isNtlmUsed = false; + return Status.GSS_S_BAD_BINDINGS; + } + + return InitSecContext( + out minorStatus, + initiatorCredHandle, + ref contextHandle, + packageType, + cbtAppData, + cbtAppDataSize, + targetName, + reqFlags, + ref MemoryMarshal.GetReference(inputBytes), + inputBytes.Length, + ref token, + out retFlags, + out isNtlmUsed); + } + finally + { + if (refAdded) + { + channelBinding.DangerousRelease(); + } + } + } + + // Resolves the application-specific data inside a SecChannelBindings buffer, honoring the + // ApplicationDataOffset/ApplicationDataLength fields rather than assuming the data starts + // immediately after the header. Returns false for invalid handles or out-of-bounds ranges. + // The caller must hold a ref on the handle (DangerousAddRef) while using the returned pointer. + private static unsafe bool TryGetChannelBindingApplicationData(ChannelBinding channelBinding, out IntPtr applicationData, out int applicationDataSize) + { + applicationData = IntPtr.Zero; + applicationDataSize = 0; + + int size = channelBinding.Size; + if (channelBinding.IsInvalid || size < sizeof(SecChannelBindings)) + { + return false; + } + + SecChannelBindings* bindings = (SecChannelBindings*)channelBinding.DangerousGetHandle(); + int offset = bindings->ApplicationDataOffset; + int length = bindings->ApplicationDataLength; + + // The application data must lie entirely within the buffer, after the fixed header. + if (offset < sizeof(SecChannelBindings) || length < 0 || (long)offset + length > size) + { + return false; + } + + applicationData = (IntPtr)((byte*)bindings + offset); + applicationDataSize = length; + return true; } [LibraryImport(Interop.Libraries.NetSecurityNative, EntryPoint = "NetSecurityNative_AcceptSecContext")] @@ -163,30 +216,73 @@ private static partial Status AcceptSecContext( out Status minorStatus, SafeGssCredHandle acceptorCredHandle, ref SafeGssContextHandle acceptContextHandle, + IntPtr cbt, + int cbtSize, ref byte inputBytes, int inputLength, ref GssBuffer token, out uint retFlags, [MarshalAs(UnmanagedType.Bool)] out bool isNtlmUsed); - internal static Status AcceptSecContext( + internal static unsafe Status AcceptSecContext( out Status minorStatus, SafeGssCredHandle acceptorCredHandle, ref SafeGssContextHandle acceptContextHandle, + ChannelBinding? channelBinding, ReadOnlySpan inputBytes, ref GssBuffer token, out uint retFlags, out bool isNtlmUsed) { - return AcceptSecContext( - out minorStatus, - acceptorCredHandle, - ref acceptContextHandle, - ref MemoryMarshal.GetReference(inputBytes), - inputBytes.Length, - ref token, - out retFlags, - out isNtlmUsed); + if (channelBinding is null) + { + return AcceptSecContext( + out minorStatus, + acceptorCredHandle, + ref acceptContextHandle, + IntPtr.Zero, + 0, + ref MemoryMarshal.GetReference(inputBytes), + inputBytes.Length, + ref token, + out retFlags, + out isNtlmUsed); + } + + // Ref-count the channel binding handle so it cannot be released while the native + // call, which receives a raw pointer into the application-specific data, is in flight. + bool refAdded = false; + try + { + channelBinding.DangerousAddRef(ref refAdded); + if (!TryGetChannelBindingApplicationData(channelBinding, out IntPtr cbtAppData, out int cbtAppDataSize)) + { + // Invalid or malformed channel binding; fail rather than passing a bogus pointer to interop. + minorStatus = Status.GSS_S_COMPLETE; + retFlags = 0; + isNtlmUsed = false; + return Status.GSS_S_BAD_BINDINGS; + } + + return AcceptSecContext( + out minorStatus, + acceptorCredHandle, + ref acceptContextHandle, + cbtAppData, + cbtAppDataSize, + ref MemoryMarshal.GetReference(inputBytes), + inputBytes.Length, + ref token, + out retFlags, + out isNtlmUsed); + } + finally + { + if (refAdded) + { + channelBinding.DangerousRelease(); + } + } } [LibraryImport(Interop.Libraries.NetSecurityNative, EntryPoint = "NetSecurityNative_DeleteSecContext")] diff --git a/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.Unix.cs b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.Unix.cs index 6a05913a5b2f51..f824e1fa747631 100644 --- a/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.Unix.cs +++ b/src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.Unix.cs @@ -281,13 +281,12 @@ public override void Dispose() } else { - // TODO: We don't currently check channel bindings. - // Server session. statusCode = AcceptSecurityContext( _credentialsHandle, ref _securityContext, incomingBlob, + _channelBinding, ref _tokenBuffer, out resultBlobLength, ref _contextFlags); @@ -540,7 +539,7 @@ Interop.NetSecurityNative.Status status } } - private unsafe NegotiateAuthenticationStatusCode InitializeSecurityContext( + private NegotiateAuthenticationStatusCode InitializeSecurityContext( ref SafeGssCredHandle credentialsHandle, ref SafeGssContextHandle? contextHandle, ref SafeGssNameHandle? targetNameHandle, @@ -582,18 +581,11 @@ private unsafe NegotiateAuthenticationStatusCode InitializeSecurityContext( if (channelBinding != null) { - // If a TLS channel binding token (cbt) is available then get the pointer - // to the application specific data. - int appDataOffset = sizeof(SecChannelBindings); - Debug.Assert(appDataOffset < channelBinding.Size); - IntPtr cbtAppData = channelBinding.DangerousGetHandle() + appDataOffset; - int cbtAppDataSize = channelBinding.Size - appDataOffset; status = Interop.NetSecurityNative.InitSecContext(out minorStatus, credentialsHandle, ref contextHandle, _packageType, - cbtAppData, - cbtAppDataSize, + channelBinding, targetNameHandle, (uint)requestedContextFlags, incomingBlob, @@ -670,9 +662,8 @@ private unsafe NegotiateAuthenticationStatusCode InitializeSecurityContext( private NegotiateAuthenticationStatusCode AcceptSecurityContext( SafeGssCredHandle credentialsHandle, ref SafeGssContextHandle? contextHandle, - //ContextFlagsPal requestedContextFlags, ReadOnlySpan incomingBlob, - //ChannelBinding? channelBinding, + ChannelBinding? channelBinding, ref byte[]? resultBlob, out int resultBlobLength, ref Interop.NetSecurityNative.GssFlags contextFlags) @@ -684,9 +675,11 @@ private NegotiateAuthenticationStatusCode AcceptSecurityContext( { Interop.NetSecurityNative.Status status; Interop.NetSecurityNative.Status minorStatus; + status = Interop.NetSecurityNative.AcceptSecContext(out minorStatus, credentialsHandle, ref contextHandle, + channelBinding, incomingBlob, ref token, out uint outputFlags, diff --git a/src/libraries/System.Net.Security/src/System/Security/Authentication/ExtendedProtection/ExtendedProtectionPolicy.cs b/src/libraries/System.Net.Security/src/System/Security/Authentication/ExtendedProtection/ExtendedProtectionPolicy.cs index 5db4917a1943f9..7a563b96e045ea 100644 --- a/src/libraries/System.Net.Security/src/System/Security/Authentication/ExtendedProtection/ExtendedProtectionPolicy.cs +++ b/src/libraries/System.Net.Security/src/System/Security/Authentication/ExtendedProtection/ExtendedProtectionPolicy.cs @@ -149,7 +149,9 @@ public static bool OSSupportsExtendedProtection get { // ExtendedProtection is supported on all Windows versions supported by current .NET version. - return OperatingSystem.IsWindows(); + // Linux is supported via GSSAPI (Managed implements only client-side). + // MacOS's Heimdal-derived GSS.framework does not reject a channel binding mismatch (the exchange completes successfully) + return OperatingSystem.IsWindows() || OperatingSystem.IsLinux(); } } } diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateAuthenticationKerberosTest.ChannelBindings.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateAuthenticationKerberosTest.ChannelBindings.cs new file mode 100644 index 00000000000000..6c59f38cc739f4 --- /dev/null +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateAuthenticationKerberosTest.ChannelBindings.cs @@ -0,0 +1,155 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net.Security.Kerberos; +using System.Security.Authentication.ExtendedProtection; +using System.Threading.Tasks; +using Xunit; + +namespace System.Net.Security.Tests +{ + public partial class NegotiateAuthenticationKerberosTest + { + [ConditionalFact(typeof(ExtendedProtectionPolicy), nameof(ExtendedProtectionPolicy.OSSupportsExtendedProtection))] + public Task ChannelBindings_Matching_Endpoint_Succeeds() => RunMatchingTest(ChannelBindingKind.Endpoint); + + [ConditionalFact(typeof(ExtendedProtectionPolicy), nameof(ExtendedProtectionPolicy.OSSupportsExtendedProtection))] + public Task ChannelBindings_Matching_Unique_Succeeds() => RunMatchingTest(ChannelBindingKind.Unique); + + [ConditionalFact(typeof(ExtendedProtectionPolicy), nameof(ExtendedProtectionPolicy.OSSupportsExtendedProtection))] + public Task ChannelBindings_Mismatched_Endpoint_FailsWithBadBinding() => RunMismatchTest(ChannelBindingKind.Endpoint); + + [ConditionalFact(typeof(ExtendedProtectionPolicy), nameof(ExtendedProtectionPolicy.OSSupportsExtendedProtection))] + public Task ChannelBindings_Mismatched_Unique_FailsWithBadBinding() => RunMismatchTest(ChannelBindingKind.Unique); + + private async Task RunMatchingTest(ChannelBindingKind kind) + { + using var kerberosExecutor = new KerberosExecutor(_testOutputHelper, "LINUX.CONTOSO.COM"); + + kerberosExecutor.AddService("HTTP/linux.contoso.com"); + kerberosExecutor.AddUser("user"); + + if (kind == ChannelBindingKind.Endpoint) + { + await kerberosExecutor.Invoke(static () => RunMatching(ChannelBindingKind.Endpoint)); + } + else + { + await kerberosExecutor.Invoke(static () => RunMatching(ChannelBindingKind.Unique)); + } + } + + private async Task RunMismatchTest(ChannelBindingKind kind) + { + using var kerberosExecutor = new KerberosExecutor(_testOutputHelper, "LINUX.CONTOSO.COM"); + + kerberosExecutor.AddService("HTTP/linux.contoso.com"); + kerberosExecutor.AddUser("user"); + + if (kind == ChannelBindingKind.Endpoint) + { + await kerberosExecutor.Invoke(static () => RunMismatch(ChannelBindingKind.Endpoint)); + } + else + { + await kerberosExecutor.Invoke(static () => RunMismatch(ChannelBindingKind.Unique)); + } + } + + private static void RunMatching(ChannelBindingKind kind) + { + using SafeChannelBindingHandle clientBinding = CreateChannelBinding(kind, hashSeed: 0x42); + using SafeChannelBindingHandle serverBinding = CreateChannelBinding(kind, hashSeed: 0x42); + + using NegotiateAuthentication client = new(new NegotiateAuthenticationClientOptions + { + Credential = new NetworkCredential("user", KerberosExecutor.DefaultUserPassword, "LINUX.CONTOSO.COM"), + TargetName = "HTTP/linux.contoso.com", + Binding = clientBinding, + }); + using NegotiateAuthentication server = new(new NegotiateAuthenticationServerOptions + { + Binding = serverBinding, + }); + + RunExchange(client, server, expectAuthenticated: true, out NegotiateAuthenticationStatusCode lastClientStatus, out NegotiateAuthenticationStatusCode lastServerStatus); + + Assert.Equal(NegotiateAuthenticationStatusCode.Completed, lastClientStatus); + Assert.Equal(NegotiateAuthenticationStatusCode.Completed, lastServerStatus); + } + + private static void RunMismatch(ChannelBindingKind kind) + { + using SafeChannelBindingHandle clientBinding = CreateChannelBinding(kind, hashSeed: 0x42); + using SafeChannelBindingHandle serverBinding = CreateChannelBinding(kind, hashSeed: 0x99); + + using NegotiateAuthentication client = new(new NegotiateAuthenticationClientOptions + { + Credential = new NetworkCredential("user", KerberosExecutor.DefaultUserPassword, "LINUX.CONTOSO.COM"), + TargetName = "HTTP/linux.contoso.com", + Binding = clientBinding, + }); + using NegotiateAuthentication server = new(new NegotiateAuthenticationServerOptions + { + Binding = serverBinding, + }); + + RunExchange(client, server, expectAuthenticated: false, out _, out NegotiateAuthenticationStatusCode lastServerStatus); + + Assert.Equal(NegotiateAuthenticationStatusCode.BadBinding, lastServerStatus); + } + + private static SafeChannelBindingHandle CreateChannelBinding(ChannelBindingKind kind, byte hashSeed) + { + const int HashLength = 32; + SafeChannelBindingHandle handle = new SafeChannelBindingHandle(kind); + byte[] hash = new byte[HashLength]; + Array.Fill(hash, hashSeed); + handle.SetCertHash(hash); + return handle; + } + + private static void RunExchange( + NegotiateAuthentication client, + NegotiateAuthentication server, + bool expectAuthenticated, + out NegotiateAuthenticationStatusCode lastClientStatus, + out NegotiateAuthenticationStatusCode lastServerStatus) + { + byte[]? serverBlob = null; + byte[]? clientBlob; + lastClientStatus = NegotiateAuthenticationStatusCode.ContinueNeeded; + lastServerStatus = NegotiateAuthenticationStatusCode.ContinueNeeded; + + const int MaxIterations = 20; + for (int i = 0; i < MaxIterations; i++) + { + clientBlob = client.GetOutgoingBlob(serverBlob, out lastClientStatus); + if (lastClientStatus >= NegotiateAuthenticationStatusCode.GenericFailure) + { + return; + } + if (clientBlob is null) + { + return; + } + + serverBlob = server.GetOutgoingBlob(clientBlob, out lastServerStatus); + if (lastServerStatus >= NegotiateAuthenticationStatusCode.GenericFailure) + { + return; + } + if (lastServerStatus == NegotiateAuthenticationStatusCode.Completed && + lastClientStatus == NegotiateAuthenticationStatusCode.Completed) + { + return; + } + } + + if (expectAuthenticated) + { + Assert.Fail("Authentication did not complete within iteration limit."); + } + } + } +} diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateAuthenticationKerberosTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateAuthenticationKerberosTest.cs index 28db989b9fdfad..778248a08dddb0 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateAuthenticationKerberosTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/NegotiateAuthenticationKerberosTest.cs @@ -9,7 +9,7 @@ namespace System.Net.Security.Tests { [ConditionalClass(typeof(KerberosExecutor), nameof(KerberosExecutor.IsSupported))] - public class NegotiateAuthenticationKerberosTest + public partial class NegotiateAuthenticationKerberosTest { private readonly ITestOutputHelper _testOutputHelper; diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj b/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj index 4bcd5c8bcdf0d1..44eb210f08eadb 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj @@ -37,6 +37,8 @@ + @@ -110,6 +112,12 @@ Link="Common\System\Net\Security\SslClientAuthenticationOptionsExtensions.cs" /> + + diff --git a/src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs b/src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs index 70ab0a338c3b68..2d8aead2bdb7af 100644 --- a/src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs +++ b/src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs @@ -24,6 +24,7 @@ public class NegotiateAuthenticationTests private static bool UseManagedNtlm => PlatformDetection.IsUbuntu24 || PlatformDetection.IsUbuntu26 || PlatformDetection.IsOpenSUSE16 || (PlatformDetection.IsRedHatFamily && !PlatformDetection.IsOpenSsl3); private static bool IsNtlmAvailable => UseManagedNtlm || Capability.IsNtlmInstalled() || OperatingSystem.IsAndroid() || OperatingSystem.IsTvOS(); private static bool IsNtlmUnavailable => !IsNtlmAvailable; + private static bool OSDoesNotSupportExtendedProtection => !ExtendedProtectionPolicy.OSSupportsExtendedProtection; private static NetworkCredential s_testCredentialRight = new NetworkCredential("rightusername", "rightpassword"); private static NetworkCredential s_testCredentialWrong = new NetworkCredential("rightusername", "wrongpassword"); @@ -438,6 +439,12 @@ public void NegotiateCorrectExchangeTest(bool requestMIC, bool requestConfidenti while (!ntAuth.IsAuthenticated); } + [ConditionalFact(typeof(NegotiateAuthenticationTests), nameof(OSDoesNotSupportExtendedProtection))] + public void ExtendedProtectionPolicy_NotSupported_Throws() + { + Assert.Throws(() => new NegotiateAuthentication(new NegotiateAuthenticationServerOptions { Policy = new ExtendedProtectionPolicy(PolicyEnforcement.Always) })); + } + public static IEnumerable MalformedChallengeBlobs() { // Truncated below sizeof(ChallengeMessage). The fixed header is 56 bytes. @@ -531,12 +538,5 @@ public void NtlmMalformedChallenge_ReturnsInvalidToken(string scenario, Func(() => new NegotiateAuthentication(new NegotiateAuthenticationServerOptions { Policy = new ExtendedProtectionPolicy(PolicyEnforcement.Always) })); - } } } diff --git a/src/libraries/System.Net.Security/tests/UnitTests/System/Security/Authentication/ExtendedProtection/ExtendedProtectionPolicyTest.cs b/src/libraries/System.Net.Security/tests/UnitTests/System/Security/Authentication/ExtendedProtection/ExtendedProtectionPolicyTest.cs index 9bd3c08b08acff..bec5a03121d38a 100644 --- a/src/libraries/System.Net.Security/tests/UnitTests/System/Security/Authentication/ExtendedProtection/ExtendedProtectionPolicyTest.cs +++ b/src/libraries/System.Net.Security/tests/UnitTests/System/Security/Authentication/ExtendedProtection/ExtendedProtectionPolicyTest.cs @@ -56,20 +56,6 @@ public void Constructor_PolicyEnforcement_MembersAreSet() Assert.Equal(ProtectionScenario.TransportSelected, extendedProtectionPolicy.ProtectionScenario); } - [Fact] - [PlatformSpecific(TestPlatforms.Windows)] - public void ExtendedProtectionPolicy_OSSupportsExtendedProtection_Windows() - { - Assert.True(ExtendedProtectionPolicy.OSSupportsExtendedProtection); - } - - [Fact] - [PlatformSpecific(TestPlatforms.AnyUnix)] - public void ExtendedProtectionPolicy_OSSupportsExtendedProtection_NonWindows() - { - Assert.False(ExtendedProtectionPolicy.OSSupportsExtendedProtection); - } - [Fact] public void ExtendedProtectionPolicy_Properties() { diff --git a/src/native/libs/System.Net.Security.Native/pal_gssapi.c b/src/native/libs/System.Net.Security.Native/pal_gssapi.c index db646050971865..26a82f5ecbce01 100644 --- a/src/native/libs/System.Net.Security.Native/pal_gssapi.c +++ b/src/native/libs/System.Net.Security.Native/pal_gssapi.c @@ -338,6 +338,18 @@ uint32_t NetSecurityNative_InitSecContextEx(uint32_t* minorStatus, // Note: claimantCredHandle can be null // Note: *contextHandle is null only in the first call and non-null in the subsequent calls + // Guard against a malformed channel binding token size. A negative value would be cast to a + // huge size_t below and cause gss_init_sec_context to read past the buffer. + if (cbtSize < 0) + { + *minorStatus = 0; + outBuffer->length = 0; + outBuffer->data = NULL; + *retFlags = 0; + *isNtlmUsed = 0; + return GSS_S_BAD_BINDINGS; + } + #if HAVE_GSS_SPNEGO_MECHANISM gss_OID krbMech = GSS_KRB5_MECHANISM; gss_OID desiredMech; @@ -405,6 +417,8 @@ uint32_t NetSecurityNative_InitSecContextEx(uint32_t* minorStatus, uint32_t NetSecurityNative_AcceptSecContext(uint32_t* minorStatus, GssCredId* acceptorCredHandle, GssCtxId** contextHandle, + void* cbt, + int32_t cbtSize, uint8_t* inputBytes, uint32_t inputLength, PAL_GssBuffer* outBuffer, @@ -416,18 +430,40 @@ uint32_t NetSecurityNative_AcceptSecContext(uint32_t* minorStatus, assert(contextHandle != NULL); assert(inputBytes != NULL || inputLength == 0); assert(outBuffer != NULL); + assert(retFlags != NULL); assert(isNtlmUsed != NULL); + assert(cbt != NULL || cbtSize == 0); // Note: *contextHandle is null only in the first call and non-null in the subsequent calls + // Guard against a malformed channel binding token size. A negative value would be cast to a + // huge size_t below and cause gss_accept_sec_context to read past the buffer. + if (cbtSize < 0) + { + *minorStatus = 0; + outBuffer->length = 0; + outBuffer->data = NULL; + *retFlags = 0; + *isNtlmUsed = 0; + return GSS_S_BAD_BINDINGS; + } + GssBuffer inputToken = {.length = inputLength, .value = inputBytes}; GssBuffer gssBuffer = {.length = 0, .value = NULL}; + struct gss_channel_bindings_struct gssCbt; + if (cbt != NULL) + { + memset(&gssCbt, 0, sizeof(struct gss_channel_bindings_struct)); + gssCbt.application_data.length = (size_t)cbtSize; + gssCbt.application_data.value = cbt; + } + gss_OID mechType = GSS_C_NO_OID; uint32_t majorStatus = gss_accept_sec_context(minorStatus, contextHandle, acceptorCredHandle, &inputToken, - GSS_C_NO_CHANNEL_BINDINGS, + (cbt != NULL) ? &gssCbt : GSS_C_NO_CHANNEL_BINDINGS, NULL, &mechType, &gssBuffer, diff --git a/src/native/libs/System.Net.Security.Native/pal_gssapi.h b/src/native/libs/System.Net.Security.Native/pal_gssapi.h index 10be636c7789e5..2da7ed172aa170 100644 --- a/src/native/libs/System.Net.Security.Native/pal_gssapi.h +++ b/src/native/libs/System.Net.Security.Native/pal_gssapi.h @@ -147,6 +147,8 @@ Shims the gss_accept_sec_context method. PALEXPORT uint32_t NetSecurityNative_AcceptSecContext(uint32_t* minorStatus, GssCredId* acceptorCredHandle, GssCtxId** contextHandle, + void* cbt, + int32_t cbtSize, uint8_t* inputBytes, uint32_t inputLength, PAL_GssBuffer* outBuffer,