diff --git a/src/Microsoft.AspNet.Server.WebListener/FeatureContext.cs b/src/Microsoft.AspNet.Server.WebListener/FeatureContext.cs
index 4d42a0db5e..18a0c9c9c9 100644
--- a/src/Microsoft.AspNet.Server.WebListener/FeatureContext.cs
+++ b/src/Microsoft.AspNet.Server.WebListener/FeatureContext.cs
@@ -38,6 +38,7 @@ namespace Microsoft.AspNet.Server.WebListener
IHttpResponseFeature,
IHttpSendFileFeature,
ITlsConnectionFeature,
+ ITlsTokenBindingFeature,
IHttpRequestLifetimeFeature,
IHttpWebSocketFeature,
IHttpAuthenticationFeature,
@@ -103,6 +104,7 @@ namespace Microsoft.AspNet.Server.WebListener
if (Request.IsSecureConnection)
{
_features.Add(typeof(ITlsConnectionFeature), this);
+ _features.Add(typeof(ITlsTokenBindingFeature), this);
}
// Win8+
@@ -316,6 +318,16 @@ namespace Microsoft.AspNet.Server.WebListener
return _clientCert;
}
+ byte[] ITlsTokenBindingFeature.GetProvidedTokenBindingId()
+ {
+ return Request.GetProvidedTokenBindingId();
+ }
+
+ byte[] ITlsTokenBindingFeature.GetReferredTokenBindingId()
+ {
+ return Request.GetReferredTokenBindingId();
+ }
+
Stream IHttpResponseFeature.Body
{
get
diff --git a/src/Microsoft.Net.Http.Server/AuthenticationManager.cs b/src/Microsoft.Net.Http.Server/AuthenticationManager.cs
index d0a43e5dd1..ea67513993 100644
--- a/src/Microsoft.Net.Http.Server/AuthenticationManager.cs
+++ b/src/Microsoft.Net.Http.Server/AuthenticationManager.cs
@@ -167,14 +167,18 @@ namespace Microsoft.Net.Http.Server
return false;
}
- internal static unsafe ClaimsPrincipal GetUser(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_INFO* requestInfo)
+ internal static unsafe ClaimsPrincipal GetUser(UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_INFO* requestInfo, int infoCount)
{
- if (requestInfo != null
- && requestInfo->InfoType == UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_INFO_TYPE.HttpRequestInfoTypeAuth
- && requestInfo->pInfo->AuthStatus == UnsafeNclNativeMethods.HttpApi.HTTP_AUTH_STATUS.HttpAuthStatusSuccess)
+ for (int i = 0; i < infoCount; i++)
{
- return new WindowsPrincipal(new WindowsIdentity(requestInfo->pInfo->AccessToken,
- GetAuthTypeFromRequest(requestInfo->pInfo->AuthType).ToString()));
+ var info = &requestInfo[i];
+ if (requestInfo != null
+ && info->InfoType == UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_INFO_TYPE.HttpRequestInfoTypeAuth
+ && info->pInfo->AuthStatus == UnsafeNclNativeMethods.HttpApi.HTTP_AUTH_STATUS.HttpAuthStatusSuccess)
+ {
+ return new WindowsPrincipal(new WindowsIdentity(info->pInfo->AccessToken,
+ GetAuthTypeFromRequest(info->pInfo->AuthType).ToString()));
+ }
}
return new ClaimsPrincipal(new ClaimsIdentity()); // Anonymous / !IsAuthenticated
}
diff --git a/src/Microsoft.Net.Http.Server/NativeInterop/HeapAllocHandle.cs b/src/Microsoft.Net.Http.Server/NativeInterop/HeapAllocHandle.cs
new file mode 100644
index 0000000000..e7d24271d9
--- /dev/null
+++ b/src/Microsoft.Net.Http.Server/NativeInterop/HeapAllocHandle.cs
@@ -0,0 +1,39 @@
+// Copyright (c) .NET Foundation.
+// All Rights Reserved
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR
+// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
+// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF
+// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR
+// NON-INFRINGEMENT.
+// See the Apache 2 License for the specific language governing
+// permissions and limitations under the License.
+
+using System;
+using Microsoft.Win32.SafeHandles;
+
+namespace Microsoft.Net.Http.Server
+{
+ internal sealed class HeapAllocHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ private static readonly IntPtr ProcessHeap = UnsafeNclNativeMethods.GetProcessHeap();
+
+ // Called by P/Invoke when returning SafeHandles
+ private HeapAllocHandle()
+ : base(ownsHandle: true)
+ {
+ }
+
+ // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
+ protected override bool ReleaseHandle()
+ {
+ return UnsafeNclNativeMethods.HeapFree(ProcessHeap, 0, handle);
+ }
+ }
+}
diff --git a/src/Microsoft.Net.Http.Server/NativeInterop/TokenBindingUtil.cs b/src/Microsoft.Net.Http.Server/NativeInterop/TokenBindingUtil.cs
new file mode 100644
index 0000000000..08aaeb2973
--- /dev/null
+++ b/src/Microsoft.Net.Http.Server/NativeInterop/TokenBindingUtil.cs
@@ -0,0 +1,94 @@
+// Copyright (c) .NET Foundation.
+// All Rights Reserved
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR
+// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
+// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF
+// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR
+// NON-INFRINGEMENT.
+// See the Apache 2 License for the specific language governing
+// permissions and limitations under the License.
+
+using System;
+using System.Runtime.InteropServices;
+using static Microsoft.Net.Http.Server.UnsafeNclNativeMethods.HttpApi;
+using static Microsoft.Net.Http.Server.UnsafeNclNativeMethods.TokenBinding;
+
+namespace Microsoft.Net.Http.Server
+{
+ ///
+ /// Contains helpers for dealing with TLS token binding.
+ ///
+ internal unsafe static class TokenBindingUtil
+ {
+ private static byte[] ExtractIdentifierBlob(TOKENBINDING_RESULT_DATA* pTokenBindingResultData)
+ {
+ // Per http://tools.ietf.org/html/draft-ietf-tokbind-protocol-00, Sec. 4,
+ // the identifer is a tuple which contains (token binding type, hash algorithm
+ // signature algorithm, key data). We'll strip off the token binding type and
+ // return the remainder (starting with the hash algorithm) as an opaque blob.
+ byte[] retVal = new byte[checked(pTokenBindingResultData->identifierSize - 1)];
+ Marshal.Copy((IntPtr)(&pTokenBindingResultData->identifierData->hashAlgorithm), retVal, 0, retVal.Length);
+ return retVal;
+ }
+
+ ///
+ /// Returns the 'provided' token binding identifier, optionally also returning the
+ /// 'referred' token binding identifier. Returns null on failure.
+ ///
+ public static byte[] GetProvidedTokenIdFromBindingInfo(HTTP_REQUEST_TOKEN_BINDING_INFO* pTokenBindingInfo, out byte[] referredId)
+ {
+ byte[] providedId = null;
+ referredId = null;
+
+ HeapAllocHandle handle = null;
+ int status = UnsafeNclNativeMethods.TokenBindingVerifyMessage(
+ pTokenBindingInfo->TokenBinding,
+ pTokenBindingInfo->TokenBindingSize,
+ pTokenBindingInfo->KeyType,
+ pTokenBindingInfo->TlsUnique,
+ pTokenBindingInfo->TlsUniqueSize,
+ out handle);
+
+ // No match found or there was an error?
+ if (status != 0 || handle == null || handle.IsInvalid)
+ {
+ return null;
+ }
+
+ using (handle)
+ {
+ // Find the first 'provided' and 'referred' types.
+ TOKENBINDING_RESULT_LIST* pResultList = (TOKENBINDING_RESULT_LIST*)handle.DangerousGetHandle();
+ for (int i = 0; i < pResultList->resultCount; i++)
+ {
+ TOKENBINDING_RESULT_DATA* pThisResultData = &pResultList->resultData[i];
+ if (pThisResultData->identifierData->bindingType == TOKENBINDING_TYPE.TOKENBINDING_TYPE_PROVIDED)
+ {
+ if (providedId != null)
+ {
+ return null; // It is invalid to have more than one 'provided' identifier.
+ }
+ providedId = ExtractIdentifierBlob(pThisResultData);
+ }
+ else if (pThisResultData->identifierData->bindingType == TOKENBINDING_TYPE.TOKENBINDING_TYPE_REFERRED)
+ {
+ if (referredId != null)
+ {
+ return null; // It is invalid to have more than one 'referred' identifier.
+ }
+ referredId = ExtractIdentifierBlob(pThisResultData);
+ }
+ }
+ }
+
+ return providedId;
+ }
+ }
+}
diff --git a/src/Microsoft.Net.Http.Server/NativeInterop/UnsafeNativeMethods.cs b/src/Microsoft.Net.Http.Server/NativeInterop/UnsafeNativeMethods.cs
index 054375cee9..7e48142209 100644
--- a/src/Microsoft.Net.Http.Server/NativeInterop/UnsafeNativeMethods.cs
+++ b/src/Microsoft.Net.Http.Server/NativeInterop/UnsafeNativeMethods.cs
@@ -28,7 +28,7 @@ using System.Runtime.InteropServices;
namespace Microsoft.Net.Http.Server
{
- internal static class UnsafeNclNativeMethods
+ internal static unsafe class UnsafeNclNativeMethods
{
private const string HTTPAPI = "httpapi.dll";
@@ -38,12 +38,15 @@ namespace Microsoft.Net.Http.Server
private const string api_ms_win_core_io_LIB = "api-ms-win-core-io-l1-1-1.dll";
private const string api_ms_win_core_handle_LIB = "api-ms-win-core-handle-l1-1-0.dll";
private const string api_ms_win_core_libraryloader_LIB = "api-ms-win-core-libraryloader-l1-1-0.dll";
+ private const string api_ms_win_core_heap_LIB = "api-ms-win-core-heap-L1-2-0.dll";
private const string api_ms_win_core_heap_obsolete_LIB = "api-ms-win-core-heap-obsolete-L1-1-0.dll";
private const string api_ms_win_core_kernel32_legacy_LIB = "api-ms-win-core-kernel32-legacy-l1-1-0.dll";
#else
private const string KERNEL32 = "kernel32.dll";
private const string SECUR32 = "secur32.dll";
#endif
+ private const string TOKENBINDING = "tokenbinding.dll";
+
// CONSIDER: Make this an enum, requires changing a lot of types from uint to ErrorCodes.
internal static class ErrorCodes
{
@@ -88,6 +91,34 @@ namespace Microsoft.Net.Http.Server
SkipSetEventOnHandle = 2
}
+ [DllImport(TOKENBINDING, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
+ public static extern int TokenBindingVerifyMessage(
+ [In] byte* tokenBindingMessage,
+ [In] uint tokenBindingMessageSize,
+ [In] char* keyType,
+ [In] byte* tlsUnique,
+ [In] uint tlsUniqueSize,
+ [Out] out HeapAllocHandle resultList);
+
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa366569(v=vs.85).aspx
+#if DNXCORE50
+ [DllImport(api_ms_win_core_heap_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+#else
+ [DllImport(KERNEL32, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+#endif
+ internal static extern IntPtr GetProcessHeap();
+
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa366701(v=vs.85).aspx
+#if DNXCORE50
+ [DllImport(api_ms_win_core_heap_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+#else
+ [DllImport(KERNEL32, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+#endif
+ internal static extern bool HeapFree(
+ [In] IntPtr hHeap,
+ [In] uint dwFlags,
+ [In] IntPtr lpMem);
+
internal static class SafeNetHandles
{
#if DNXCORE50
@@ -248,6 +279,9 @@ namespace Microsoft.Net.Http.Server
internal enum HTTP_REQUEST_INFO_TYPE
{
HttpRequestInfoTypeAuth,
+ HttpRequestInfoTypeChannelBind,
+ HttpRequestInfoTypeSslProtocol,
+ HttpRequestInfoTypeSslTokenBinding
}
internal enum HTTP_RESPONSE_INFO_TYPE
@@ -707,6 +741,18 @@ namespace Microsoft.Net.Http.Server
char* Realm;
}
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct HTTP_REQUEST_TOKEN_BINDING_INFO
+ {
+ public byte* TokenBinding;
+ public uint TokenBindingSize;
+
+ public byte* TlsUnique;
+ public uint TlsUniqueSize;
+
+ public char* KeyType;
+ }
+
[StructLayout(LayoutKind.Sequential)]
internal struct HTTP_TIMEOUT_LIMIT_INFO
{
@@ -1206,6 +1252,60 @@ namespace Microsoft.Net.Http.Server
}
}
+ // from tokenbinding.h
+ internal static class TokenBinding
+ {
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct TOKENBINDING_RESULT_DATA
+ {
+ public uint identifierSize;
+ public TOKENBINDING_IDENTIFIER* identifierData;
+ public TOKENBINDING_EXTENSION_FORMAT extensionFormat;
+ public uint extensionSize;
+ public IntPtr extensionData;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct TOKENBINDING_IDENTIFIER
+ {
+ // Note: If the layout of these fields changes, be sure to make the
+ // corresponding change to TokenBindingUtil.ExtractIdentifierBlob.
+
+ public TOKENBINDING_TYPE bindingType;
+ public TOKENBINDING_HASH_ALGORITHM hashAlgorithm;
+ public TOKENBINDING_SIGNATURE_ALGORITHM signatureAlgorithm;
+ }
+
+ internal enum TOKENBINDING_TYPE : byte
+ {
+ TOKENBINDING_TYPE_PROVIDED = 0,
+ TOKENBINDING_TYPE_REFERRED = 1,
+ }
+
+ internal enum TOKENBINDING_HASH_ALGORITHM : byte
+ {
+ TOKENBINDING_HASH_ALGORITHM_SHA256 = 4,
+ }
+
+ internal enum TOKENBINDING_SIGNATURE_ALGORITHM : byte
+ {
+ TOKENBINDING_SIGNATURE_ALGORITHM_RSA = 1,
+ TOKENBINDING_SIGNATURE_ALGORITHM_ECDSAP256 = 3,
+ }
+
+ internal enum TOKENBINDING_EXTENSION_FORMAT
+ {
+ TOKENBINDING_EXTENSION_FORMAT_UNDEFINED = 0,
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct TOKENBINDING_RESULT_LIST
+ {
+ public uint resultCount;
+ public TOKENBINDING_RESULT_DATA* resultData;
+ }
+ }
+
// DACL related stuff
[SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Instantiated natively")]
diff --git a/src/Microsoft.Net.Http.Server/RequestProcessing/Request.cs b/src/Microsoft.Net.Http.Server/RequestProcessing/Request.cs
index 668cc6dfea..51333996fc 100644
--- a/src/Microsoft.Net.Http.Server/RequestProcessing/Request.cs
+++ b/src/Microsoft.Net.Http.Server/RequestProcessing/Request.cs
@@ -60,6 +60,8 @@ namespace Microsoft.Net.Http.Server
private string _path;
private X509Certificate2 _clientCert;
+ private byte[] _providedTokenBindingId;
+ private byte[] _referredTokenBindingId;
private HeaderCollection _headers;
private BoundaryType _contentBoundaryType;
@@ -142,8 +144,10 @@ namespace Microsoft.Net.Http.Server
_httpMethod = UnsafeNclNativeMethods.HttpApi.GetVerb(RequestBuffer, OriginalBlobAddress);
_headers = new HeaderCollection(new RequestHeaders(_nativeRequestContext));
- UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_V2* requestV2 = (UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_V2*)memoryBlob.RequestBlob;
- _user = AuthenticationManager.GetUser(requestV2->pRequestInfo);
+ var requestV2 = (UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_V2*)memoryBlob.RequestBlob;
+ _user = AuthenticationManager.GetUser(requestV2->pRequestInfo, requestV2->RequestInfoCount);
+
+ GetTlsTokenBindingInfo();
// TODO: Verbose log parameters
@@ -470,6 +474,38 @@ namespace Microsoft.Net.Http.Server
return _clientCert;
}
+ public byte[] GetProvidedTokenBindingId()
+ {
+ return _providedTokenBindingId;
+ }
+
+ public byte[] GetReferredTokenBindingId()
+ {
+ return _referredTokenBindingId;
+ }
+
+ // Only call from the constructor so we can directly access the native request blob.
+ // This requires Windows 10 and the following reg key:
+ // Set Key: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\HTTP\Parameters to Value: EnableSslTokenBinding = 1 [DWORD]
+ // Then for IE to work you need to set these:
+ // Key: HKLM\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ENABLE_TOKEN_BINDING
+ // Value: "iexplore.exe"=dword:0x00000001
+ // Key: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_ENABLE_TOKEN_BINDING
+ // Value: "iexplore.exe"=dword:00000001
+ private unsafe void GetTlsTokenBindingInfo()
+ {
+ var nativeRequest = (UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_V2*)_nativeRequestContext.RequestBlob;
+ for (int i = 0; i < nativeRequest->RequestInfoCount; i++)
+ {
+ var pThisInfo = &nativeRequest->pRequestInfo[i];
+ if (pThisInfo->InfoType == UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_INFO_TYPE.HttpRequestInfoTypeSslTokenBinding)
+ {
+ var pTokenBindingInfo = (UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_TOKEN_BINDING_INFO*)pThisInfo->pInfo;
+ _providedTokenBindingId = TokenBindingUtil.GetProvidedTokenIdFromBindingInfo(pTokenBindingInfo, out _referredTokenBindingId);
+ }
+ }
+ }
+
// Use this to save the blob from dispose if this object was never used (never given to a user) and is about to be
// disposed.
internal void DetachBlob(NativeRequestContext memoryBlob)