using System.Security.Cryptography;using System.Security.Cryptography.X509Certificates; using var key = ECDsa.Create(ECCurve.NamedCurves.nistP256); // The name string is read most-specific first (as .NET prints names), so CN comes firstvar request = new CertificateRequest( "CN=api.example.com, O=Example Corp, C=US", key, HashAlgorithmName.SHA256); var san = new SubjectAlternativeNameBuilder();san.AddDnsName("api.example.com");san.AddDnsName("www.example.com");request.CertificateExtensions.Add(san.Build()); // PKCS#10, signed with the request's own key (ecdsa-with-SHA256)File.WriteAllText("csr.pem", request.CreateSigningRequestPem());File.WriteAllText("key.pem", key.ExportPkcs8PrivateKeyPem());How it works
ECDsa.Create(ECCurve.NamedCurves.nistP256)creates the key.new CertificateRequest(name, key, HashAlgorithmName.SHA256)fixes the subject and the signature algorithm (ecdsa-with-SHA256).SubjectAlternativeNameBuilderwithAddDnsNamebuilds the SAN extension; extensions added toCertificateExtensionsgo into the CSR's extensionRequest.CreateSigningRequestPem()(.NET 7+) returns the PEM, andExportPkcs8PrivateKeyPem()the key.
Gotchas
- The name string is read most-specific first, as .NET prints names:
"C=US, O=Example Corp, CN=api.example.com"produces a subject in reverse order (CNfirst in the DER). Write"CN=…, O=…, C=US".X500DistinguishedNameBuilderfollows the same convention — components added C, O, CN also end up reversed. CreateSigningRequest()returns DER bytes; CAs and ACME clients usually want the PEM fromCreateSigningRequestPem().ExportPkcs8PrivateKeyPem()writes the key unencrypted;ExportEncryptedPkcs8PrivateKeyPem(password, …)protects it with a password.CertificateRequestcan also sign certificates (CreateSelfSigned,Create), but the CSR itself carries only what you add — key usages and validity are chosen by the CA.