diff --git a/src/Microsoft.AspNetCore.Diagnostics.Identity.Service/DeveloperCertificateMiddleware.cs b/src/Microsoft.AspNetCore.Diagnostics.Identity.Service/DeveloperCertificateMiddleware.cs index ca849aac9b..38cdb446ab 100644 --- a/src/Microsoft.AspNetCore.Diagnostics.Identity.Service/DeveloperCertificateMiddleware.cs +++ b/src/Microsoft.AspNetCore.Diagnostics.Identity.Service/DeveloperCertificateMiddleware.cs @@ -44,15 +44,20 @@ namespace Microsoft.AspNetCore.Diagnostics.Identity.Service public async Task InvokeAsync(HttpContext context) { - var credentialsProvider = context.RequestServices.GetRequiredService(); - var openIdOptionsCache = context.RequestServices.GetRequiredService>(); + var credentialsProvider = context.RequestServices.GetService(); + if (credentialsProvider == null) + { + await _next(context); + return; + } + var openIdOptionsCache = context.RequestServices.GetRequiredService>(); if (_environment.IsDevelopment() && context.Request.Path.Equals(_options.Value.ListeningEndpoint)) { if (context.Request.Method.Equals(HttpMethods.Get)) { - var credentials = await credentialsProvider.GetAllCredentialsAsync(); + var credentials = await credentialsProvider.GetCredentials(); bool hasDevelopmentCertificate = await IsDevelopmentCertificateConfiguredAndValid(); var foundDeveloperCertificate = FoundDeveloperCertificate(); if (!foundDeveloperCertificate || !hasDevelopmentCertificate) @@ -102,7 +107,6 @@ namespace Microsoft.AspNetCore.Diagnostics.Identity.Service store.Open(OpenFlags.ReadWrite); store.Add(imported); store.Close(); - _identityServiceOptionsCache.TryRemove(Options.DefaultName); openIdOptionsCache.TryRemove(OpenIdConnectDefaults.AuthenticationScheme); context.Response.StatusCode = StatusCodes.Status204NoContent; @@ -131,7 +135,7 @@ namespace Microsoft.AspNetCore.Diagnostics.Identity.Service async Task IsDevelopmentCertificateConfiguredAndValid() { - var certificates = await credentialsProvider.GetAllCredentialsAsync(); + var certificates = await credentialsProvider.GetCredentials(); return certificates.Any( c => _timeStampManager.IsValidPeriod(c.NotBefore, c.Expires) && c.Credentials.Key is X509SecurityKey key && diff --git a/src/Microsoft.AspNetCore.Identity.Service.Core/DeveloperCertificateSigningCredentialsSource.cs b/src/Microsoft.AspNetCore.Identity.Service.Core/DeveloperCertificateSigningCredentialsSource.cs new file mode 100644 index 0000000000..a94802ba03 --- /dev/null +++ b/src/Microsoft.AspNetCore.Identity.Service.Core/DeveloperCertificateSigningCredentialsSource.cs @@ -0,0 +1,73 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.IdentityModel.Tokens; + +namespace Microsoft.AspNetCore.Identity.Service +{ + public class DeveloperCertificateSigningCredentialsSource : ISigningCredentialsSource + { + private readonly IHostingEnvironment _environment; + private readonly ITimeStampManager _timeStampManager; + + public DeveloperCertificateSigningCredentialsSource( + IHostingEnvironment environment, + ITimeStampManager timeStampManager) + { + _environment = environment; + _timeStampManager = timeStampManager; + } + + public Task> GetCredentials() + { + if (!_environment.IsDevelopment()) + { + return Task.FromResult(Enumerable.Empty()); + } + + using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) + { + store.Open(OpenFlags.ReadOnly); + var cert = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, "CN=IdentityService.Development", validOnly: false); + var valid = cert.OfType().FirstOrDefault(c => _timeStampManager.IsValidPeriod(c.NotBefore, c.NotAfter)); + store.Close(); + + if (valid != null) + { + return Task.FromResult>(new[] { CreateDescriptor(valid) }); + } + else + { + return Task.FromResult(Enumerable.Empty()); + } + } + } + + private SigningCredentialsDescriptor CreateDescriptor(X509Certificate2 certificate) + { + CryptographyHelpers.ValidateRsaKeyLength(certificate); + var credentials = new SigningCredentials(new X509SecurityKey(certificate), CryptographyHelpers.FindAlgorithm(certificate)); + return new SigningCredentialsDescriptor( + credentials, + CryptographyHelpers.GetAlgorithm(credentials), + certificate.NotBefore, + certificate.NotAfter, + GetMetadata()); + + IDictionary GetMetadata() + { + var rsaParameters = CryptographyHelpers.GetRSAParameters(credentials); + return new Dictionary + { + [JsonWebKeyParameterNames.E] = Base64UrlEncoder.Encode(rsaParameters.Exponent), + [JsonWebKeyParameterNames.N] = Base64UrlEncoder.Encode(rsaParameters.Modulus), + }; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Identity.Service.Core/IdentityServiceBuilderExtensions.cs b/src/Microsoft.AspNetCore.Identity.Service.Core/IdentityServiceBuilderExtensions.cs index 74aa3661c5..fa3d0ad36a 100644 --- a/src/Microsoft.AspNetCore.Identity.Service.Core/IdentityServiceBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Identity.Service.Core/IdentityServiceBuilderExtensions.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Security.Cryptography.X509Certificates; +using Microsoft.AspNetCore.Identity.Service.Core; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; @@ -56,6 +58,20 @@ namespace Microsoft.AspNetCore.Identity.Service return builder; } + public static IIdentityServiceBuilder DisableDeveloperCertificate(this IIdentityServiceBuilder builder) + { + var services = builder.Services; + foreach (var service in services.ToList()) + { + if (service.ImplementationType == typeof(DeveloperCertificateSigningCredentialsSource)) + { + services.Remove(service); + } + } + + return builder; + } + public static IIdentityServiceBuilder AddSigningCertificate(this IIdentityServiceBuilder builder, Func func) { var cert = func(); diff --git a/src/Microsoft.AspNetCore.Identity.Service/IdentityServiceServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Identity.Service/IdentityServiceServiceCollectionExtensions.cs index 7a240dd84a..4ec8332478 100644 --- a/src/Microsoft.AspNetCore.Identity.Service/IdentityServiceServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Identity.Service/IdentityServiceServiceCollectionExtensions.cs @@ -83,6 +83,8 @@ namespace Microsoft.Extensions.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton, PasswordHasher>(); services.AddScoped();