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)