File: src\Servers\Kestrel\shared\test\CertHelper.cs
Web Access
Project: src\src\Servers\Kestrel\test\InMemory.FunctionalTests\InMemory.FunctionalTests.csproj (InMemory.FunctionalTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
 
namespace Microsoft.AspNetCore.InternalTesting;
 
#nullable enable
// Copied from https://github.com/dotnet/runtime/main/src/libraries/System.Net.Security/tests/FunctionalTests/TestHelper.cs
public static class CertHelper
{
    private static readonly X509KeyUsageExtension s_eeKeyUsage =
        new X509KeyUsageExtension(
            X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DataEncipherment,
            critical: false);
 
    private static readonly X509EnhancedKeyUsageExtension s_tlsServerEku =
        new X509EnhancedKeyUsageExtension(
            new OidCollection
            {
                new Oid("1.3.6.1.5.5.7.3.1", null)
            },
            false);
 
    private static readonly X509EnhancedKeyUsageExtension s_tlsClientEku =
        new X509EnhancedKeyUsageExtension(
            new OidCollection
            {
                new Oid("1.3.6.1.5.5.7.3.2", null)
            },
            false);
 
    private static readonly X509BasicConstraintsExtension s_eeConstraints =
        new X509BasicConstraintsExtension(false, false, 0, false);
 
    public static bool AllowAnyServerCertificate(object sender, X509Certificate certificate, X509Chain chain)
    {
        return true;
    }
 
    internal static (NetworkStream ClientStream, NetworkStream ServerStream) GetConnectedTcpStreams()
    {
        using (Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
        {
            listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
            listener.Listen(1);
 
            var clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            clientSocket.Connect(listener.LocalEndPoint!);
            Socket serverSocket = listener.Accept();
 
            serverSocket.NoDelay = true;
            clientSocket.NoDelay = true;
 
            return (new NetworkStream(clientSocket, ownsSocket: true), new NetworkStream(serverSocket, ownsSocket: true));
        }
    }
 
    internal static void CleanupCertificates([CallerMemberName] string? testName = null)
    {
        string caName = $"O={testName}";
        try
        {
            using (X509Store store = new X509Store(StoreName.CertificateAuthority, StoreLocation.LocalMachine))
            {
                store.Open(OpenFlags.ReadWrite);
                foreach (X509Certificate2 cert in store.Certificates)
                {
                    if (cert.Subject.Contains(caName))
                    {
                        store.Remove(cert);
                    }
                    cert.Dispose();
                }
            }
        }
        catch { };
 
        try
        {
            using (X509Store store = new X509Store(StoreName.CertificateAuthority, StoreLocation.CurrentUser))
            {
                store.Open(OpenFlags.ReadWrite);
                foreach (X509Certificate2 cert in store.Certificates)
                {
                    if (cert.Subject.Contains(caName))
                    {
                        store.Remove(cert);
                    }
                    cert.Dispose();
                }
            }
        }
        catch { };
    }
 
    internal static X509ExtensionCollection BuildTlsServerCertExtensions(string serverName)
    {
        return BuildTlsCertExtensions(serverName, true);
    }
 
    private static X509ExtensionCollection BuildTlsCertExtensions(string targetName, bool serverCertificate)
    {
        X509ExtensionCollection extensions = new X509ExtensionCollection();
 
        SubjectAlternativeNameBuilder builder = new SubjectAlternativeNameBuilder();
        builder.AddDnsName(targetName);
        extensions.Add(builder.Build());
        extensions.Add(s_eeConstraints);
        extensions.Add(s_eeKeyUsage);
        extensions.Add(serverCertificate ? s_tlsServerEku : s_tlsClientEku);
 
        return extensions;
    }
 
    internal static (X509Certificate2 certificate, X509Certificate2Collection) GenerateCertificates(string targetName, [CallerMemberName] string? testName = null, bool longChain = false, bool serverCertificate = true)
    {
        const int keySize = 2048;
        if (OperatingSystem.IsWindows() && testName != null)
        {
            CleanupCertificates(testName);
        }
 
        X509Certificate2Collection chain = new X509Certificate2Collection();
        X509ExtensionCollection extensions = BuildTlsCertExtensions(targetName, serverCertificate);
 
        CertificateAuthority.BuildPrivatePki(
            PkiOptions.IssuerRevocationViaCrl,
            out RevocationResponder responder,
            out CertificateAuthority root,
            out CertificateAuthority[] intermediates,
            out X509Certificate2 endEntity,
            intermediateAuthorityCount: longChain ? 3 : 1,
            subjectName: targetName,
            testName: testName,
            keySize: keySize,
            extensions: extensions);
 
        // Walk the intermediates backwards so we build the chain collection as
        // Issuer3
        // Issuer2
        // Issuer1
        // Root
        for (int i = intermediates.Length - 1; i >= 0; i--)
        {
            CertificateAuthority authority = intermediates[i];
 
            chain.Add(authority.CloneIssuerCert());
            authority.Dispose();
        }
 
        chain.Add(root.CloneIssuerCert());
 
        responder.Dispose();
        root.Dispose();
 
        if (OperatingSystem.IsWindows())
        {
            X509Certificate2 ephemeral = endEntity;
            endEntity = new X509Certificate2(endEntity.Export(X509ContentType.Pfx), (string?)null, X509KeyStorageFlags.Exportable);
            ephemeral.Dispose();
        }
 
        return (endEntity, chain);
    }
 
    internal static string GetTestSNIName(string testMethodName, params SslProtocols?[] protocols)
    {
        static string ProtocolToString(SslProtocols? protocol)
        {
            return (protocol?.ToString() ?? "null").Replace(", ", "-");
        }
 
        var args = string.Join(".", protocols.Select(p => ProtocolToString(p)));
        var name = testMethodName.Length > 63 ? testMethodName.Substring(0, 63) : testMethodName;
 
        name = $"{name}.{args}";
        if (OperatingSystem.IsAndroid())
        {
            // Android does not support underscores in host names
            name = name.Replace("_", string.Empty);
        }
 
        return name;
    }
}