From edd48bf2400828ee207467d7ac7935b74a552c0d Mon Sep 17 00:00:00 2001 From: David Shulman Date: Mon, 12 Nov 2018 14:47:16 -0800 Subject: [PATCH] Fix SPN used for Negotiate authentication (#33426) SocketsHttpHandler was not normalizing the DNS name prior to using it for the SPN (Service Principal Name). So, when using URI's that involve a CNAME, it was using the CNAME directly and not evaluating it to the normalized FQDN A record of the host. This change fixes the behavior to match .NET Framework so that CNAMEs are resolved properly. We can use the standard Dns.GetHostEntryAsync() API to resolve the name. From a performance perspective, this additional DNS API call is limited to just the SPN calculation for NT Auth. Calling this API doesn't impact the performance on the wire since the OS will cache DNS calls. Wireshark confirms that no additional DNS protocol packets will be sent. .NET Framework actually caches the normalized DNS resolution on the ServicePoint object when it opens up a connections. Thus, it doesn't have to call Dns.GetHostEntryAsync() for the SPN calculation. While a future PR could further optimize SocketsHttpHandler to also cache this DNS host name, it isn't clear it would result in measurable performance gain. I tested this change in a separate Enterprise testing environment I set up. I created a CNAME for a Windows IIS server in a Windows domain-joined environment and demonstrated that the Negotiate protocol results in a Kerberos authentication (and doesn't fall back to NTLM). Fixes #32328 --- .../src/System.Net.Http.csproj | 3 +++ .../AuthenticationHelper.NtAuth.cs | 24 ++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/System.Net.Http/src/System.Net.Http.csproj b/src/System.Net.Http/src/System.Net.Http.csproj index a349ce848d45..dd933eb290d8 100644 --- a/src/System.Net.Http/src/System.Net.Http.csproj +++ b/src/System.Net.Http/src/System.Net.Http.csproj @@ -599,6 +599,9 @@ + + + diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs index 1f262968e03e..3d3eaed48b8c 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.NtAuth.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Net; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; @@ -77,7 +78,28 @@ private static async Task SendWithNtAuthAsync(HttpRequestMe string challengeData = challenge.ChallengeData; - string spn = "HTTP/" + authUri.IdnHost; + // Need to use FQDN normalized host so that CNAME's are traversed. + // Use DNS to do the forward lookup to an A (host) record. + // But skip DNS lookup on IP literals. Otherwise, we would end up + // doing an unintended reverse DNS lookup. + string spn; + UriHostNameType hnt = authUri.HostNameType; + if (hnt == UriHostNameType.IPv6 || hnt == UriHostNameType.IPv4) + { + spn = authUri.IdnHost; + } + else + { + IPHostEntry result = await Dns.GetHostEntryAsync(authUri.IdnHost).ConfigureAwait(false); + spn = result.HostName; + } + spn = "HTTP/" + spn; + + if (NetEventSource.IsEnabled) + { + NetEventSource.Info(connection, $"Authentication: {challenge.AuthenticationType}, Host: {authUri.IdnHost}, SPN: {spn}"); + } + ChannelBinding channelBinding = connection.TransportContext?.GetChannelBinding(ChannelBindingKind.Endpoint); NTAuthentication authContext = new NTAuthentication(isServer:false, challenge.SchemeName, challenge.Credential, spn, ContextFlagsPal.Connection, channelBinding); try