using System.Net;using System.Security.Cryptography;using System.Security.Cryptography.X509Certificates; using var key = ECDsa.Create(ECCurve.NamedCurves.nistP256);var request = new CertificateRequest("CN=localhost", key, HashAlgorithmName.SHA256); var san = new SubjectAlternativeNameBuilder();san.AddDnsName("localhost");san.AddIpAddress(IPAddress.Loopback);request.CertificateExtensions.Add(san.Build());request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true)); // CA:FALSE, criticalrequest.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, true));request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension([new Oid("1.3.6.1.5.5.7.3.1")], false)); // serverAuth var notBefore = DateTimeOffset.UtcNow;using X509Certificate2 cert = request.CreateSelfSigned(notBefore, notBefore.AddDays(365)); File.WriteAllText("cert.pem", cert.ExportCertificatePem());File.WriteAllText("key.pem", key.ExportPkcs8PrivateKeyPem());How it works
ECDsa.Create(ECCurve.NamedCurves.nistP256)creates the key;new CertificateRequest("CN=localhost", key, HashAlgorithmName.SHA256)sets the subject and the signature hash.SubjectAlternativeNameBuildercollects the DNS name andIPAddress.Loopback(127.0.0.1);Build()turns them into the extension.- The last constructor argument of
X509BasicConstraintsExtensionandX509KeyUsageExtensioniscritical; serverAuth is given by its OID,1.3.6.1.5.5.7.3.1. CreateSelfSigned(notBefore, notBefore.AddDays(365))signs the certificate;ExportCertificatePem()writes it, andkey.ExportPkcs8PrivateKeyPem()writes the key.
Gotchas
- On Linux,
Oid.FromFriendlyName("Server Authentication", OidGroup.EnhancedKeyUsage)fails withCryptographicException: No OID value matches this name.— the dotted OID works everywhere. - The certificate from
CreateSelfSignedalready holds the private key (HasPrivateKeyis true), butExportCertificatePem()exports only the certificate. Save the key separately;X509Certificate2.CreateFromPemFile("cert.pem", "key.pem")loads both back. CreateSelfSignedchecks the order of the dates: a notAfter before notBefore throwsArgumentException: The provided notBefore value is later than the notAfter value.- For ASP.NET Core development,
dotnet dev-certs httpsmakes a ready localhost certificate instead: RSA 2048-bit, valid for a year, with SANs for localhost, 127.0.0.1 and ::1;--export-path dev.pem --format Pem --no-passwordwrites it out.