Skip to content

Commit b75b892

Browse files
authored
Add CertificateAuthentication (#9756)
1 parent 4dde8b9 commit b75b892

44 files changed

Lines changed: 2178 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

eng/ProjectReferences.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Abstractions\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.csproj" RefProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Abstractions\ref\Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.csproj" />
5858
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Libuv\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj" RefProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Libuv\ref\Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.csproj" />
5959
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" ProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Sockets\src\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj" RefProjectPath="$(RepoRoot)src\Servers\Kestrel\Transport.Sockets\ref\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj" />
60+
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Authentication.Certificate" ProjectPath="$(RepoRoot)src\Security\Authentication\Certificate\src\Microsoft.AspNetCore.Authentication.Certificate.csproj" RefProjectPath="$(RepoRoot)src\Security\Authentication\Certificate\ref\Microsoft.AspNetCore.Authentication.Certificate.csproj" />
6061
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Authentication.Cookies" ProjectPath="$(RepoRoot)src\Security\Authentication\Cookies\src\Microsoft.AspNetCore.Authentication.Cookies.csproj" RefProjectPath="$(RepoRoot)src\Security\Authentication\Cookies\ref\Microsoft.AspNetCore.Authentication.Cookies.csproj" />
6162
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Authentication" ProjectPath="$(RepoRoot)src\Security\Authentication\Core\src\Microsoft.AspNetCore.Authentication.csproj" RefProjectPath="$(RepoRoot)src\Security\Authentication\Core\ref\Microsoft.AspNetCore.Authentication.csproj" />
6263
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Authentication.Facebook" ProjectPath="$(RepoRoot)src\Security\Authentication\Facebook\src\Microsoft.AspNetCore.Authentication.Facebook.csproj" RefProjectPath="$(RepoRoot)src\Security\Authentication\Facebook\ref\Microsoft.AspNetCore.Authentication.Facebook.csproj" />

src/Middleware/HttpOverrides/ref/Microsoft.AspNetCore.HttpOverrides.netcoreapp3.0.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33

44
namespace Microsoft.AspNetCore.Builder
55
{
6+
public static partial class CertificateForwardingBuilderExtensions
7+
{
8+
public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseCertificateForwarding(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) { throw null; }
9+
}
610
public static partial class ForwardedHeadersExtensions
711
{
812
public static Microsoft.AspNetCore.Builder.IApplicationBuilder UseForwardedHeaders(this Microsoft.AspNetCore.Builder.IApplicationBuilder builder) { throw null; }
@@ -37,6 +41,17 @@ public HttpMethodOverrideOptions() { }
3741
}
3842
namespace Microsoft.AspNetCore.HttpOverrides
3943
{
44+
public partial class CertificateForwardingMiddleware
45+
{
46+
public CertificateForwardingMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.HttpOverrides.CertificateForwardingOptions> options) { }
47+
public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext httpContext) { throw null; }
48+
}
49+
public partial class CertificateForwardingOptions
50+
{
51+
public System.Func<string, System.Security.Cryptography.X509Certificates.X509Certificate2> HeaderConverter;
52+
public CertificateForwardingOptions() { }
53+
public string CertificateHeader { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
54+
}
4055
[System.FlagsAttribute]
4156
public enum ForwardedHeaders
4257
{
@@ -75,3 +90,10 @@ public IPNetwork(System.Net.IPAddress prefix, int prefixLength) { }
7590
public bool Contains(System.Net.IPAddress address) { throw null; }
7691
}
7792
}
93+
namespace Microsoft.Extensions.DependencyInjection
94+
{
95+
public static partial class CertificateForwardingServiceExtensions
96+
{
97+
public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddCertificateForwarding(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Action<Microsoft.AspNetCore.HttpOverrides.CertificateForwardingOptions> configure) { throw null; }
98+
}
99+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.AspNetCore.HttpOverrides;
6+
7+
namespace Microsoft.AspNetCore.Builder
8+
{
9+
/// <summary>
10+
/// Extension methods for using certificate fowarding.
11+
/// </summary>
12+
public static class CertificateForwardingBuilderExtensions
13+
{
14+
/// <summary>
15+
/// Adds a middleware to the pipeline that will look for a certificate in a request header
16+
/// decode it, and updates HttpContext.Connection.ClientCertificate.
17+
/// </summary>
18+
/// <param name="app"></param>
19+
/// <returns></returns>
20+
public static IApplicationBuilder UseCertificateForwarding(this IApplicationBuilder app)
21+
{
22+
if (app == null)
23+
{
24+
throw new ArgumentNullException(nameof(app));
25+
}
26+
27+
return app.UseMiddleware<CertificateForwardingMiddleware>();
28+
}
29+
}
30+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Security.Cryptography.X509Certificates;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.AspNetCore.Http.Features;
9+
using Microsoft.Extensions.Logging;
10+
using Microsoft.Extensions.Primitives;
11+
12+
namespace Microsoft.AspNetCore.HttpOverrides
13+
{
14+
internal class CertificateForwardingFeature : ITlsConnectionFeature
15+
{
16+
private ILogger _logger;
17+
private StringValues _header;
18+
private CertificateForwardingOptions _options;
19+
private X509Certificate2 _certificate;
20+
21+
public CertificateForwardingFeature(ILogger logger, StringValues header, CertificateForwardingOptions options)
22+
{
23+
_logger = logger;
24+
_options = options;
25+
_header = header;
26+
}
27+
28+
public X509Certificate2 ClientCertificate
29+
{
30+
get
31+
{
32+
if (_certificate == null)
33+
{
34+
try
35+
{
36+
_certificate = _options.HeaderConverter(_header);
37+
}
38+
catch (Exception e)
39+
{
40+
_logger.NoCertificate(e);
41+
}
42+
}
43+
return _certificate;
44+
}
45+
set => _certificate = value;
46+
}
47+
48+
public Task<X509Certificate2> GetClientCertificateAsync(CancellationToken cancellationToken)
49+
=> Task.FromResult(ClientCertificate);
50+
}
51+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Threading.Tasks;
6+
using Microsoft.AspNetCore.Http;
7+
using Microsoft.AspNetCore.Http.Features;
8+
using Microsoft.Extensions.Logging;
9+
using Microsoft.Extensions.Options;
10+
using Microsoft.Extensions.Primitives;
11+
12+
namespace Microsoft.AspNetCore.HttpOverrides
13+
{
14+
/// <summary>
15+
/// Middleware that converts a forward header into a client certificate if found.
16+
/// </summary>
17+
public class CertificateForwardingMiddleware
18+
{
19+
private readonly RequestDelegate _next;
20+
private readonly CertificateForwardingOptions _options;
21+
private readonly ILogger _logger;
22+
23+
/// <summary>
24+
/// Constructor.
25+
/// </summary>
26+
/// <param name="next"></param>
27+
/// <param name="loggerFactory"></param>
28+
/// <param name="options"></param>
29+
public CertificateForwardingMiddleware(
30+
RequestDelegate next,
31+
ILoggerFactory loggerFactory,
32+
IOptions<CertificateForwardingOptions> options)
33+
{
34+
_next = next ?? throw new ArgumentNullException(nameof(next));
35+
36+
if (loggerFactory == null)
37+
{
38+
throw new ArgumentNullException(nameof(loggerFactory));
39+
}
40+
41+
if (options == null)
42+
{
43+
throw new ArgumentNullException(nameof(options));
44+
}
45+
46+
_options = options.Value;
47+
_logger = loggerFactory.CreateLogger<CertificateForwardingMiddleware>();
48+
}
49+
50+
/// <summary>
51+
/// Looks for the presence of a <see cref="CertificateForwardingOptions.CertificateHeader"/> header in the request,
52+
/// if found, converts this header to a ClientCertificate set on the connection.
53+
/// </summary>
54+
/// <param name="httpContext">The <see cref="HttpContext"/>.</param>
55+
/// <returns>A <see cref="Task"/>.</returns>
56+
public Task Invoke(HttpContext httpContext)
57+
{
58+
var header = httpContext.Request.Headers[_options.CertificateHeader];
59+
if (!StringValues.IsNullOrEmpty(header))
60+
{
61+
httpContext.Features.Set<ITlsConnectionFeature>(new CertificateForwardingFeature(_logger, header, _options));
62+
}
63+
return _next(httpContext);
64+
}
65+
}
66+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Security.Cryptography.X509Certificates;
6+
7+
namespace Microsoft.AspNetCore.HttpOverrides
8+
{
9+
/// <summary>
10+
/// Used to configure the <see cref="CertificateForwardingMiddleware"/>.
11+
/// </summary>
12+
public class CertificateForwardingOptions
13+
{
14+
/// <summary>
15+
/// The name of the header containing the client certificate.
16+
/// </summary>
17+
/// <remarks>
18+
/// This defaults to X-Client-Cert
19+
/// </remarks>
20+
public string CertificateHeader { get; set; } = "X-Client-Cert";
21+
22+
/// <summary>
23+
/// The function used to convert the header to an instance of <see cref="X509Certificate2"/>.
24+
/// </summary>
25+
/// <remarks>
26+
/// This defaults to a conversion from a base64 encoded string.
27+
/// </remarks>
28+
public Func<string, X509Certificate2> HeaderConverter = (headerValue) => new X509Certificate2(Convert.FromBase64String(headerValue));
29+
}
30+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.AspNetCore.HttpOverrides;
6+
7+
namespace Microsoft.Extensions.DependencyInjection
8+
{
9+
/// <summary>
10+
/// Extension methods for using certificate fowarding.
11+
/// </summary>
12+
public static class CertificateForwardingServiceExtensions
13+
{
14+
/// <summary>
15+
/// Adds certificate forwarding to the specified <see cref="IServiceCollection" />.
16+
/// </summary>
17+
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
18+
/// <param name="configure">An action delegate to configure the provided <see cref="CertificateForwardingOptions"/>.</param>
19+
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
20+
public static IServiceCollection AddCertificateForwarding(
21+
this IServiceCollection services,
22+
Action<CertificateForwardingOptions> configure)
23+
{
24+
if (services == null)
25+
{
26+
throw new ArgumentNullException(nameof(services));
27+
}
28+
29+
if (configure == null)
30+
{
31+
throw new ArgumentNullException(nameof(configure));
32+
}
33+
34+
services.AddOptions<CertificateForwardingOptions>().Validate(o => !string.IsNullOrEmpty(o.CertificateHeader), "CertificateForwarderOptions.CertificateHeader cannot be null or empty.");
35+
return services.Configure(configure);
36+
}
37+
}
38+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace Microsoft.Extensions.Logging
7+
{
8+
internal static class LoggingExtensions
9+
{
10+
private static Action<ILogger, Exception> _noCertificate;
11+
12+
static LoggingExtensions()
13+
{
14+
_noCertificate = LoggerMessage.Define(
15+
eventId: new EventId(0, "NoCertificate"),
16+
logLevel: LogLevel.Warning,
17+
formatString: "Could not read certificate from header.");
18+
}
19+
20+
public static void NoCertificate(this ILogger logger, Exception exception)
21+
{
22+
_noCertificate(logger, exception);
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)