diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props
index fa3ee5688b9e..9df79e4053be 100644
--- a/eng/ProjectReferences.props
+++ b/eng/ProjectReferences.props
@@ -57,6 +57,7 @@
+
diff --git a/src/Middleware/HttpOverrides/ref/Microsoft.AspNetCore.HttpOverrides.netcoreapp3.0.cs b/src/Middleware/HttpOverrides/ref/Microsoft.AspNetCore.HttpOverrides.netcoreapp3.0.cs
index cb7ee21e145a..f21794c54bdc 100644
--- a/src/Middleware/HttpOverrides/ref/Microsoft.AspNetCore.HttpOverrides.netcoreapp3.0.cs
+++ b/src/Middleware/HttpOverrides/ref/Microsoft.AspNetCore.HttpOverrides.netcoreapp3.0.cs
@@ -3,6 +3,10 @@
namespace Microsoft.AspNetCore.Builder
{
+ public static partial class CertificateForwardingBuilderExtensions
+ {
+ public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseCertificateForwarding(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) { throw null; }
+ }
public static partial class ForwardedHeadersExtensions
{
public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseForwardedHeaders(this Microsoft.AspNetCore.Builder.IApplicationBuilder builder) { throw null; }
@@ -37,6 +41,17 @@ public HttpMethodOverrideOptions() { }
}
namespace Microsoft.AspNetCore.HttpOverrides
{
+ public partial class CertificateForwardingMiddleware
+ {
+ public CertificateForwardingMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.Extensions.Options.IOptions options) { }
+ public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext httpContext) { throw null; }
+ }
+ public partial class CertificateForwardingOptions
+ {
+ public System.Func HeaderConverter;
+ public CertificateForwardingOptions() { }
+ public string CertificateHeader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
+ }
[System.FlagsAttribute]
public enum ForwardedHeaders
{
@@ -75,3 +90,10 @@ public IPNetwork(System.Net.IPAddress prefix, int prefixLength) { }
public bool Contains(System.Net.IPAddress address) { throw null; }
}
}
+namespace Microsoft.Extensions.DependencyInjection
+{
+ public static partial class CertificateForwardingServiceExtensions
+ {
+ public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddCertificateForwarding(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action configure) { throw null; }
+ }
+}
diff --git a/src/Middleware/HttpOverrides/src/CertificateForwardingBuilderExtensions.cs b/src/Middleware/HttpOverrides/src/CertificateForwardingBuilderExtensions.cs
new file mode 100644
index 000000000000..038b19b63704
--- /dev/null
+++ b/src/Middleware/HttpOverrides/src/CertificateForwardingBuilderExtensions.cs
@@ -0,0 +1,30 @@
+// 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.
+
+using System;
+using Microsoft.AspNetCore.HttpOverrides;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ ///
+ /// Extension methods for using certificate fowarding.
+ ///
+ public static class CertificateForwardingBuilderExtensions
+ {
+ ///
+ /// Adds a middleware to the pipeline that will look for a certificate in a request header
+ /// decode it, and updates HttpContext.Connection.ClientCertificate.
+ ///
+ ///
+ ///
+ public static IApplicationBuilder UseCertificateForwarding(this IApplicationBuilder app)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ return app.UseMiddleware();
+ }
+ }
+}
diff --git a/src/Middleware/HttpOverrides/src/CertificateForwardingFeature.cs b/src/Middleware/HttpOverrides/src/CertificateForwardingFeature.cs
new file mode 100644
index 000000000000..a7d284a0cc7a
--- /dev/null
+++ b/src/Middleware/HttpOverrides/src/CertificateForwardingFeature.cs
@@ -0,0 +1,51 @@
+// 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.
+
+using System;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.HttpOverrides
+{
+ internal class CertificateForwardingFeature : ITlsConnectionFeature
+ {
+ private ILogger _logger;
+ private StringValues _header;
+ private CertificateForwardingOptions _options;
+ private X509Certificate2 _certificate;
+
+ public CertificateForwardingFeature(ILogger logger, StringValues header, CertificateForwardingOptions options)
+ {
+ _logger = logger;
+ _options = options;
+ _header = header;
+ }
+
+ public X509Certificate2 ClientCertificate
+ {
+ get
+ {
+ if (_certificate == null)
+ {
+ try
+ {
+ _certificate = _options.HeaderConverter(_header);
+ }
+ catch (Exception e)
+ {
+ _logger.NoCertificate(e);
+ }
+ }
+ return _certificate;
+ }
+ set => _certificate = value;
+ }
+
+ public Task GetClientCertificateAsync(CancellationToken cancellationToken)
+ => Task.FromResult(ClientCertificate);
+ }
+}
diff --git a/src/Middleware/HttpOverrides/src/CertificateForwardingMiddleware.cs b/src/Middleware/HttpOverrides/src/CertificateForwardingMiddleware.cs
new file mode 100644
index 000000000000..77ff2823612d
--- /dev/null
+++ b/src/Middleware/HttpOverrides/src/CertificateForwardingMiddleware.cs
@@ -0,0 +1,66 @@
+// 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.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.HttpOverrides
+{
+ ///
+ /// Middleware that converts a forward header into a client certificate if found.
+ ///
+ public class CertificateForwardingMiddleware
+ {
+ private readonly RequestDelegate _next;
+ private readonly CertificateForwardingOptions _options;
+ private readonly ILogger _logger;
+
+ ///
+ /// Constructor.
+ ///
+ ///
+ ///
+ ///
+ public CertificateForwardingMiddleware(
+ RequestDelegate next,
+ ILoggerFactory loggerFactory,
+ IOptions options)
+ {
+ _next = next ?? throw new ArgumentNullException(nameof(next));
+
+ if (loggerFactory == null)
+ {
+ throw new ArgumentNullException(nameof(loggerFactory));
+ }
+
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ _options = options.Value;
+ _logger = loggerFactory.CreateLogger();
+ }
+
+ ///
+ /// Looks for the presence of a header in the request,
+ /// if found, converts this header to a ClientCertificate set on the connection.
+ ///
+ /// The .
+ /// A .
+ public Task Invoke(HttpContext httpContext)
+ {
+ var header = httpContext.Request.Headers[_options.CertificateHeader];
+ if (!StringValues.IsNullOrEmpty(header))
+ {
+ httpContext.Features.Set(new CertificateForwardingFeature(_logger, header, _options));
+ }
+ return _next(httpContext);
+ }
+ }
+}
diff --git a/src/Middleware/HttpOverrides/src/CertificateForwardingOptions.cs b/src/Middleware/HttpOverrides/src/CertificateForwardingOptions.cs
new file mode 100644
index 000000000000..4dccdda3b154
--- /dev/null
+++ b/src/Middleware/HttpOverrides/src/CertificateForwardingOptions.cs
@@ -0,0 +1,30 @@
+// 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.
+
+using System;
+using System.Security.Cryptography.X509Certificates;
+
+namespace Microsoft.AspNetCore.HttpOverrides
+{
+ ///
+ /// Used to configure the .
+ ///
+ public class CertificateForwardingOptions
+ {
+ ///
+ /// The name of the header containing the client certificate.
+ ///
+ ///
+ /// This defaults to X-Client-Cert
+ ///
+ public string CertificateHeader { get; set; } = "X-Client-Cert";
+
+ ///
+ /// The function used to convert the header to an instance of .
+ ///
+ ///
+ /// This defaults to a conversion from a base64 encoded string.
+ ///
+ public Func HeaderConverter = (headerValue) => new X509Certificate2(Convert.FromBase64String(headerValue));
+ }
+}
diff --git a/src/Middleware/HttpOverrides/src/CertificateForwardingServiceExtensions.cs b/src/Middleware/HttpOverrides/src/CertificateForwardingServiceExtensions.cs
new file mode 100644
index 000000000000..ffdd4e403bc8
--- /dev/null
+++ b/src/Middleware/HttpOverrides/src/CertificateForwardingServiceExtensions.cs
@@ -0,0 +1,38 @@
+// 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.
+
+using System;
+using Microsoft.AspNetCore.HttpOverrides;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ ///
+ /// Extension methods for using certificate fowarding.
+ ///
+ public static class CertificateForwardingServiceExtensions
+ {
+ ///
+ /// Adds certificate forwarding to the specified .
+ ///
+ /// The .
+ /// An action delegate to configure the provided .
+ /// The so that additional calls can be chained.
+ public static IServiceCollection AddCertificateForwarding(
+ this IServiceCollection services,
+ Action configure)
+ {
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ if (configure == null)
+ {
+ throw new ArgumentNullException(nameof(configure));
+ }
+
+ services.AddOptions().Validate(o => !string.IsNullOrEmpty(o.CertificateHeader), "CertificateForwarderOptions.CertificateHeader cannot be null or empty.");
+ return services.Configure(configure);
+ }
+ }
+}
diff --git a/src/Middleware/HttpOverrides/src/LoggingExtensions.cs b/src/Middleware/HttpOverrides/src/LoggingExtensions.cs
new file mode 100644
index 000000000000..27a2dce49408
--- /dev/null
+++ b/src/Middleware/HttpOverrides/src/LoggingExtensions.cs
@@ -0,0 +1,25 @@
+// 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.
+
+using System;
+
+namespace Microsoft.Extensions.Logging
+{
+ internal static class LoggingExtensions
+ {
+ private static Action _noCertificate;
+
+ static LoggingExtensions()
+ {
+ _noCertificate = LoggerMessage.Define(
+ eventId: new EventId(0, "NoCertificate"),
+ logLevel: LogLevel.Warning,
+ formatString: "Could not read certificate from header.");
+ }
+
+ public static void NoCertificate(this ILogger logger, Exception exception)
+ {
+ _noCertificate(logger, exception);
+ }
+ }
+}
diff --git a/src/Middleware/HttpOverrides/test/CertificateForwardingTest.cs b/src/Middleware/HttpOverrides/test/CertificateForwardingTest.cs
new file mode 100644
index 000000000000..42464ccb9895
--- /dev/null
+++ b/src/Middleware/HttpOverrides/test/CertificateForwardingTest.cs
@@ -0,0 +1,222 @@
+// 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.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Microsoft.Net.Http.Headers;
+using Xunit;
+
+namespace Microsoft.AspNetCore.HttpOverrides
+{
+ public class CertificateForwardingTests
+ {
+ [Fact]
+ public void VerifySettingNullHeaderOptionThrows()
+ {
+ var services = new ServiceCollection()
+ .AddOptions()
+ .AddCertificateForwarding(o => o.CertificateHeader = null);
+ var options = services.BuildServiceProvider().GetRequiredService>();
+ Assert.Throws(() => options.Value);
+ }
+
+ [Fact]
+ public void VerifySettingEmptyHeaderOptionThrows()
+ {
+ var services = new ServiceCollection()
+ .AddOptions()
+ .AddCertificateForwarding(o => o.CertificateHeader = "");
+ var options = services.BuildServiceProvider().GetRequiredService>();
+ Assert.Throws(() => options.Value);
+ }
+
+ [Fact]
+ public async Task VerifyHeaderIsUsedIfNoCertificateAlreadySet()
+ {
+ var builder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddCertificateForwarding(options => { });
+ })
+ .Configure(app =>
+ {
+ app.Use(async (context, next) =>
+ {
+ Assert.Null(context.Connection.ClientCertificate);
+ await next();
+ });
+ app.UseCertificateForwarding();
+ app.Use(async (context, next) =>
+ {
+ Assert.Equal(context.Connection.ClientCertificate, Certificates.SelfSignedValidWithNoEku);
+ await next();
+ });
+ });
+ var server = new TestServer(builder);
+
+ var context = await server.SendAsync(c =>
+ {
+ c.Request.Headers["X-Client-Cert"] = Convert.ToBase64String(Certificates.SelfSignedValidWithNoEku.RawData);
+ });
+ }
+
+ [Fact]
+ public async Task VerifyHeaderOverridesCertificateEvenAlreadySet()
+ {
+ var builder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddCertificateForwarding(options => { });
+ })
+ .Configure(app =>
+ {
+ app.Use(async (context, next) =>
+ {
+ Assert.Null(context.Connection.ClientCertificate);
+ context.Connection.ClientCertificate = Certificates.SelfSignedNotYetValid;
+ await next();
+ });
+ app.UseCertificateForwarding();
+ app.Use(async (context, next) =>
+ {
+ Assert.Equal(context.Connection.ClientCertificate, Certificates.SelfSignedValidWithNoEku);
+ await next();
+ });
+ });
+ var server = new TestServer(builder);
+
+ var context = await server.SendAsync(c =>
+ {
+ c.Request.Headers["X-Client-Cert"] = Convert.ToBase64String(Certificates.SelfSignedValidWithNoEku.RawData);
+ });
+ }
+
+ [Fact]
+ public async Task VerifySettingTheAzureHeaderOnTheForwarderOptionsWorks()
+ {
+ var builder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddCertificateForwarding(options => options.CertificateHeader = "X-ARR-ClientCert");
+ })
+ .Configure(app =>
+ {
+ app.Use(async (context, next) =>
+ {
+ Assert.Null(context.Connection.ClientCertificate);
+ await next();
+ });
+ app.UseCertificateForwarding();
+ app.Use(async (context, next) =>
+ {
+ Assert.Equal(context.Connection.ClientCertificate, Certificates.SelfSignedValidWithNoEku);
+ await next();
+ });
+ });
+ var server = new TestServer(builder);
+
+ var context = await server.SendAsync(c =>
+ {
+ c.Request.Headers["X-ARR-ClientCert"] = Convert.ToBase64String(Certificates.SelfSignedValidWithNoEku.RawData);
+ });
+ }
+
+ [Fact]
+ public async Task VerifyACustomHeaderFailsIfTheHeaderIsNotPresent()
+ {
+ var builder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddCertificateForwarding(options => options.CertificateHeader = "some-random-header");
+ })
+ .Configure(app =>
+ {
+ app.Use(async (context, next) =>
+ {
+ Assert.Null(context.Connection.ClientCertificate);
+ await next();
+ });
+ app.UseCertificateForwarding();
+ app.Use(async (context, next) =>
+ {
+ Assert.Null(context.Connection.ClientCertificate);
+ await next();
+ });
+ });
+ var server = new TestServer(builder);
+
+ var context = await server.SendAsync(c =>
+ {
+ c.Request.Headers["not-the-right-header"] = Convert.ToBase64String(Certificates.SelfSignedValidWithNoEku.RawData);
+ });
+ }
+
+ [Fact]
+ public async Task VerifyArrHeaderEncodedCertFailsOnBadEncoding()
+ {
+ var builder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddCertificateForwarding(options => { });
+ })
+ .Configure(app =>
+ {
+ app.Use(async (context, next) =>
+ {
+ Assert.Null(context.Connection.ClientCertificate);
+ await next();
+ });
+ app.UseCertificateForwarding();
+ app.Use(async (context, next) =>
+ {
+ Assert.Null(context.Connection.ClientCertificate);
+ await next();
+ });
+ });
+ var server = new TestServer(builder);
+
+ var context = await server.SendAsync(c =>
+ {
+ c.Request.Headers["X-Client-Cert"] = "OOPS" + Convert.ToBase64String(Certificates.SelfSignedValidWithNoEku.RawData);
+ });
+ }
+
+ private static class Certificates
+ {
+ public static X509Certificate2 SelfSignedValidWithClientEku { get; private set; } =
+ new X509Certificate2(GetFullyQualifiedFilePath("validSelfSignedClientEkuCertificate.cer"));
+
+ public static X509Certificate2 SelfSignedValidWithNoEku { get; private set; } =
+ new X509Certificate2(GetFullyQualifiedFilePath("validSelfSignedNoEkuCertificate.cer"));
+
+ public static X509Certificate2 SelfSignedValidWithServerEku { get; private set; } =
+ new X509Certificate2(GetFullyQualifiedFilePath("validSelfSignedServerEkuCertificate.cer"));
+
+ public static X509Certificate2 SelfSignedNotYetValid { get; private set; } =
+ new X509Certificate2(GetFullyQualifiedFilePath("selfSignedNoEkuCertificateNotValidYet.cer"));
+
+ public static X509Certificate2 SelfSignedExpired { get; private set; } =
+ new X509Certificate2(GetFullyQualifiedFilePath("selfSignedNoEkuCertificateExpired.cer"));
+
+ private static string GetFullyQualifiedFilePath(string filename)
+ {
+ var filePath = Path.Combine(AppContext.BaseDirectory, filename);
+ if (!File.Exists(filePath))
+ {
+ throw new FileNotFoundException(filePath);
+ }
+ return filePath;
+ }
+ }
+
+ }
+}
diff --git a/src/Middleware/HttpOverrides/test/Microsoft.AspNetCore.HttpOverrides.Tests.csproj b/src/Middleware/HttpOverrides/test/Microsoft.AspNetCore.HttpOverrides.Tests.csproj
index da3cde94cb41..c5f9652ddca2 100644
--- a/src/Middleware/HttpOverrides/test/Microsoft.AspNetCore.HttpOverrides.Tests.csproj
+++ b/src/Middleware/HttpOverrides/test/Microsoft.AspNetCore.HttpOverrides.Tests.csproj
@@ -1,4 +1,4 @@
-
+
netcoreapp3.0
@@ -8,6 +8,7 @@
+
diff --git a/src/Security/Authentication/Certificate/ref/Microsoft.AspNetCore.Authentication.Certificate.csproj b/src/Security/Authentication/Certificate/ref/Microsoft.AspNetCore.Authentication.Certificate.csproj
new file mode 100644
index 000000000000..3cf6d5107924
--- /dev/null
+++ b/src/Security/Authentication/Certificate/ref/Microsoft.AspNetCore.Authentication.Certificate.csproj
@@ -0,0 +1,10 @@
+
+
+
+ netcoreapp3.0
+
+
+
+
+
+
diff --git a/src/Security/Authentication/Certificate/ref/Microsoft.AspNetCore.Authentication.Certificate.netcoreapp3.0.cs b/src/Security/Authentication/Certificate/ref/Microsoft.AspNetCore.Authentication.Certificate.netcoreapp3.0.cs
new file mode 100644
index 000000000000..7fa9e147ab26
--- /dev/null
+++ b/src/Security/Authentication/Certificate/ref/Microsoft.AspNetCore.Authentication.Certificate.netcoreapp3.0.cs
@@ -0,0 +1,59 @@
+// 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.Authentication.Certificate
+{
+ public static partial class CertificateAuthenticationDefaults
+ {
+ public const string AuthenticationScheme = "Certificate";
+ }
+ public partial class CertificateAuthenticationEvents
+ {
+ public CertificateAuthenticationEvents() { }
+ public System.Func OnAuthenticationFailed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
+ public System.Func OnCertificateValidated { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
+ public virtual System.Threading.Tasks.Task AuthenticationFailed(Microsoft.AspNetCore.Authentication.Certificate.CertificateAuthenticationFailedContext context) { throw null; }
+ public virtual System.Threading.Tasks.Task CertificateValidated(Microsoft.AspNetCore.Authentication.Certificate.CertificateValidatedContext context) { throw null; }
+ }
+ public partial class CertificateAuthenticationFailedContext : Microsoft.AspNetCore.Authentication.ResultContext
+ {
+ public CertificateAuthenticationFailedContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, Microsoft.AspNetCore.Authentication.Certificate.CertificateAuthenticationOptions options) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(Microsoft.AspNetCore.Authentication.Certificate.CertificateAuthenticationOptions)) { }
+ public System.Exception Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
+ }
+ public partial class CertificateAuthenticationOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions
+ {
+ public CertificateAuthenticationOptions() { }
+ public Microsoft.AspNetCore.Authentication.Certificate.CertificateTypes AllowedCertificateTypes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
+ public new Microsoft.AspNetCore.Authentication.Certificate.CertificateAuthenticationEvents Events { get { throw null; } set { } }
+ public System.Security.Cryptography.X509Certificates.X509RevocationFlag RevocationFlag { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
+ public System.Security.Cryptography.X509Certificates.X509RevocationMode RevocationMode { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
+ public bool ValidateCertificateUse { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
+ public bool ValidateValidityPeriod { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
+ }
+ [System.FlagsAttribute]
+ public enum CertificateTypes
+ {
+ Chained = 1,
+ SelfSigned = 2,
+ All = 3,
+ }
+ public partial class CertificateValidatedContext : Microsoft.AspNetCore.Authentication.ResultContext
+ {
+ public CertificateValidatedContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, Microsoft.AspNetCore.Authentication.Certificate.CertificateAuthenticationOptions options) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(Microsoft.AspNetCore.Authentication.Certificate.CertificateAuthenticationOptions)) { }
+ public System.Security.Cryptography.X509Certificates.X509Certificate2 ClientCertificate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
+ }
+ public static partial class X509Certificate2Extensions
+ {
+ public static bool IsSelfSigned(this System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { throw null; }
+ }
+}
+namespace Microsoft.Extensions.DependencyInjection
+{
+ public static partial class CertificateAuthenticationAppBuilderExtensions
+ {
+ public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddCertificate(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder) { throw null; }
+ public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddCertificate(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, System.Action configureOptions) { throw null; }
+ public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddCertificate(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, string authenticationScheme) { throw null; }
+ public static Microsoft.AspNetCore.Authentication.AuthenticationBuilder AddCertificate(this Microsoft.AspNetCore.Authentication.AuthenticationBuilder builder, string authenticationScheme, System.Action configureOptions) { throw null; }
+ }
+}
diff --git a/src/Security/Authentication/Certificate/samples/Certificate.Sample/Certificate.Sample.csproj b/src/Security/Authentication/Certificate/samples/Certificate.Sample/Certificate.Sample.csproj
new file mode 100644
index 000000000000..2f085e5c416f
--- /dev/null
+++ b/src/Security/Authentication/Certificate/samples/Certificate.Sample/Certificate.Sample.csproj
@@ -0,0 +1,21 @@
+
+
+
+ netcoreapp3.0
+ OutOfProcess
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Security/Authentication/Certificate/samples/Certificate.Sample/Controllers/HomeController.cs b/src/Security/Authentication/Certificate/samples/Certificate.Sample/Controllers/HomeController.cs
new file mode 100644
index 000000000000..60be48074b67
--- /dev/null
+++ b/src/Security/Authentication/Certificate/samples/Certificate.Sample/Controllers/HomeController.cs
@@ -0,0 +1,12 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace Certificate.Sample.Controllers
+{
+ public class HomeController : Controller
+ {
+ public IActionResult Index()
+ {
+ return View();
+ }
+ }
+}
diff --git a/src/Security/Authentication/Certificate/samples/Certificate.Sample/Program.cs b/src/Security/Authentication/Certificate/samples/Certificate.Sample/Program.cs
new file mode 100644
index 000000000000..1c4a2d295808
--- /dev/null
+++ b/src/Security/Authentication/Certificate/samples/Certificate.Sample/Program.cs
@@ -0,0 +1,26 @@
+using Microsoft.AspNetCore;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Server.Kestrel.Https;
+
+namespace Certificate.Sample
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ BuildWebHost(args).Run();
+ }
+
+ public static IWebHost BuildWebHost(string[] args)
+ => WebHost.CreateDefaultBuilder(args)
+ .UseStartup()
+ .ConfigureKestrel(options =>
+ {
+ options.ConfigureHttpsDefaults(opt =>
+ {
+ opt.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
+ });
+ })
+ .Build();
+ }
+}
diff --git a/src/Security/Authentication/Certificate/samples/Certificate.Sample/Properties/launchSettings.json b/src/Security/Authentication/Certificate/samples/Certificate.Sample/Properties/launchSettings.json
new file mode 100644
index 000000000000..e796cb6c7ecf
--- /dev/null
+++ b/src/Security/Authentication/Certificate/samples/Certificate.Sample/Properties/launchSettings.json
@@ -0,0 +1,20 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "https://localhost:44331/",
+ "sslPort": 44331
+ }
+ },
+ "profiles": {
+ "Certificate.Sample": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "https://localhost:5001/"
+ }
+ }
+}
diff --git a/src/Security/Authentication/Certificate/samples/Certificate.Sample/Startup.cs b/src/Security/Authentication/Certificate/samples/Certificate.Sample/Startup.cs
new file mode 100644
index 000000000000..14e2702e072c
--- /dev/null
+++ b/src/Security/Authentication/Certificate/samples/Certificate.Sample/Startup.cs
@@ -0,0 +1,61 @@
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication.Certificate;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc.Authorization;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace Certificate.Sample
+{
+ public class Startup
+ {
+ // This method gets called by the runtime. Use this method to add services to the container.
+ // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
+ .AddCertificate(options =>
+ {
+ options.Events = new CertificateAuthenticationEvents
+ {
+ OnCertificateValidated = context =>
+ {
+ var claims = new[]
+ {
+ new Claim(ClaimTypes.NameIdentifier, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer),
+ new Claim(ClaimTypes.Name, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer)
+ };
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name));
+ context.Success();
+
+ return Task.CompletedTask;
+ }
+ };
+ });
+
+ services.AddAuthorization();
+
+ services.AddMvc(config => { });
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+ {
+ app.UseRouting();
+
+ app.UseStatusCodePages();
+
+ app.UseAuthentication();
+ app.UseAuthorization();
+
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapDefaultControllerRoute();
+ });
+ }
+ }
+}
diff --git a/src/Security/Authentication/Certificate/samples/Certificate.Sample/Views/Home/Index.cshtml b/src/Security/Authentication/Certificate/samples/Certificate.Sample/Views/Home/Index.cshtml
new file mode 100644
index 000000000000..5247bfe9c6d4
--- /dev/null
+++ b/src/Security/Authentication/Certificate/samples/Certificate.Sample/Views/Home/Index.cshtml
@@ -0,0 +1 @@
+Hello @User.Identity.Name
\ No newline at end of file
diff --git a/src/Security/Authentication/Certificate/src/CertificateAuthenticationDefaults.cs b/src/Security/Authentication/Certificate/src/CertificateAuthenticationDefaults.cs
new file mode 100644
index 000000000000..d085dd3b70ae
--- /dev/null
+++ b/src/Security/Authentication/Certificate/src/CertificateAuthenticationDefaults.cs
@@ -0,0 +1,16 @@
+// 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.Authentication.Certificate
+{
+ ///
+ /// Default values related to certificate authentication middleware
+ ///
+ public static class CertificateAuthenticationDefaults
+ {
+ ///
+ /// The default value used for CertificateAuthenticationOptions.AuthenticationScheme
+ ///
+ public const string AuthenticationScheme = "Certificate";
+ }
+}
diff --git a/src/Security/Authentication/Certificate/src/CertificateAuthenticationExtensions.cs b/src/Security/Authentication/Certificate/src/CertificateAuthenticationExtensions.cs
new file mode 100644
index 000000000000..d49f2c274ba3
--- /dev/null
+++ b/src/Security/Authentication/Certificate/src/CertificateAuthenticationExtensions.cs
@@ -0,0 +1,55 @@
+// 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.
+
+using System;
+using Microsoft.AspNetCore.Authentication;
+
+using Microsoft.AspNetCore.Authentication.Certificate;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+ ///
+ /// Extension methods to add Certificate authentication capabilities to an HTTP application pipeline.
+ ///
+ public static class CertificateAuthenticationAppBuilderExtensions
+ {
+ ///
+ /// Adds certificate authentication.
+ ///
+ /// The .
+ /// The .
+ public static AuthenticationBuilder AddCertificate(this AuthenticationBuilder builder)
+ => builder.AddCertificate(CertificateAuthenticationDefaults.AuthenticationScheme);
+
+ ///
+ /// Adds certificate authentication.
+ ///
+ /// The .
+ ///
+ /// The .
+ public static AuthenticationBuilder AddCertificate(this AuthenticationBuilder builder, string authenticationScheme)
+ => builder.AddCertificate(authenticationScheme, configureOptions: null);
+
+ ///
+ /// Adds certificate authentication.
+ ///
+ /// The .
+ ///
+ /// The .
+ public static AuthenticationBuilder AddCertificate(this AuthenticationBuilder builder, Action configureOptions)
+ => builder.AddCertificate(CertificateAuthenticationDefaults.AuthenticationScheme, configureOptions);
+
+ ///
+ /// Adds certificate authentication.
+ ///
+ /// The .
+ ///
+ ///
+ /// The .
+ public static AuthenticationBuilder AddCertificate(
+ this AuthenticationBuilder builder,
+ string authenticationScheme,
+ Action configureOptions)
+ => builder.AddScheme(authenticationScheme, configureOptions);
+ }
+}
diff --git a/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs b/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs
new file mode 100644
index 000000000000..68a7abdde0a2
--- /dev/null
+++ b/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs
@@ -0,0 +1,235 @@
+// 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.
+
+using System;
+using System.Collections.Generic;
+using System.Security.Claims;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Authentication.Certificate
+{
+ internal class CertificateAuthenticationHandler : AuthenticationHandler
+ {
+ private static readonly Oid ClientCertificateOid = new Oid("1.3.6.1.5.5.7.3.2");
+
+ public CertificateAuthenticationHandler(
+ IOptionsMonitor options,
+ ILoggerFactory logger,
+ UrlEncoder encoder,
+ ISystemClock clock) : base(options, logger, encoder, clock)
+ {
+ }
+
+ ///
+ /// The handler calls methods on the events which give the application control at certain points where processing is occurring.
+ /// If it is not provided a default instance is supplied which does nothing when the methods are called.
+ ///
+ protected new CertificateAuthenticationEvents Events
+ {
+ get { return (CertificateAuthenticationEvents)base.Events; }
+ set { base.Events = value; }
+ }
+
+ ///
+ /// Creates a new instance of the events instance.
+ ///
+ /// A new instance of the events instance.
+ protected override Task