dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.43k stars 10.01k forks source link

Update cert auth tests to generate certs dynamically instead #32813

Closed HaoK closed 2 years ago

HaoK commented 3 years ago

Rather than relying on checked in certs https://github.com/dotnet/aspnetcore/pull/32812 which have hardcoded dates, we should generate certs at runtime to have the expected properties so we don't accidentally floor all our builds again by surprise on random dates.

HaoK commented 3 years ago

Also cert test is skipped due to failing on ubuntu (need to reenable)

HaoK commented 3 years ago

From @bartonjs in https://github.com/dotnet/aspnetcore/pull/32806#issuecomment-843574425

This makes ephemeral-key certificates, they can't be used with SslStream (or anything that uses SslStream, like HttpClient) on Windows (see below).

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace Namespace
{
    internal static class IDunnoCerts
    {
        private static readonly X509KeyUsageExtension s_digitalSignatureOnlyUsage =
               new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, true);

        internal static void GenerateCerts(
            out X509Certificate2 validSelfSignedClientEkuCertificate,
            out X509Certificate2 validSelfSignedServerEkuCertificate,
            out X509Certificate2 validSelfSignedNoEkuCertificate,
            out X509Certificate2 selfSignedNoEkuCertificateExpired,
            out X509Certificate2 selfSignedNoEkuCertificateNotValidYet)
        {
            DateTimeOffset now = DateTimeOffset.UtcNow;

            validSelfSignedClientEkuCertificate = MakeCert(
                "CN=Valid Self Signed Client EKU,OU=dev,DC=idunno-dev,DC=org",
                "1.3.6.1.5.5.7.3.2",
                now);

            validSelfSignedServerEkuCertificate = MakeCert(
                "CN=Valid Self Signed Server EKU,OU=dev,DC=idunno-dev,DC=org",
                "1.3.6.1.5.5.7.3.1",
                now);

            validSelfSignedNoEkuCertificate = MakeCert(
                "CN=Valid Self Signed No EKU,OU=dev,DC=idunno-dev,DC=org",
                eku: null,
                now);

            selfSignedNoEkuCertificateExpired = MakeCert(
                "CN=Expired Self Signed,OU=dev,DC=idunno-dev,DC=org",
                eku: null,
                now.AddYears(-1),
                now.AddDays(-1));

            selfSignedNoEkuCertificateNotValidYet = MakeCert(
                "CN=Not Valid Yet Self Signed,OU=dev,DC=idunno-dev,DC=org",
                eku: null,
                now.AddYears(2),
                now.AddYears(3));
        }

        private static X509Certificate2 MakeCert(
            string subjectName,
            string eku,
            DateTimeOffset now)
        {
            return MakeCert(subjectName, eku, now, now.AddYears(5));
        }

        private static X509Certificate2 MakeCert(
            string subjectName,
            string eku,
            DateTimeOffset notBefore,
            DateTimeOffset notAfter)
        {
            using (RSA key = RSA.Create(2048))
            {
                CertificateRequest request = new CertificateRequest(
                    subjectName,
                    key,
                    HashAlgorithmName.SHA256,
                    RSASignaturePadding.Pkcs1);

                request.CertificateExtensions.Add(s_digitalSignatureOnlyUsage);

                if (eku != null)
                {
                    request.CertificateExtensions.Add(
                        new X509EnhancedKeyUsageExtension(
                            new OidCollection { new Oid(eku, null) }, false));
                }

                return request.CreateSelfSigned(notBefore, notAfter);
            }
        }
    }
}

On Windows you'll need to have a persisted private key to use SslStream (because S/Channel requires it), and the way to go about that is new X509Certificate2(cert.Export(X509ContentType.Pkcs12)). When this certificate object gets disposed/collected it'll clean up the temporary keyfile. You can run this code on all OSes if you want, it doesn't hurt functionality... just perf.

That's easier than creating the keys as persisted to begin with, since that's a Windows-only concept.