The latest changes to the System.Net.Security.Native shim layer fixed a lot of important
bugs for Linux Kerberos usage. But this created a new problem since SqlClient ships
in out-of-band NuGet packages separate from the .NET Core runtime. SqlClient builds
out of the CoreFx repo and uses the common source includes for Kerberos authentication.
This created an unexpected dependency on the System.Net.Security.Native shim layer.
The recent changes to these API signatures caused problems with different combinations
of SqlClient NuGet packages and .NET Core 2.x versus .NET Core 3.0.
After discussion with the SqlClient team, we decided to rework the changes to these
native APIs so that they would remain compatible across all .NET Core versions.
Long-term, the plan is to implement dotnet/corefx#36896 to expose a Kerberos API in .NET Core which
could be used by SqlClient and other consumers.
Closes dotnet/corefx#37183
Closes dotnet/corefx#25205
Commit migrated from https://github.com/dotnet/corefx/commit/
7f920b2984a97ce8643bc8e64a93e7bd4d8a059e
int inputNameByteCount,
out SafeGssNameHandle outputName);
- [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_ImportTargetName")]
- internal static extern Status ImportTargetName(
+ [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_ImportPrincipalName")]
+ internal static extern Status ImportPrincipalName(
out Status minorStatus,
string inputName,
int inputNameByteCount,
SafeGssCredHandle initiatorCredHandle,
ref SafeGssContextHandle contextHandle,
bool isNtlmOnly,
+ SafeGssNameHandle targetName,
+ uint reqFlags,
+ byte[] inputBytes,
+ int inputLength,
+ ref GssBuffer token,
+ out uint retFlags,
+ out bool isNtlmUsed);
+
+ [DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_InitSecContextEx")]
+ internal static extern Status InitSecContext(
+ out Status minorStatus,
+ SafeGssCredHandle initiatorCredHandle,
+ ref SafeGssContextHandle contextHandle,
+ bool isNtlmOnly,
IntPtr cbt,
int cbtSize,
SafeGssNameHandle targetName,
Debug.Assert(!string.IsNullOrEmpty(name), "Invalid target name passed to SafeGssNameHandle create");
SafeGssNameHandle retHandle;
Interop.NetSecurityNative.Status minorStatus;
- Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.ImportTargetName(
+ Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.ImportPrincipalName(
out minorStatus, name, Encoding.UTF8.GetByteCount(name), out retHandle);
if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE)
out uint outFlags,
out bool 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 (channelBinding != null)
- {
- int appDataOffset = Marshal.SizeOf<SecChannelBindings>();
- Debug.Assert(appDataOffset < channelBinding.Size);
- cbtAppData = channelBinding.DangerousGetHandle() + appDataOffset;
- cbtAppDataSize = channelBinding.Size - appDataOffset;
- }
-
outputBuffer = null;
outFlags = 0;
try
{
Interop.NetSecurityNative.Status minorStatus;
- status = Interop.NetSecurityNative.InitSecContext(out minorStatus,
- credential,
- ref context,
- isNtlm,
- cbtAppData,
- cbtAppDataSize,
- targetName,
- (uint)inFlags,
- buffer,
- (buffer == null) ? 0 : buffer.Length,
- ref token,
- out outFlags,
- out isNtlmUsed);
+
+ if (channelBinding != null)
+ {
+ // If a TLS channel binding token (cbt) is available then get the pointer
+ // to the application specific data.
+ int appDataOffset = Marshal.SizeOf<SecChannelBindings>();
+ Debug.Assert(appDataOffset < channelBinding.Size);
+ IntPtr cbtAppData = channelBinding.DangerousGetHandle() + appDataOffset;
+ int cbtAppDataSize = channelBinding.Size - appDataOffset;
+ status = Interop.NetSecurityNative.InitSecContext(out minorStatus,
+ credential,
+ ref context,
+ isNtlm,
+ cbtAppData,
+ cbtAppDataSize,
+ targetName,
+ (uint)inFlags,
+ buffer,
+ (buffer == null) ? 0 : buffer.Length,
+ ref token,
+ out outFlags,
+ out isNtlmUsed);
+ }
+ else
+ {
+ status = Interop.NetSecurityNative.InitSecContext(out minorStatus,
+ credential,
+ ref context,
+ isNtlm,
+ targetName,
+ (uint)inFlags,
+ buffer,
+ (buffer == null) ? 0 : buffer.Length,
+ ref token,
+ out outFlags,
+ out isNtlmUsed);
+ }
if ((status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) &&
(status != Interop.NetSecurityNative.Status.GSS_S_CONTINUE_NEEDED))
{
try
{
- // Convert any "SERVICE/HOST" style of targetName to use "SERVICE@HOST" style.
- // This is because the System.Net.Security.Native GSS-API layer uses
- // GSS_C_NT_HOSTBASED_SERVICE format for targetName.
- _targetName = SafeGssNameHandle.CreateTarget(targetName.Replace('/', '@'));
+ _targetName = SafeGssNameHandle.CreateTarget(targetName);
}
catch
{
return gss_import_name(minorStatus, &inputNameBuffer, GSS_C_NT_USER_NAME, outputName);
}
-uint32_t NetSecurityNative_ImportTargetName(uint32_t* minorStatus,
- char* inputName,
- uint32_t inputNameLen,
- GssName** outputName)
+uint32_t NetSecurityNative_ImportPrincipalName(uint32_t* minorStatus,
+ char* inputName,
+ uint32_t inputNameLen,
+ GssName** outputName)
{
assert(minorStatus != NULL);
assert(inputName != NULL);
assert(outputName != NULL);
assert(*outputName == NULL);
+ // Principal name will usually be in the form SERVICE/HOST. But SPNEGO protocol prefers
+ // GSS_C_NT_HOSTBASED_SERVICE format. That format uses '@' separator instead of '/' between
+ // service name and host name. So convert input string into that format.
+ char* ptrSlash = memchr(inputName, '/', inputNameLen);
+ char* inputNameCopy = NULL;
+ if (ptrSlash != NULL)
+ {
+ inputNameCopy = (char*) malloc(inputNameLen);
+ if (inputNameCopy != NULL)
+ {
+ memcpy(inputNameCopy, inputName, inputNameLen);
+ inputNameCopy[ptrSlash - inputName] = '@';
+ inputName = inputNameCopy;
+ }
+ else
+ {
+ *minorStatus = 0;
+ return GSS_S_BAD_NAME;
+ }
+ }
+
GssBuffer inputNameBuffer = {.length = inputNameLen, .value = inputName};
- return gss_import_name(minorStatus, &inputNameBuffer, GSS_C_NT_HOSTBASED_SERVICE, outputName);
+ uint32_t result = gss_import_name(minorStatus, &inputNameBuffer, GSS_C_NT_HOSTBASED_SERVICE, outputName);
+
+ if (inputNameCopy != NULL)
+ {
+ free(inputNameCopy);
+ }
+
+ return result;
}
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,
uint32_t* retFlags,
int32_t* isNtlmUsed)
{
+ return NetSecurityNative_InitSecContextEx(minorStatus,
+ claimantCredHandle,
+ contextHandle,
+ isNtlm,
+ NULL,
+ 0,
+ targetName,
+ reqFlags,
+ inputBytes,
+ inputLength,
+ outBuffer,
+ retFlags,
+ isNtlmUsed);
+}
+
+uint32_t NetSecurityNative_InitSecContextEx(uint32_t* minorStatus,
+ GssCredId* claimantCredHandle,
+ GssCtxId** contextHandle,
+ uint32_t isNtlm,
+ void* cbt,
+ int32_t cbtSize,
+ GssName* targetName,
+ uint32_t reqFlags,
+ uint8_t* inputBytes,
+ uint32_t inputLength,
+ PAL_GssBuffer* outBuffer,
+ uint32_t* retFlags,
+ int32_t* isNtlmUsed)
+{
assert(minorStatus != NULL);
assert(contextHandle != NULL);
assert(isNtlm == 0 || isNtlm == 1);
/*
Shims the gss_import_name method with nametype = GSS_C_NT_USER_NAME.
*/
-DLLEXPORT uint32_t NetSecurityNative_ImportTargetName(uint32_t* minorStatus,
+DLLEXPORT uint32_t NetSecurityNative_ImportPrincipalName(uint32_t* minorStatus,
char* inputName,
uint32_t inputNameLen,
GssName** outputName);
GssCredId* claimantCredHandle,
GssCtxId** contextHandle,
uint32_t isNtlm,
- void* cbt,
- int32_t cbtSize,
GssName* targetName,
uint32_t reqFlags,
uint8_t* inputBytes,
uint32_t* retFlags,
int32_t* isNtlmUsed);
+DLLEXPORT uint32_t NetSecurityNative_InitSecContextEx(uint32_t* minorStatus,
+ GssCredId* claimantCredHandle,
+ GssCtxId** contextHandle,
+ uint32_t isNtlm,
+ void* cbt,
+ int32_t cbtSize,
+ GssName* targetName,
+ uint32_t reqFlags,
+ uint8_t* inputBytes,
+ uint32_t inputLength,
+ PAL_GssBuffer* outBuffer,
+ uint32_t* retFlags,
+ int32_t* isNtlmUsed);
+
/*
Shims the gss_accept_sec_context method.
*/
<Compile Include="System\Data\SqlClient\SNI\SNINpHandle.cs" />
<Compile Include="System\Data\SqlClient\SNI\SNIPacket.cs" />
<Compile Include="System\Data\SqlClient\SNI\SNIProxy.cs" />
- <Compile Include="System\Data\SqlClient\SNI\SNIProxy.Windows.cs" Condition="'$(TargetsNetCoreApp)' != 'true' OR '$(TargetsUnix)' != 'true'" />
- <Compile Include="System\Data\SqlClient\SNI\SNIProxy.Unix.cs" Condition="'$(TargetsNetCoreApp)' == 'true' AND '$(TargetsUnix)' == 'true'" />
<Compile Include="System\Data\SqlClient\SNI\SNITcpHandle.cs" />
<Compile Include="System\Data\SqlClient\SNI\SslOverTdsStream.cs" />
<Compile Include="System\Data\SqlClient\SNI\SNICommon.cs" />
+++ /dev/null
-// 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.
-
-namespace System.Data.SqlClient.SNI
-{
- /// <summary>
- /// Managed SNI proxy implementation. Contains many SNI entry points used by SqlClient.
- /// </summary>
- internal partial class SNIProxy
- {
- // On Unix/Linux the format for the SPN is SERVICE@HOST. This is because the System.Net.Security.Native
- // GSS-API layer uses GSS_C_NT_HOSTBASED_SERVICE format for the SPN.
- private const string SpnServiceHostSeparator = "@";
- }
-}
+++ /dev/null
-// 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.
-
-namespace System.Data.SqlClient.SNI
-{
- /// <summary>
- /// Managed SNI proxy implementation. Contains many SNI entry points used by SqlClient.
- /// </summary>
- internal partial class SNIProxy
- {
- // On Windows the format for the SPN is SERVICE/HOST. So the separator character between the
- // SERVICE and HOST components is "/".
- private const string SpnServiceHostSeparator = "/";
- }
-}
{
private const int DefaultSqlServerPort = 1433;
private const int DefaultSqlServerDacPort = 1434;
- private const string SqlServerSpnHeader = "MSSQLSvc";
+ private const string SqlServerSpnHeader = "MSSQLSvc/";
public static readonly SNIProxy Singleton = new SNIProxy();
// If the DNS lookup failed, then resort to using the user provided hostname to construct the SPN.
fullyQualifiedDomainName = hostEntry?.HostName ?? hostNameOrAddress;
}
- string serverSpn = SqlServerSpnHeader + SpnServiceHostSeparator + fullyQualifiedDomainName;
+ string serverSpn = SqlServerSpnHeader + fullyQualifiedDomainName;
if (!string.IsNullOrWhiteSpace(portOrInstanceName))
{
serverSpn += ":" + portOrInstanceName;