Skip to content

Commit 9e20382

Browse files
committed
Throw PlatformNotSupportedException on Unix/Managed NegotiateAuthenticationPal implementation for NTLM w/ default credentials.
This was handled inconsistently between the managed NTLM implementation and the GSSAPI one. Add test for the behavior. Handle PlatformNotSupportedException in SocketsHttpHandler and Managed SPNEGO implementation. Add test to ensure SocketsHttpHandler using CredentialCache.DefaultCredentials with NTLM doesn't throw PNSE exception and returns the Unauthorized HTTP response instead.
1 parent 2dfee0f commit 9e20382

File tree

7 files changed

+91
-57
lines changed

7 files changed

+91
-57
lines changed

src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,12 @@ private static async Task<HttpResponseMessage> SendWithNtAuthAsync(HttpRequestMe
217217
needDrain = true;
218218
}
219219
}
220+
catch (PlatformNotSupportedException)
221+
{
222+
// Ignore PNSE from NegotiateAuthentication with unsupported parameters
223+
// and treat it the same way as if we didn't support the authentication
224+
// in the first place.
225+
}
220226
finally
221227
{
222228
if (isNewConnection)

src/libraries/System.Net.Http/tests/FunctionalTests/NtAuthTests.FakeServer.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,5 +139,33 @@ await server.AcceptConnectionAsync(async connection =>
139139
}).ConfigureAwait(false);
140140
});
141141
}
142+
143+
[Fact]
144+
[SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.Windows, "DefaultCredentials are unsupported for NTLM on Unix / Managed implementation")]
145+
public async Task DefaultHandler_FakeServer_DefaultCredentials()
146+
{
147+
await LoopbackServer.CreateClientAndServerAsync(
148+
async uri =>
149+
{
150+
HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, uri);
151+
requestMessage.Version = new Version(1, 1);
152+
153+
HttpMessageHandler handler = new HttpClientHandler() { Credentials = CredentialCache.DefaultCredentials };
154+
using (var client = new HttpClient(handler))
155+
{
156+
HttpResponseMessage response = await client.SendAsync(requestMessage);
157+
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
158+
}
159+
},
160+
async server =>
161+
{
162+
await server.AcceptConnectionAsync(async connection =>
163+
{
164+
var authHeader = "WWW-Authenticate: NTLM\r\n";
165+
await connection.SendResponseAsync(HttpStatusCode.Unauthorized, authHeader).ConfigureAwait(false);
166+
connection.CompleteRequestProcessing();
167+
}).ConfigureAwait(false);
168+
});
169+
}
142170
}
143171
}

src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedNtlm.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,13 +222,16 @@ public ManagedNtlmNegotiateAuthenticationPal(NegotiateAuthenticationClientOption
222222
{
223223
Debug.Assert(clientOptions.Package == NegotiationInfoClass.NTLM);
224224

225-
_credential = clientOptions.Credential;
226-
if (string.IsNullOrWhiteSpace(_credential.UserName) || string.IsNullOrWhiteSpace(_credential.Password))
225+
if (clientOptions.Credential == CredentialCache.DefaultNetworkCredentials ||
226+
string.IsNullOrWhiteSpace(clientOptions.Credential.UserName) ||
227+
string.IsNullOrWhiteSpace(clientOptions.Credential.Password))
227228
{
228229
// NTLM authentication is not possible with default credentials which are no-op
230+
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, SR.net_ntlm_not_possible_default_cred);
229231
throw new PlatformNotSupportedException(SR.net_ntlm_not_possible_default_cred);
230232
}
231233

234+
_credential = clientOptions.Credential;
232235
_spn = clientOptions.TargetName;
233236
_channelBinding = clientOptions.Binding;
234237
_protectionLevel = clientOptions.RequiredProtectionLevel;

src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.ManagedSpnego.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public override void Dispose()
115115

116116
private NegotiateAuthenticationPal CreateMechanismForPackage(string packageName)
117117
{
118-
return NegotiateAuthenticationPal.Create(new NegotiateAuthenticationClientOptions
118+
var updatedClientOptions = new NegotiateAuthenticationClientOptions
119119
{
120120
Package = packageName,
121121
Credential = _clientOptions.Credential,
@@ -124,7 +124,16 @@ private NegotiateAuthenticationPal CreateMechanismForPackage(string packageName)
124124
RequiredProtectionLevel = _clientOptions.RequiredProtectionLevel,
125125
RequireMutualAuthentication = _clientOptions.RequireMutualAuthentication,
126126
AllowedImpersonationLevel = _clientOptions.AllowedImpersonationLevel,
127-
});
127+
};
128+
129+
try
130+
{
131+
return NegotiateAuthenticationPal.Create(updatedClientOptions);
132+
}
133+
catch (PlatformNotSupportedException)
134+
{
135+
return new UnsupportedNegotiateAuthenticationPal(updatedClientOptions);
136+
}
128137
}
129138

130139
private IEnumerable<KeyValuePair<string, string>> EnumerateMechanisms()
@@ -303,16 +312,7 @@ private IEnumerable<KeyValuePair<string, string>> EnumerateMechanisms()
303312
{
304313
// Abandon the optimistic path and restart with a new mechanism
305314
_optimisticMechanism?.Dispose();
306-
_mechanism = NegotiateAuthenticationPal.Create(new NegotiateAuthenticationClientOptions
307-
{
308-
Package = requestedPackage,
309-
Credential = _clientOptions.Credential,
310-
TargetName = _clientOptions.TargetName,
311-
Binding = _clientOptions.Binding,
312-
RequiredProtectionLevel = _clientOptions.RequiredProtectionLevel,
313-
RequireMutualAuthentication = _clientOptions.RequireMutualAuthentication,
314-
AllowedImpersonationLevel = _clientOptions.AllowedImpersonationLevel,
315-
});
315+
_mechanism = CreateMechanismForPackage(requestedPackage);
316316
}
317317

318318
_optimisticMechanism = null;

src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.Unix.cs

Lines changed: 26 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,15 @@ public static NegotiateAuthenticationPal Create(NegotiateAuthenticationClientOpt
4242
{
4343
return new UnixNegotiateAuthenticationPal(clientOptions);
4444
}
45-
catch (Win32Exception)
45+
catch (Interop.NetSecurityNative.GssApiException gex)
4646
{
47-
return new UnsupportedNegotiateAuthenticationPal(clientOptions);
48-
}
49-
catch (PlatformNotSupportedException)
50-
{
51-
return new UnsupportedNegotiateAuthenticationPal(clientOptions);
47+
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, gex);
48+
NegotiateAuthenticationStatusCode statusCode = UnixNegotiateAuthenticationPal.GetErrorCode(gex);
49+
if (statusCode <= NegotiateAuthenticationStatusCode.GenericFailure)
50+
{
51+
statusCode = NegotiateAuthenticationStatusCode.Unsupported;
52+
}
53+
return new UnsupportedNegotiateAuthenticationPal(clientOptions, statusCode);
5254
}
5355
catch (EntryPointNotFoundException)
5456
{
@@ -63,13 +65,15 @@ public static NegotiateAuthenticationPal Create(NegotiateAuthenticationServerOpt
6365
{
6466
return new UnixNegotiateAuthenticationPal(serverOptions);
6567
}
66-
catch (Win32Exception)
68+
catch (Interop.NetSecurityNative.GssApiException gex)
6769
{
68-
return new UnsupportedNegotiateAuthenticationPal(serverOptions);
69-
}
70-
catch (PlatformNotSupportedException)
71-
{
72-
return new UnsupportedNegotiateAuthenticationPal(serverOptions);
70+
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, gex);
71+
NegotiateAuthenticationStatusCode statusCode = UnixNegotiateAuthenticationPal.GetErrorCode(gex);
72+
if (statusCode <= NegotiateAuthenticationStatusCode.GenericFailure)
73+
{
74+
statusCode = NegotiateAuthenticationStatusCode.Unsupported;
75+
}
76+
return new UnsupportedNegotiateAuthenticationPal(serverOptions, statusCode);
7377
}
7478
catch (EntryPointNotFoundException)
7579
{
@@ -184,22 +188,25 @@ public UnixNegotiateAuthenticationPal(NegotiateAuthenticationClientOptions clien
184188

185189
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Peer SPN-> '{_spn}'");
186190

187-
if (clientOptions.Credential == CredentialCache.DefaultCredentials ||
191+
if (clientOptions.Credential == CredentialCache.DefaultNetworkCredentials ||
188192
string.IsNullOrWhiteSpace(clientOptions.Credential.UserName) ||
189193
string.IsNullOrWhiteSpace(clientOptions.Credential.Password))
190194
{
191195
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "using DefaultCredentials");
192-
_credentialsHandle = AcquireDefaultCredential();
193196

194197
if (_packageType == Interop.NetSecurityNative.PackageType.NTLM)
195198
{
196199
// NTLM authentication is not possible with default credentials which are no-op
200+
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, SR.net_ntlm_not_possible_default_cred);
197201
throw new PlatformNotSupportedException(SR.net_ntlm_not_possible_default_cred);
198202
}
199203
if (string.IsNullOrEmpty(_spn))
200204
{
205+
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, SR.net_nego_not_supported_empty_target_with_defaultcreds);
201206
throw new PlatformNotSupportedException(SR.net_nego_not_supported_empty_target_with_defaultcreds);
202207
}
208+
209+
_credentialsHandle = SafeGssCredHandle.Create(string.Empty, string.Empty, _packageType);
203210
}
204211
else
205212
{
@@ -229,7 +236,7 @@ public UnixNegotiateAuthenticationPal(NegotiateAuthenticationServerOptions serve
229236

230237
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Peer SPN-> '{_spn}'");
231238

232-
if (serverOptions.Credential == CredentialCache.DefaultCredentials ||
239+
if (serverOptions.Credential == CredentialCache.DefaultNetworkCredentials ||
233240
string.IsNullOrWhiteSpace(serverOptions.Credential.UserName) ||
234241
string.IsNullOrWhiteSpace(serverOptions.Credential.Password))
235242
{
@@ -462,24 +469,7 @@ private static Interop.NetSecurityNative.PackageType GetPackageType(string packa
462469
else
463470
{
464471
// Native shim currently supports only NTLM, Negotiate and Kerberos
465-
throw new PlatformNotSupportedException(SR.net_securitypackagesupport);
466-
}
467-
}
468-
469-
private SafeGssCredHandle AcquireDefaultCredential()
470-
{
471-
try
472-
{
473-
return SafeGssCredHandle.Create(string.Empty, string.Empty, _packageType);
474-
}
475-
catch (Exception ex)
476-
{
477-
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, ex);
478-
479-
// NOTE: We throw PlatformNotSupportedException which is caught in
480-
// NegotiateAuthenticationPal.Create and transformed into instantiation of
481-
// UnsupportedNegotiateAuthenticationPal.
482-
throw new PlatformNotSupportedException(ex.Message, ex);
472+
throw new Interop.NetSecurityNative.GssApiException(Interop.NetSecurityNative.Status.GSS_S_UNAVAILABLE, 0);
483473
}
484474
}
485475

@@ -511,14 +501,10 @@ private SafeGssCredHandle AcquireCredentialsHandle(NetworkCredential credential)
511501

512502
return SafeGssCredHandle.Create(username, password, _packageType);
513503
}
514-
catch (Exception ex)
504+
catch (Exception ex) when (ex is not Interop.NetSecurityNative.GssApiException)
515505
{
516506
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, ex);
517-
518-
// NOTE: We throw PlatformNotSupportedException which is caught in
519-
// NegotiateAuthenticationPal.Create and transformed into instantiation of
520-
// UnsupportedNegotiateAuthenticationPal.
521-
throw new PlatformNotSupportedException(ex.Message, ex);
507+
throw new Interop.NetSecurityNative.GssApiException(Interop.NetSecurityNative.Status.GSS_S_BAD_NAME, 0);
522508
}
523509
}
524510

@@ -753,7 +739,7 @@ private NegotiateAuthenticationStatusCode AcceptSecurityContext(
753739
}
754740

755741
// https://www.gnu.org/software/gss/reference/gss.pdf (page 25)
756-
private static NegotiateAuthenticationStatusCode GetErrorCode(Interop.NetSecurityNative.GssApiException exception)
742+
internal static NegotiateAuthenticationStatusCode GetErrorCode(Interop.NetSecurityNative.GssApiException exception)
757743
{
758744
switch (exception.MajorStatus)
759745
{

src/libraries/System.Net.Security/src/System/Net/NegotiateAuthenticationPal.Unsupported.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ internal sealed class UnsupportedNegotiateAuthenticationPal : NegotiateAuthentic
1515
{
1616
private string _package;
1717
private string? _targetName;
18+
private NegotiateAuthenticationStatusCode _statusCode;
1819

1920
public override bool IsAuthenticated => false;
2021
public override bool IsSigned => false;
@@ -25,15 +26,17 @@ internal sealed class UnsupportedNegotiateAuthenticationPal : NegotiateAuthentic
2526
public override IIdentity RemoteIdentity => throw new InvalidOperationException();
2627
public override System.Security.Principal.TokenImpersonationLevel ImpersonationLevel => System.Security.Principal.TokenImpersonationLevel.Impersonation;
2728

28-
public UnsupportedNegotiateAuthenticationPal(NegotiateAuthenticationClientOptions clientOptions)
29+
public UnsupportedNegotiateAuthenticationPal(NegotiateAuthenticationClientOptions clientOptions, NegotiateAuthenticationStatusCode statusCode = NegotiateAuthenticationStatusCode.Unsupported)
2930
{
3031
_package = clientOptions.Package;
3132
_targetName = clientOptions.TargetName;
33+
_statusCode = statusCode;
3234
}
3335

34-
public UnsupportedNegotiateAuthenticationPal(NegotiateAuthenticationServerOptions serverOptions)
36+
public UnsupportedNegotiateAuthenticationPal(NegotiateAuthenticationServerOptions serverOptions, NegotiateAuthenticationStatusCode statusCode = NegotiateAuthenticationStatusCode.Unsupported)
3537
{
3638
_package = serverOptions.Package;
39+
_statusCode = statusCode;
3740
}
3841

3942
public override void Dispose()
@@ -42,7 +45,7 @@ public override void Dispose()
4245

4346
public override byte[]? GetOutgoingBlob(ReadOnlySpan<byte> incomingBlob, out NegotiateAuthenticationStatusCode statusCode)
4447
{
45-
statusCode = NegotiateAuthenticationStatusCode.Unsupported;
48+
statusCode = _statusCode;
4649
return null;
4750
}
4851

src/libraries/System.Net.Security/tests/UnitTests/NegotiateAuthenticationTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,14 @@ public void Package_Unsupported_NTLM()
9494
Assert.Equal(NegotiateAuthenticationStatusCode.Unsupported, statusCode);
9595
}
9696

97+
[Fact]
98+
[SkipOnPlatform(TestPlatforms.Windows, "The test is specific to GSSAPI / Managed implementations of NegotiateAuthentication")]
99+
public void DefaultNetworkCredentials_NTLM_Throws()
100+
{
101+
NegotiateAuthenticationClientOptions clientOptions = new NegotiateAuthenticationClientOptions { Package = "NTLM", Credential = CredentialCache.DefaultNetworkCredentials, TargetName = "HTTP/foo" };
102+
Assert.Throws<PlatformNotSupportedException>(() => new NegotiateAuthentication(clientOptions));
103+
}
104+
97105
[Fact]
98106
public void NtlmProtocolExampleTest()
99107
{

0 commit comments

Comments
 (0)