Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Commit

Permalink
Port Kerberos auth fixes to 2.1 branch (#40109)
Browse files Browse the repository at this point in the history
This PR ports some important Kerberos auth fixes from the 3.0 to 2.1 LTS
branch. These fixes help enterprise customers that have complex Kerberos
authentication scenarios that involve cross Windows (Active Directory)
and Linux (Kerberos) domains/realms.

These fixes are from PRs:

* #38465 - Use 'Host' header when calculating SPN for Kerberos auth
* #38377 - Use GSS_C_NT_HOSTBASED_SERVICE format for Linux Kerberos SPN

and are related to issue #36329.
  • Loading branch information
davidsh authored and danmoseley committed Aug 8, 2019
1 parent a38b2f0 commit 7bb81c6
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 10 deletions.
35 changes: 26 additions & 9 deletions src/Native/Unix/System.Net.Security.Native/pal_gssapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ static uint32_t NetSecurityNative_DisplayStatus(uint32_t* minorStatus,
assert(minorStatus != nullptr);
assert(outBuffer != nullptr);

uint32_t messageContext;
uint32_t messageContext = 0; // Must initialize to 0 before calling gss_display_status.
GssBuffer gssBuffer{.length = 0, .value = nullptr};
uint32_t majorStatus =
gss_display_status(minorStatus, statusValue, statusType, GSS_C_NO_OID, &messageContext, &gssBuffer);
Expand Down Expand Up @@ -155,19 +155,36 @@ extern "C" uint32_t NetSecurityNative_ImportPrincipalName(uint32_t* minorStatus,
assert(outputName != nullptr);
assert(*outputName == nullptr);

gss_OID nameType;

if (strchr(inputName, '/') != nullptr)
// 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 = (char*) memchr(inputName, '/', inputNameLen);
char* inputNameCopy = NULL;
if (ptrSlash != NULL)
{
nameType = const_cast<gss_OID>(GSS_KRB5_NT_PRINCIPAL_NAME);
inputNameCopy = (char*) malloc(inputNameLen);
if (inputNameCopy != NULL)
{
memcpy(inputNameCopy, inputName, inputNameLen);
inputNameCopy[ptrSlash - inputName] = '@';
inputName = inputNameCopy;
}
else
{
*minorStatus = 0;
return GSS_S_BAD_NAME;
}
}
else

GssBuffer inputNameBuffer = {.length = inputNameLen, .value = inputName};
uint32_t result = gss_import_name(minorStatus, &inputNameBuffer, GSS_C_NT_HOSTBASED_SERVICE, outputName);

if (inputNameCopy != NULL)
{
nameType = const_cast<gss_OID>(GSS_C_NT_HOSTBASED_SERVICE);
free(inputNameCopy);
}

GssBuffer inputNameBuffer{.length = inputNameLen, .value = inputName};
return gss_import_name(minorStatus, &inputNameBuffer, nameType, outputName);
return result;
}

extern "C" uint32_t NetSecurityNative_InitSecContext(uint32_t* minorStatus,
Expand Down
3 changes: 3 additions & 0 deletions src/System.Net.Http/src/System.Net.Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,9 @@
<Compile Include="System\Net\Http\HttpClientHandler.Core.cs" />
<Compile Include="uap\System\Net\HttpClientHandler.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetGroup)' == 'netcoreapp'">
<Reference Include="System.Net.NameResolution" />
</ItemGroup>
<ItemGroup Condition="'$(TargetGroup)' == 'netcoreapp' OR '$(TargetGroup)' == 'uap'">
<Reference Include="Microsoft.Win32.Primitives" />
<Reference Include="System.Buffers" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Net;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -76,7 +77,44 @@ private static async Task<HttpResponseMessage> SendWithNtAuthAsync(HttpRequestMe

string challengeData = challenge.ChallengeData;

string spn = "HTTP/" + authUri.IdnHost;
// Calculate SPN (Service Principal Name) using the host name of the request.
// Use the request's 'Host' header if available. Otherwise, use the request uri.
// Ignore the 'Host' header if this is proxy authentication since we need to use
// the host name of the proxy itself for SPN calculation.
string hostName;
if (!isProxyAuth && request.HasHeaders && request.Headers.Host != null)
{
// Use the host name without any normalization.
hostName = request.Headers.Host;
if (NetEventSource.IsEnabled)
{
NetEventSource.Info(connection, $"Authentication: {challenge.AuthenticationType}, Host: {hostName}");
}
}
else
{
// 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.
UriHostNameType hnt = authUri.HostNameType;
if (hnt == UriHostNameType.IPv6 || hnt == UriHostNameType.IPv4)
{
hostName = authUri.IdnHost;
}
else
{
IPHostEntry result = await Dns.GetHostEntryAsync(authUri.IdnHost).ConfigureAwait(false);
hostName = result.HostName;
}
}

string spn = "HTTP/" + hostName;
if (NetEventSource.IsEnabled)
{
NetEventSource.Info(connection, $"Authentication: {challenge.AuthenticationType}, SPN: {spn}");
}

ChannelBinding channelBinding = connection.TransportContext?.GetChannelBinding(ChannelBindingKind.Endpoint);
NTAuthentication authContext = new NTAuthentication(isServer:false, challenge.SchemeName, challenge.Credential, spn, ContextFlagsPal.Connection, channelBinding);
try
Expand Down

0 comments on commit 7bb81c6

Please sign in to comment.