File: DeveloperCertificateService.cs
Web Access
Project: src\src\Aspire.Hosting\Aspire.Hosting.csproj (Aspire.Hosting)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Hosting.Utils;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System.Collections.Immutable;
using System.Security.Cryptography.X509Certificates;
 
namespace Aspire.Hosting;
 
#pragma warning disable ASPIRECERTIFICATES001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
internal class DeveloperCertificateService : IDeveloperCertificateService
#pragma warning restore ASPIRECERTIFICATES001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
{
    private readonly Lazy<ImmutableList<X509Certificate2>> _certificates;
    private readonly Lazy<bool> _supportsContainerTrust;
 
    public DeveloperCertificateService(ILogger<DeveloperCertificateService> logger, IConfiguration configuration, DistributedApplicationOptions options)
    {
        _certificates = new Lazy<ImmutableList<X509Certificate2>>(() =>
        {
            try
            {
                var devCerts = new List<X509Certificate2>();
 
                using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
                store.Open(OpenFlags.ReadOnly);
 
                // Order by version and expiration date descending to get the most recent, highest version first.
                // OpenSSL will only check the first self-signed certificate in the bundle that matches a given domain,
                // so we want to ensure the certificate that will be used by ASP.NET Core is the first one in the bundle.
                // Match the ordering logic ASP.NET Core uses, including DateTimeOffset.Now for current time: https://github.com/dotnet/aspnetcore/blob/0aefdae365ff9b73b52961acafd227309524ce3c/src/Shared/CertificateGeneration/CertificateManager.cs#L122
                var now = DateTimeOffset.Now;
                devCerts.AddRange(
                    store.Certificates
                        .Where(c => c.IsAspNetCoreDevelopmentCertificate())
                        .Where(c => c.NotBefore <= now && now <= c.NotAfter)
                        .OrderByDescending(c => c.GetCertificateVersion())
                        .Take(1));
 
                if (devCerts.Count == 0)
                {
                    logger.LogInformation("No ASP.NET Core developer certificates found in the CurrentUser/My certificate store.");
                    return ImmutableList<X509Certificate2>.Empty;
                }
 
                return devCerts.ToImmutableList();
            }
            catch (Exception ex)
            {
                logger.LogWarning("Failed to load developer certificates from the CurrentUser/My certificate store. Automatic trust of development certificates will not be available. Reason: {Message}", ex.Message);
                return ImmutableList<X509Certificate2>.Empty;
            }
        });
 
        _supportsContainerTrust = new Lazy<bool>(() =>
        {
            var containerTrustAvailable = Certificates.Any(c => c.GetCertificateVersion() >= X509Certificate2Extensions.MinimumCertificateVersionSupportingContainerTrust);
            logger.LogDebug("Container trust for developer certificates is {Status}.", containerTrustAvailable ? "available" : "not available");
            return containerTrustAvailable;
        });
 
        // Environment variable config > DistributedApplicationOptions > default true
        TrustCertificate = configuration.GetBool(KnownConfigNames.TrustDeveloperCertificate) ??
            options.TrustDeveloperCertificate ??
            true;
    }
 
    /// <inheritdoc />
    public ImmutableList<X509Certificate2> Certificates => _certificates.Value;
 
    /// <inheritdoc />
    public bool SupportsContainerTrust => _supportsContainerTrust.Value;
 
    /// <inheritdoc />
    public bool TrustCertificate { get; }
}