aspnetcore/src/Microsoft.AspNetCore.Diagno.../DeveloperCertificateMiddlew...

138 lines
6.3 KiB
C#

// 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.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity.Service;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
namespace Microsoft.AspNetCore.Diagnostics.Identity.Service
{
public class DeveloperCertificateMiddleware
{
private readonly RequestDelegate _next;
private readonly IHostingEnvironment _environment;
private readonly IOptions<DeveloperCertificateOptions> _options;
private readonly ITimeStampManager _timeStampManager;
private readonly IConfiguration _configuration;
private readonly IOptionsCache<IdentityServiceOptions> _identityServiceOptionsCache;
public DeveloperCertificateMiddleware(
RequestDelegate next,
IOptions<DeveloperCertificateOptions> options,
IOptionsCache<IdentityServiceOptions> identityServiceOptions,
ITimeStampManager timeStampManager,
IHostingEnvironment environment,
IConfiguration configuration)
{
_next = next;
_options = options;
_identityServiceOptionsCache = identityServiceOptions;
_environment = environment;
_timeStampManager = timeStampManager;
_configuration = configuration;
}
public async Task InvokeAsync(HttpContext context)
{
var credentialsProvider = context.RequestServices.GetRequiredService<ISigningCredentialsPolicyProvider>();
var openIdOptionsCache = context.RequestServices.GetRequiredService<IOptionsCache<OpenIdConnectOptions>>();
if (_environment.IsDevelopment() &&
context.Request.Path.Equals(_options.Value.ListeningEndpoint))
{
if (context.Request.Method.Equals(HttpMethods.Get))
{
var credentials = await credentialsProvider.GetAllCredentialsAsync();
bool hasDevelopmentCertificate = await IsDevelopmentCertificateConfiguredAndValid();
var foundDeveloperCertificate = FoundDeveloperCertificate();
if (!foundDeveloperCertificate || !hasDevelopmentCertificate)
{
var page = new DeveloperCertificateErrorPage();
page.Model = new DeveloperCertificateViewModel()
{
CertificateExists = foundDeveloperCertificate,
CertificateIsInvalid = !hasDevelopmentCertificate,
Options = _options.Value
};
await page.ExecuteAsync(context);
return;
}
}
if (context.Request.Method.Equals(HttpMethods.Post))
{
CreateDevelopmentCertificate();
return;
}
}
await _next(context);
void CreateDevelopmentCertificate()
{
using (var rsa = RSA.Create(2048))
{
var signingRequest = new CertificateRequest(
new X500DistinguishedName("CN=IdentityService.Development"), rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
var enhacedKeyUsage = new OidCollection();
enhacedKeyUsage.Add(new Oid("1.3.6.1.5.5.7.3.1", "Server Authentication"));
signingRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(enhacedKeyUsage, critical: true));
signingRequest.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true));
var certificate = signingRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(1));
certificate.FriendlyName = "Identity Service developer certificate";
// We need to take this step so that the key gets persisted.
var export = certificate.Export(X509ContentType.Pkcs12, "");
var imported = new X509Certificate2(export, "", X509KeyStorageFlags.PersistKeySet);
Array.Clear(export, 0, export.Length);
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
store.Add(imported);
store.Close();
_identityServiceOptionsCache.TryRemove(Options.DefaultName);
openIdOptionsCache.TryRemove(OpenIdConnectDefaults.AuthenticationScheme);
context.Response.StatusCode = StatusCodes.Status204NoContent;
};
}
}
bool FoundDeveloperCertificate()
{
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadOnly);
var developmentCertificate = store.Certificates.Find(
X509FindType.FindBySubjectName,
"IdentityService.Development",
validOnly: false);
store.Close();
return developmentCertificate.OfType<X509Certificate2>().Any();
}
}
async Task<bool> IsDevelopmentCertificateConfiguredAndValid()
{
var certificates = await credentialsProvider.GetAllCredentialsAsync();
return certificates.Any(
c => _timeStampManager.IsValidPeriod(c.NotBefore, c.Expires) &&
c.Credentials.Key is X509SecurityKey key &&
key.Certificate.Subject.Equals("CN=IdentityService.Development"));
}
}
}
}