Skip to content
ecdsa.com

Self-signed certificate · .NET

How to create a self-signed EC P-256 certificate with SANs in C#

.NET creates self-signed certificates without packages: CertificateRequest takes the subject, an ECDsa P-256 key and SHA-256, the SANs and extensions go into CertificateExtensions, and CreateSelfSigned signs the certificate with its own key. The run is a single-file app (dotnet run app.cs); OpenSSL then decodes cert.pem and verifies it for localhost and 127.0.0.1.

Tested with .NET 10.0.402 (SDK, System.Security.Cryptography.X509Certificates).

app.cs — P-256 key and self-signed server certificate
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, critical
request.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

  1. ECDsa.Create(ECCurve.NamedCurves.nistP256) creates the key; new CertificateRequest("CN=localhost", key, HashAlgorithmName.SHA256) sets the subject and the signature hash.
  2. SubjectAlternativeNameBuilder collects the DNS name and IPAddress.Loopback (127.0.0.1); Build() turns them into the extension.
  3. The last constructor argument of X509BasicConstraintsExtension and X509KeyUsageExtension is critical; serverAuth is given by its OID, 1.3.6.1.5.5.7.3.1.
  4. CreateSelfSigned(notBefore, notBefore.AddDays(365)) signs the certificate; ExportCertificatePem() writes it, and key.ExportPkcs8PrivateKeyPem() writes the key.

Gotchas

  • On Linux, Oid.FromFriendlyName("Server Authentication", OidGroup.EnhancedKeyUsage) fails with CryptographicException: No OID value matches this name. — the dotted OID works everywhere.
  • The certificate from CreateSelfSigned already holds the private key (HasPrivateKey is true), but ExportCertificatePem() exports only the certificate. Save the key separately; X509Certificate2.CreateFromPemFile("cert.pem", "key.pem") loads both back.
  • CreateSelfSigned checks the order of the dates: a notAfter before notBefore throws ArgumentException: The provided notBefore value is later than the notAfter value.
  • For ASP.NET Core development, dotnet dev-certs https makes 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-password writes it out.

Related recipes