Enable TLS channel binding token for NTLM authentication on Linux (dotnet/corefx...
authorDavid Shulman <david.shulman@microsoft.com>
Fri, 1 Feb 2019 01:31:03 +0000 (17:31 -0800)
committerGitHub <noreply@github.com>
Fri, 1 Feb 2019 01:31:03 +0000 (17:31 -0800)
This PR adds support for passing in the computed TLS channel binding
token (cbt) into the gssapi during NTLM authentication. This included
removing the code that threw the PlatformNotSupported exception.

No new tests were added to this PR since it requires machine changes
to test. However, I manually tested various combinations of NTLM
authentication including when the server requires (or NOT) the cbt
to be used. Currently only Windows Servers uses this functionality
as part of the "Extended Protection for Authentication" feature.

CurlHandler does not support using the cbt during NTLM authentication.
This is a problem with libcurl itself.

As part of this PR I removed a redundant assert and refactored the
SecChannelBindings structure into src/Common.

Fixes dotnet/corefx#34879

Commit migrated from https://github.com/dotnet/corefx/commit/6575de65608c95717b2a032b689adae32378778f

14 files changed:
src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs
src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs
src/libraries/Common/src/System/Net/Security/Unix/SecChannelBindings.cs [new file with mode: 0644]
src/libraries/Native/Unix/System.Net.Security.Native/pal_gssapi.c
src/libraries/Native/Unix/System.Net.Security.Native/pal_gssapi.h
src/libraries/System.Data.SqlClient/src/Resources/Strings.resx
src/libraries/System.Data.SqlClient/src/System.Data.SqlClient.csproj
src/libraries/System.Net.Http/src/Resources/Strings.resx
src/libraries/System.Net.Http/src/System.Net.Http.csproj
src/libraries/System.Net.Mail/src/Resources/Strings.resx
src/libraries/System.Net.Mail/src/System.Net.Mail.csproj
src/libraries/System.Net.Security/src/Resources/Strings.resx
src/libraries/System.Net.Security/src/System.Net.Security.csproj
src/libraries/System.Net.Security/src/System/Net/Security/Pal.Managed/SafeChannelBindingHandle.cs

index 3da7cf4..249fbc3 100644 (file)
@@ -74,6 +74,8 @@ internal static partial class Interop
             SafeGssCredHandle initiatorCredHandle,
             ref SafeGssContextHandle contextHandle,
             bool isNtlmOnly,
+            IntPtr cbt,
+            int cbtSize,
             SafeGssNameHandle targetName,
             uint reqFlags,
             byte[] inputBytes,
index 74ed1b5..9e55c5f 100644 (file)
@@ -98,6 +98,7 @@ namespace System.Net.Security
             ref SafeGssContextHandle context,
             SafeGssCredHandle credential,
             bool isNtlm,
+            SecurityBuffer cbt,
             SafeGssNameHandle targetName,
             Interop.NetSecurityNative.GssFlags inFlags,
             byte[] buffer,
@@ -105,6 +106,18 @@ namespace System.Net.Security
             out uint outFlags,
             out int isNtlmUsed)
         {
+            // If a TLS channel binding token (cbt) is available then get the pointer
+            // to the application specific data.
+            IntPtr cbtAppData = IntPtr.Zero;
+            int cbtAppDataSize = 0;
+            if (cbt != null)
+            {
+                int appDataOffset = Marshal.SizeOf<SecChannelBindings>();
+                Debug.Assert(appDataOffset < cbt.size);
+                cbtAppData = cbt.unmanagedToken.DangerousGetHandle() + appDataOffset;
+                cbtAppDataSize = cbt.size - appDataOffset;
+            }
+
             outputBuffer = null;
             outFlags = 0;
 
@@ -126,6 +139,8 @@ namespace System.Net.Security
                                                           credential,
                                                           ref context,
                                                           isNtlm,
+                                                          cbtAppData,
+                                                          cbtAppDataSize,
                                                           targetName,
                                                           (uint)inFlags,
                                                           buffer,
@@ -152,6 +167,7 @@ namespace System.Net.Security
         private static SecurityStatusPal EstablishSecurityContext(
           SafeFreeNegoCredentials credential,
           ref SafeDeleteContext context,
+          SecurityBuffer cbt,
           string targetName,
           ContextFlagsPal inFlags,
           SecurityBuffer inputBuffer,
@@ -177,6 +193,7 @@ namespace System.Net.Security
                    ref contextHandle,
                    credential.GssCredential,
                    isNtlmOnly,
+                   cbt,
                    negoContext.TargetName,
                    inputFlags,
                    inputBuffer?.token,
@@ -224,12 +241,6 @@ namespace System.Net.Security
             SecurityBuffer outSecurityBuffer,
             ref ContextFlagsPal contextFlags)
         {
-            // TODO (Issue #3718): The second buffer can contain a channel binding which is not supported
-            if ((null != inSecurityBufferArray) && (inSecurityBufferArray.Length > 1))
-            {
-                throw new PlatformNotSupportedException(SR.net_nego_channel_binding_not_supported);
-            }
-
             SafeFreeNegoCredentials negoCredentialsHandle = (SafeFreeNegoCredentials)credentialsHandle;
 
             if (negoCredentialsHandle.IsDefault && string.IsNullOrEmpty(spn))
@@ -237,9 +248,17 @@ namespace System.Net.Security
                 throw new PlatformNotSupportedException(SR.net_nego_not_supported_empty_target_with_defaultcreds);
             }
 
+            SecurityBuffer cbtBuffer = null;
+            if ((inSecurityBufferArray != null) && (inSecurityBufferArray.Length > 1))
+            {
+                Debug.Assert(inSecurityBufferArray[1].type == SecurityBufferType.SECBUFFER_CHANNEL_BINDINGS);
+                cbtBuffer = inSecurityBufferArray[1];
+            }
+
             SecurityStatusPal status = EstablishSecurityContext(
                 negoCredentialsHandle,
                 ref securityContext,
+                cbtBuffer,
                 spn,
                 requestedContextFlags,
                 ((inSecurityBufferArray != null && inSecurityBufferArray.Length != 0) ? inSecurityBufferArray[0] : null),
diff --git a/src/libraries/Common/src/System/Net/Security/Unix/SecChannelBindings.cs b/src/libraries/Common/src/System/Net/Security/Unix/SecChannelBindings.cs
new file mode 100644 (file)
index 0000000..8843504
--- /dev/null
@@ -0,0 +1,20 @@
+// 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.Runtime.InteropServices;
+
+namespace System.Net.Security
+{
+    [StructLayout(LayoutKind.Sequential)]
+    internal struct SecChannelBindings
+    {
+        internal int InitiatorLength;
+        internal int InitiatorOffset;
+        internal int AcceptorAddrType;
+        internal int AcceptorLength;
+        internal int AcceptorOffset;
+        internal int ApplicationDataLength;
+        internal int ApplicationDataOffset;
+    }
+}
index cd99491..231e35b 100644 (file)
@@ -173,6 +173,8 @@ uint32_t NetSecurityNative_InitSecContext(uint32_t* minorStatus,
                                           GssCredId* claimantCredHandle,
                                           GssCtxId** contextHandle,
                                           uint32_t isNtlm,
+                                          void* cbt,
+                                          int32_t cbtSize,
                                           GssName* targetName,
                                           uint32_t reqFlags,
                                           uint8_t* inputBytes,
@@ -189,7 +191,7 @@ uint32_t NetSecurityNative_InitSecContext(uint32_t* minorStatus,
     assert(outBuffer != NULL);
     assert(retFlags != NULL);
     assert(isNtlmUsed != NULL);
-    assert(inputBytes != NULL || inputLength == 0);
+    assert(cbt != NULL || cbtSize == 0);
 
 // Note: claimantCredHandle can be null
 // Note: *contextHandle is null only in the first call and non-null in the subsequent calls
@@ -226,6 +228,14 @@ uint32_t NetSecurityNative_InitSecContext(uint32_t* minorStatus,
     GssBuffer gssBuffer = {.length = 0, .value = NULL};
     gss_OID_desc* outmech;
 
+    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;
+    }
+
     uint32_t majorStatus = gss_init_sec_context(minorStatus,
                                                 claimantCredHandle,
                                                 contextHandle,
@@ -233,7 +243,7 @@ uint32_t NetSecurityNative_InitSecContext(uint32_t* minorStatus,
                                                 desiredMech,
                                                 reqFlags,
                                                 0,
-                                                GSS_C_NO_CHANNEL_BINDINGS,
+                                                (cbt != NULL) ? &gssCbt : GSS_C_NO_CHANNEL_BINDINGS,
                                                 &inputToken,
                                                 &outmech,
                                                 &gssBuffer,
index f36ad2e..b2c911a 100644 (file)
@@ -107,6 +107,8 @@ DLLEXPORT uint32_t NetSecurityNative_InitSecContext(uint32_t* minorStatus,
                                                     GssCredId* claimantCredHandle,
                                                     GssCtxId** contextHandle,
                                                     uint32_t isNtlm,
+                                                    void* cbt,
+                                                    int32_t cbtSize,
                                                     GssName* targetName,
                                                     uint32_t reqFlags,
                                                     uint8_t* inputBytes,
index 4f7a63a..73f9579 100644 (file)
   <data name="AZURESQL_ChinaEndpoint" xml:space="preserve">
     <value>.database.chinacloudapi.cn</value>
   </data>
-  <data name="net_nego_channel_binding_not_supported" xml:space="preserve">
-    <value>No support for channel binding on operating systems other than Windows.</value>
-  </data>
   <data name="net_gssapi_operation_failed_detailed" xml:space="preserve">
     <value>GSSAPI operation failed with error - {0} ({1}).</value>
   </data>
index f8a2e62..757b391 100644 (file)
     <Compile Include="$(CommonPath)\System\Net\Security\Unix\SafeFreeNegoCredentials.cs">
       <Link>Common\System\Net\Security\Unix\SafeFreeNegoCredentials.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\System\Net\Security\Unix\SecChannelBindings.cs">
+      <Link>Common\System\Net\Security\Unix\SecChannelBindings.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)\Interop\Unix\Interop.Libraries.cs">
       <Link>Common\Interop\Unix\Interop.Libraries.cs</Link>
     </Compile>
index 7bbdbe7..090b35f 100644 (file)
   <data name="net_gssapi_ntlm_missing_plugin" xml:space="preserve">
     <value>NTLM authentication requires the GSSAPI plugin 'gss-ntlmssp'.</value>
   </data>
-  <data name="net_nego_channel_binding_not_supported" xml:space="preserve">
-    <value>No support for channel binding on operating systems other than Windows.</value>
-  </data>
   <data name="net_ntlm_not_possible_default_cred" xml:space="preserve">
     <value>NTLM authentication is not possible with default credentials on this platform.</value>
   </data>
index f935636..4fd08d8 100644 (file)
     <Compile Include="$(CommonPath)\System\Net\Security\Unix\SafeFreeNegoCredentials.cs">
       <Link>Common\System\Net\Security\Unix\SafeFreeNegoCredentials.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\System\Net\Security\Unix\SecChannelBindings.cs">
+      <Link>Common\System\Net\Security\Unix\SecChannelBindings.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)\Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.cs">
       <Link>Common\Interop\Unix\System.Net.Security.Native\Interop.NetSecurityNative.cs</Link>
     </Compile>
index 1c002ca..2a6b4dc 100644 (file)
@@ -95,9 +95,6 @@
   <data name="net_gssapi_ntlm_missing_plugin" xml:space="preserve">
     <value>NTLM authentication requires the GSSAPI plugin 'gss-ntlmssp'.</value>
   </data>
-  <data name="net_nego_channel_binding_not_supported" xml:space="preserve">
-    <value>No support for channel binding on operating systems other than Windows.</value>
-  </data>
   <data name="net_ntlm_not_possible_default_cred" xml:space="preserve">
     <value>NTLM authentication is not possible with default credentials on this platform.</value>
   </data>
index 73b49e2..68f1447 100644 (file)
     <Compile Include="$(CommonPath)\System\Net\Security\Unix\SafeFreeNegoCredentials.cs">
       <Link>Common\System\Net\Security\Unix\SafeFreeNegoCredentials.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\System\Net\Security\Unix\SecChannelBindings.cs">
+      <Link>Common\System\Net\Security\Unix\SecChannelBindings.cs</Link>
+    </Compile>
   </ItemGroup>
   <!-- Windows specific files -->
   <ItemGroup Condition="'$(TargetsWindows)'=='true'">
index 711bbc3..4befab0 100644 (file)
   <data name="net_context_buffer_too_small" xml:space="preserve">
     <value>Insufficient buffer space. Required: {0} Actual: {1}.</value>
   </data>
-  <data name="net_nego_channel_binding_not_supported" xml:space="preserve">
-    <value>No support for channel binding on operating systems other than Windows.</value>
-  </data>
   <data name="net_ntlm_not_possible_default_cred" xml:space="preserve">
     <value>NTLM authentication is not possible with default credentials on this platform.</value>
   </data>
index 244cfe6..9226a77 100644 (file)
     <Compile Include="$(CommonPath)\System\Net\Security\Unix\SafeFreeNegoCredentials.cs">
       <Link>Common\System\Net\Security\Unix\SafeFreeNegoCredentials.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\System\Net\Security\Unix\SecChannelBindings.cs">
+      <Link>Common\System\Net\Security\Unix\SecChannelBindings.cs</Link>
+    </Compile>
     <Compile Include="System\Net\Security\NegotiateStreamPal.Unix.cs" />
     <Compile Include="System\Net\Security\Pal.Managed\EndpointChannelBindingToken.cs" />
     <Compile Include="System\Net\Security\Pal.Managed\SafeChannelBindingHandle.cs" />
index ad5bd76..60e5cee 100644 (file)
@@ -11,18 +11,6 @@ namespace System.Net.Security
 {
     internal sealed class SafeChannelBindingHandle : ChannelBinding
     {
-        [StructLayout(LayoutKind.Sequential)]
-        private struct SecChannelBindings
-        {
-            internal int InitiatorLength;
-            internal int InitiatorOffset;
-            internal int AcceptorAddrType;
-            internal int AcceptorLength;
-            internal int AcceptorOffset;
-            internal int ApplicationDataLength;
-            internal int ApplicationDataOffset;
-        }
-
         private const int CertHashMaxSize = 128;
         private static readonly byte[] s_tlsServerEndPointByteArray = Encoding.UTF8.GetBytes("tls-server-end-point:");
         private static readonly byte[] s_tlsUniqueByteArray = Encoding.UTF8.GetBytes("tls-unique:");