Skip to content

Commit e334603

Browse files
authored
Rework Linux/Kerberos native interop layer (dotnet/corefx#38377)
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 dotnet/corefx@7f920b2
1 parent 624f160 commit e334603

File tree

10 files changed

+134
-78
lines changed

10 files changed

+134
-78
lines changed

src/libraries/Common/src/Interop/Unix/System.Net.Security.Native/Interop.NetSecurityNative.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ internal static extern Status ImportUserName(
3636
int inputNameByteCount,
3737
out SafeGssNameHandle outputName);
3838

39-
[DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_ImportTargetName")]
40-
internal static extern Status ImportTargetName(
39+
[DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_ImportPrincipalName")]
40+
internal static extern Status ImportPrincipalName(
4141
out Status minorStatus,
4242
string inputName,
4343
int inputNameByteCount,
@@ -69,6 +69,20 @@ internal static extern Status ReleaseCred(
6969
ref IntPtr credHandle);
7070

7171
[DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_InitSecContext")]
72+
internal static extern Status InitSecContext(
73+
out Status minorStatus,
74+
SafeGssCredHandle initiatorCredHandle,
75+
ref SafeGssContextHandle contextHandle,
76+
bool isNtlmOnly,
77+
SafeGssNameHandle targetName,
78+
uint reqFlags,
79+
byte[] inputBytes,
80+
int inputLength,
81+
ref GssBuffer token,
82+
out uint retFlags,
83+
out bool isNtlmUsed);
84+
85+
[DllImport(Interop.Libraries.NetSecurityNative, EntryPoint="NetSecurityNative_InitSecContextEx")]
7286
internal static extern Status InitSecContext(
7387
out Status minorStatus,
7488
SafeGssCredHandle initiatorCredHandle,

src/libraries/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public static SafeGssNameHandle CreateTarget(string name)
3636
Debug.Assert(!string.IsNullOrEmpty(name), "Invalid target name passed to SafeGssNameHandle create");
3737
SafeGssNameHandle retHandle;
3838
Interop.NetSecurityNative.Status minorStatus;
39-
Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.ImportTargetName(
39+
Interop.NetSecurityNative.Status status = Interop.NetSecurityNative.ImportPrincipalName(
4040
out minorStatus, name, Encoding.UTF8.GetByteCount(name), out retHandle);
4141

4242
if (status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE)

src/libraries/Common/src/System/Net/Security/NegotiateStreamPal.Unix.cs

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -107,18 +107,6 @@ private static bool GssInitSecurityContext(
107107
out uint outFlags,
108108
out bool isNtlmUsed)
109109
{
110-
// If a TLS channel binding token (cbt) is available then get the pointer
111-
// to the application specific data.
112-
IntPtr cbtAppData = IntPtr.Zero;
113-
int cbtAppDataSize = 0;
114-
if (channelBinding != null)
115-
{
116-
int appDataOffset = Marshal.SizeOf<SecChannelBindings>();
117-
Debug.Assert(appDataOffset < channelBinding.Size);
118-
cbtAppData = channelBinding.DangerousGetHandle() + appDataOffset;
119-
cbtAppDataSize = channelBinding.Size - appDataOffset;
120-
}
121-
122110
outputBuffer = null;
123111
outFlags = 0;
124112

@@ -138,19 +126,43 @@ private static bool GssInitSecurityContext(
138126
try
139127
{
140128
Interop.NetSecurityNative.Status minorStatus;
141-
status = Interop.NetSecurityNative.InitSecContext(out minorStatus,
142-
credential,
143-
ref context,
144-
isNtlm,
145-
cbtAppData,
146-
cbtAppDataSize,
147-
targetName,
148-
(uint)inFlags,
149-
buffer,
150-
(buffer == null) ? 0 : buffer.Length,
151-
ref token,
152-
out outFlags,
153-
out isNtlmUsed);
129+
130+
if (channelBinding != null)
131+
{
132+
// If a TLS channel binding token (cbt) is available then get the pointer
133+
// to the application specific data.
134+
int appDataOffset = Marshal.SizeOf<SecChannelBindings>();
135+
Debug.Assert(appDataOffset < channelBinding.Size);
136+
IntPtr cbtAppData = channelBinding.DangerousGetHandle() + appDataOffset;
137+
int cbtAppDataSize = channelBinding.Size - appDataOffset;
138+
status = Interop.NetSecurityNative.InitSecContext(out minorStatus,
139+
credential,
140+
ref context,
141+
isNtlm,
142+
cbtAppData,
143+
cbtAppDataSize,
144+
targetName,
145+
(uint)inFlags,
146+
buffer,
147+
(buffer == null) ? 0 : buffer.Length,
148+
ref token,
149+
out outFlags,
150+
out isNtlmUsed);
151+
}
152+
else
153+
{
154+
status = Interop.NetSecurityNative.InitSecContext(out minorStatus,
155+
credential,
156+
ref context,
157+
isNtlm,
158+
targetName,
159+
(uint)inFlags,
160+
buffer,
161+
(buffer == null) ? 0 : buffer.Length,
162+
ref token,
163+
out outFlags,
164+
out isNtlmUsed);
165+
}
154166

155167
if ((status != Interop.NetSecurityNative.Status.GSS_S_COMPLETE) &&
156168
(status != Interop.NetSecurityNative.Status.GSS_S_CONTINUE_NEEDED))

src/libraries/Common/src/System/Net/Security/Unix/SafeDeleteNegoContext.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,7 @@ public SafeDeleteNegoContext(SafeFreeNegoCredentials credential, string targetNa
4343
{
4444
try
4545
{
46-
// Convert any "SERVICE/HOST" style of targetName to use "SERVICE@HOST" style.
47-
// This is because the System.Net.Security.Native GSS-API layer uses
48-
// GSS_C_NT_HOSTBASED_SERVICE format for targetName.
49-
_targetName = SafeGssNameHandle.CreateTarget(targetName.Replace('/', '@'));
46+
_targetName = SafeGssNameHandle.CreateTarget(targetName);
5047
}
5148
catch
5249
{

src/libraries/Native/Unix/System.Net.Security.Native/pal_gssapi.c

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,33 +144,88 @@ NetSecurityNative_ImportUserName(uint32_t* minorStatus, char* inputName, uint32_
144144
return gss_import_name(minorStatus, &inputNameBuffer, GSS_C_NT_USER_NAME, outputName);
145145
}
146146

147-
uint32_t NetSecurityNative_ImportTargetName(uint32_t* minorStatus,
148-
char* inputName,
149-
uint32_t inputNameLen,
150-
GssName** outputName)
147+
uint32_t NetSecurityNative_ImportPrincipalName(uint32_t* minorStatus,
148+
char* inputName,
149+
uint32_t inputNameLen,
150+
GssName** outputName)
151151
{
152152
assert(minorStatus != NULL);
153153
assert(inputName != NULL);
154154
assert(outputName != NULL);
155155
assert(*outputName == NULL);
156156

157+
// Principal name will usually be in the form SERVICE/HOST. But SPNEGO protocol prefers
158+
// GSS_C_NT_HOSTBASED_SERVICE format. That format uses '@' separator instead of '/' between
159+
// service name and host name. So convert input string into that format.
160+
char* ptrSlash = memchr(inputName, '/', inputNameLen);
161+
char* inputNameCopy = NULL;
162+
if (ptrSlash != NULL)
163+
{
164+
inputNameCopy = (char*) malloc(inputNameLen);
165+
if (inputNameCopy != NULL)
166+
{
167+
memcpy(inputNameCopy, inputName, inputNameLen);
168+
inputNameCopy[ptrSlash - inputName] = '@';
169+
inputName = inputNameCopy;
170+
}
171+
else
172+
{
173+
*minorStatus = 0;
174+
return GSS_S_BAD_NAME;
175+
}
176+
}
177+
157178
GssBuffer inputNameBuffer = {.length = inputNameLen, .value = inputName};
158-
return gss_import_name(minorStatus, &inputNameBuffer, GSS_C_NT_HOSTBASED_SERVICE, outputName);
179+
uint32_t result = gss_import_name(minorStatus, &inputNameBuffer, GSS_C_NT_HOSTBASED_SERVICE, outputName);
180+
181+
if (inputNameCopy != NULL)
182+
{
183+
free(inputNameCopy);
184+
}
185+
186+
return result;
159187
}
160188

161189
uint32_t NetSecurityNative_InitSecContext(uint32_t* minorStatus,
162190
GssCredId* claimantCredHandle,
163191
GssCtxId** contextHandle,
164192
uint32_t isNtlm,
165-
void* cbt,
166-
int32_t cbtSize,
167193
GssName* targetName,
168194
uint32_t reqFlags,
169195
uint8_t* inputBytes,
170196
uint32_t inputLength,
171197
PAL_GssBuffer* outBuffer,
172198
uint32_t* retFlags,
173199
int32_t* isNtlmUsed)
200+
{
201+
return NetSecurityNative_InitSecContextEx(minorStatus,
202+
claimantCredHandle,
203+
contextHandle,
204+
isNtlm,
205+
NULL,
206+
0,
207+
targetName,
208+
reqFlags,
209+
inputBytes,
210+
inputLength,
211+
outBuffer,
212+
retFlags,
213+
isNtlmUsed);
214+
}
215+
216+
uint32_t NetSecurityNative_InitSecContextEx(uint32_t* minorStatus,
217+
GssCredId* claimantCredHandle,
218+
GssCtxId** contextHandle,
219+
uint32_t isNtlm,
220+
void* cbt,
221+
int32_t cbtSize,
222+
GssName* targetName,
223+
uint32_t reqFlags,
224+
uint8_t* inputBytes,
225+
uint32_t inputLength,
226+
PAL_GssBuffer* outBuffer,
227+
uint32_t* retFlags,
228+
int32_t* isNtlmUsed)
174229
{
175230
assert(minorStatus != NULL);
176231
assert(contextHandle != NULL);

src/libraries/Native/Unix/System.Net.Security.Native/pal_gssapi.h

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ NetSecurityNative_ImportUserName(uint32_t* minorStatus, char* inputName, uint32_
7979
/*
8080
Shims the gss_import_name method with nametype = GSS_C_NT_USER_NAME.
8181
*/
82-
DLLEXPORT uint32_t NetSecurityNative_ImportTargetName(uint32_t* minorStatus,
82+
DLLEXPORT uint32_t NetSecurityNative_ImportPrincipalName(uint32_t* minorStatus,
8383
char* inputName,
8484
uint32_t inputNameLen,
8585
GssName** outputName);
@@ -107,8 +107,6 @@ DLLEXPORT uint32_t NetSecurityNative_InitSecContext(uint32_t* minorStatus,
107107
GssCredId* claimantCredHandle,
108108
GssCtxId** contextHandle,
109109
uint32_t isNtlm,
110-
void* cbt,
111-
int32_t cbtSize,
112110
GssName* targetName,
113111
uint32_t reqFlags,
114112
uint8_t* inputBytes,
@@ -117,6 +115,20 @@ DLLEXPORT uint32_t NetSecurityNative_InitSecContext(uint32_t* minorStatus,
117115
uint32_t* retFlags,
118116
int32_t* isNtlmUsed);
119117

118+
DLLEXPORT uint32_t NetSecurityNative_InitSecContextEx(uint32_t* minorStatus,
119+
GssCredId* claimantCredHandle,
120+
GssCtxId** contextHandle,
121+
uint32_t isNtlm,
122+
void* cbt,
123+
int32_t cbtSize,
124+
GssName* targetName,
125+
uint32_t reqFlags,
126+
uint8_t* inputBytes,
127+
uint32_t inputLength,
128+
PAL_GssBuffer* outBuffer,
129+
uint32_t* retFlags,
130+
int32_t* isNtlmUsed);
131+
120132
/*
121133
Shims the gss_accept_sec_context method.
122134
*/

src/libraries/System.Data.SqlClient/src/System.Data.SqlClient.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,6 @@
211211
<Compile Include="System\Data\SqlClient\SNI\SNINpHandle.cs" />
212212
<Compile Include="System\Data\SqlClient\SNI\SNIPacket.cs" />
213213
<Compile Include="System\Data\SqlClient\SNI\SNIProxy.cs" />
214-
<Compile Include="System\Data\SqlClient\SNI\SNIProxy.Windows.cs" Condition="'$(TargetsNetCoreApp)' != 'true' OR '$(TargetsUnix)' != 'true'" />
215-
<Compile Include="System\Data\SqlClient\SNI\SNIProxy.Unix.cs" Condition="'$(TargetsNetCoreApp)' == 'true' AND '$(TargetsUnix)' == 'true'" />
216214
<Compile Include="System\Data\SqlClient\SNI\SNITcpHandle.cs" />
217215
<Compile Include="System\Data\SqlClient\SNI\SslOverTdsStream.cs" />
218216
<Compile Include="System\Data\SqlClient\SNI\SNICommon.cs" />

src/libraries/System.Data.SqlClient/src/System/Data/SqlClient/SNI/SNIProxy.Unix.cs

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/libraries/System.Data.SqlClient/src/System/Data/SqlClient/SNI/SNIProxy.Windows.cs

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/libraries/System.Data.SqlClient/src/System/Data/SqlClient/SNI/SNIProxy.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ internal partial class SNIProxy
2222
{
2323
private const int DefaultSqlServerPort = 1433;
2424
private const int DefaultSqlServerDacPort = 1434;
25-
private const string SqlServerSpnHeader = "MSSQLSvc";
25+
private const string SqlServerSpnHeader = "MSSQLSvc/";
2626

2727
public static readonly SNIProxy Singleton = new SNIProxy();
2828

@@ -341,7 +341,7 @@ private static byte[] GetSqlServerSPN(string hostNameOrAddress, string portOrIns
341341
// If the DNS lookup failed, then resort to using the user provided hostname to construct the SPN.
342342
fullyQualifiedDomainName = hostEntry?.HostName ?? hostNameOrAddress;
343343
}
344-
string serverSpn = SqlServerSpnHeader + SpnServiceHostSeparator + fullyQualifiedDomainName;
344+
string serverSpn = SqlServerSpnHeader + fullyQualifiedDomainName;
345345
if (!string.IsNullOrWhiteSpace(portOrInstanceName))
346346
{
347347
serverSpn += ":" + portOrInstanceName;

0 commit comments

Comments
 (0)