Allow opt out of HttpSys client cert negotiation #14806 (#14839)

This commit is contained in:
Chris Ross 2019-10-09 15:57:11 -07:00 committed by GitHub
parent d0de73684d
commit f3b0fbe207
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 123 additions and 1 deletions

View File

@ -26,6 +26,12 @@ namespace Microsoft.AspNetCore.Server.HttpSys
Negotiate = 8,
Kerberos = 16,
}
public enum ClientCertificateMethod
{
NoCertificate = 0,
AllowCertificate = 1,
AllowRenegotation = 2,
}
public enum Http503VerbosityLevel : long
{
Basic = (long)0,
@ -46,6 +52,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
public HttpSysOptions() { }
public bool AllowSynchronousIO { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public Microsoft.AspNetCore.Server.HttpSys.AuthenticationManager Authentication { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public Microsoft.AspNetCore.Server.HttpSys.ClientCertificateMethod ClientCertificateMethod { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public bool EnableResponseCaching { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public Microsoft.AspNetCore.Server.HttpSys.Http503VerbosityLevel Http503Verbosity { get { throw null; } set { } }
public int MaxAccepts { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }

View File

@ -0,0 +1,26 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Server.HttpSys
{
/// <summary>
/// Describes the client certificate negotiation method for HTTPS connections.
/// </summary>
public enum ClientCertificateMethod
{
/// <summary>
/// A client certificate will not be populated on the request.
/// </summary>
NoCertificate = 0,
/// <summary>
/// A client certificate will be populated if already present at the start of a request.
/// </summary>
AllowCertificate,
/// <summary>
/// The TLS session can be renegotiated to request a client certificate.
/// </summary>
AllowRenegotation
}
}

View File

@ -316,7 +316,17 @@ namespace Microsoft.AspNetCore.Server.HttpSys
{
if (IsNotInitialized(Fields.ClientCertificate))
{
_clientCert = Request.GetClientCertificateAsync().Result; // TODO: Sync;
var method = _requestContext.Server.Options.ClientCertificateMethod;
if (method == ClientCertificateMethod.AllowCertificate)
{
_clientCert = Request.ClientCertificate;
}
else if (method == ClientCertificateMethod.AllowRenegotation)
{
_clientCert = Request.GetClientCertificateAsync().Result; // TODO: Sync over async;
}
// else if (method == ClientCertificateMethod.NoCertificate) // No-op
SetInitialized(Fields.ClientCertificate);
}
return _clientCert;

View File

@ -54,6 +54,13 @@ namespace Microsoft.AspNetCore.Server.HttpSys
/// </summary>
public RequestQueueMode RequestQueueMode { get; set; }
/// <summary>
/// Indicates how client certificates should be populated. The default is to allow renegotation.
/// This does not change the netsh 'clientcertnegotiation' binding option which will need to be enabled for
/// ClientCertificateMethod.AllowCertificate to resolve a certificate.
/// </summary>
public ClientCertificateMethod ClientCertificateMethod { get; set; } = ClientCertificateMethod.AllowRenegotation;
/// <summary>
/// The maximum number of concurrent accepts.
/// </summary>

View File

@ -6,12 +6,15 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Security;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.HttpSys
{
@ -323,6 +326,30 @@ namespace Microsoft.AspNetCore.Server.HttpSys
KeyExchangeStrength = (int)handshake.KeyExchangeStrength;
}
public X509Certificate2 ClientCertificate
{
get
{
if (_clientCert == null && SslStatus == SslStatus.ClientCert)
{
try
{
_clientCert = _nativeRequestContext.GetClientCertificate();
}
catch (CryptographicException ce)
{
RequestContext.Logger.LogDebug(ce, "An error occurred reading the client certificate.");
}
catch (SecurityException se)
{
RequestContext.Logger.LogDebug(se, "An error occurred reading the client certificate.");
}
}
return _clientCert;
}
}
// Populates the client certificate. The result may be null if there is no client cert.
// TODO: Does it make sense for this to be invoked multiple times (e.g. renegotiate)? Client and server code appear to
// enable this, but it's unclear what Http.Sys would do.

View File

@ -8,6 +8,7 @@ using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using Microsoft.Extensions.Primitives;
@ -518,5 +519,49 @@ namespace Microsoft.AspNetCore.HttpSys.Internal
return new ReadOnlyDictionary<int, ReadOnlyMemory<byte>>(info);
}
internal X509Certificate2 GetClientCertificate()
{
if (_permanentlyPinned)
{
return GetClientCertificate((IntPtr)_nativeRequest, (HttpApiTypes.HTTP_REQUEST_V2*)_nativeRequest);
}
else
{
fixed (byte* pMemoryBlob = _backingBuffer)
{
var request = (HttpApiTypes.HTTP_REQUEST_V2*)(pMemoryBlob + _bufferAlignment);
return GetClientCertificate(_originalBufferAddress, request);
}
}
}
// Throws CryptographicException
private X509Certificate2 GetClientCertificate(IntPtr baseAddress, HttpApiTypes.HTTP_REQUEST_V2* nativeRequest)
{
var request = nativeRequest->Request;
long fixup = (byte*)nativeRequest - (byte*)baseAddress;
if (request.pSslInfo == null)
{
return null;
}
var sslInfo = (HttpApiTypes.HTTP_SSL_INFO*)((byte*)request.pSslInfo + fixup);
if (sslInfo->SslClientCertNegotiated == 0 || sslInfo->pClientCertInfo == null)
{
return null;
}
var clientCertInfo = (HttpApiTypes.HTTP_SSL_CLIENT_CERT_INFO*)((byte*)sslInfo->pClientCertInfo + fixup);
if (clientCertInfo->pCertEncoded == null)
{
return null;
}
var clientCert = clientCertInfo->pCertEncoded + fixup;
byte[] certEncoded = new byte[clientCertInfo->CertEncodedSize];
Marshal.Copy((IntPtr)clientCert, certEncoded, 0, certEncoded.Length);
return new X509Certificate2(certEncoded);
}
}
}