File: DeveloperCertificateServiceTests.cs
Web Access
Project: src\tests\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj (Aspire.Hosting.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
 
namespace Aspire.Hosting.Tests;
 
public class DeveloperCertificateServiceTests
{
    [Fact]
    public async Task GetKeyMaterialAsync_WithRsaUserCertificate_ReturnsKeyAndPfx()
    {
        using var cert = CreateRsaSelfSignedCertificate();
 
        var (keyPem, pfxBytes) = await DeveloperCertificateService.GetKeyMaterialAsync(
            cert,
            password: null,
            needKeyPem: true,
            needPfx: true,
            TestContext.Current.CancellationToken);
 
        Assert.NotNull(keyPem);
        Assert.NotNull(pfxBytes);
        Assert.Contains("PRIVATE KEY", new string(keyPem!));
    }
 
    [Fact]
    public async Task GetKeyMaterialAsync_WithEcdsaUserCertificate_ReturnsKeyAndPfx()
    {
        using var cert = CreateEcdsaSelfSignedCertificate();
 
        var (keyPem, pfxBytes) = await DeveloperCertificateService.GetKeyMaterialAsync(
            cert,
            password: null,
            needKeyPem: true,
            needPfx: true,
            TestContext.Current.CancellationToken);
 
        Assert.NotNull(keyPem);
        Assert.NotNull(pfxBytes);
        Assert.Contains("PRIVATE KEY", new string(keyPem!));
    }
 
    [Fact]
    public async Task GetKeyMaterialAsync_WithEcdsaUserCertificate_WithPassword_ProducesEncryptedPem()
    {
        using var cert = CreateEcdsaSelfSignedCertificate();
 
        var (keyPem, pfxBytes) = await DeveloperCertificateService.GetKeyMaterialAsync(
            cert,
            password: "test-password",
            needKeyPem: true,
            needPfx: true,
            TestContext.Current.CancellationToken);
 
        Assert.NotNull(keyPem);
        Assert.NotNull(pfxBytes);
        Assert.Contains("ENCRYPTED PRIVATE KEY", new string(keyPem!));
    }
 
    [Fact]
    public async Task GetKeyMaterialAsync_WithCertificateWithoutPrivateKey_Throws()
    {
        using var cert = CreateEcdsaSelfSignedCertificate();
        using var publicOnly = X509CertificateLoader.LoadCertificate(cert.Export(X509ContentType.Cert));
 
        await Assert.ThrowsAsync<InvalidOperationException>(async () =>
            await DeveloperCertificateService.GetKeyMaterialAsync(
                publicOnly,
                password: null,
                needKeyPem: true,
                needPfx: true,
                TestContext.Current.CancellationToken));
    }
 
    private static X509Certificate2 CreateRsaSelfSignedCertificate()
    {
        var subject = new X500DistinguishedName($"CN=aspire-test-{Guid.NewGuid():N}");
 
        using var rsa = RSA.Create(2048);
        var req = new CertificateRequest(subject, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        return req.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(30));
    }
 
    private static X509Certificate2 CreateEcdsaSelfSignedCertificate()
    {
        var subject = new X500DistinguishedName($"CN=aspire-test-{Guid.NewGuid():N}");
 
        using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
        var req = new CertificateRequest(subject, ecdsa, HashAlgorithmName.SHA256);
        using var selfSigned = req.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(30));
 
        // Round-trip through PFX so the returned cert is detached from the in-memory ECDsa instance.
        var pfxBytes = selfSigned.Export(X509ContentType.Pfx);
        return X509CertificateLoader.LoadPkcs12(pfxBytes, password: null, X509KeyStorageFlags.Exportable);
    }
}