A root CA signs other certificates. This example creates a private test root, not a publicly trusted TLS certificate.
dotnet add package RecursiveAI.OneCrypto --prereleaseRequires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.
using System.Security.Cryptography; using RecursiveAI.OneCrypto; using RecursiveAI.OneCrypto.Issuance; using RecursiveAI.OneCrypto.X509; using var key = RSA.Create(2048); var root = CertificateAuthority.CreateRoot( DistinguishedName.Create( (KnownOids.CommonName, "Example Root")), X509Signer.FromRsa( key, HashAlgorithmName.SHA256), DateTimeOffset.UtcNow, TimeSpan.FromDays(365)); File.WriteAllText( "root.pem", root.Certificate.ToPem()); Console.WriteLine("Wrote root.pem");
Wrote root.pemUnzip the example, then dotnet run.
The snippet at the top generates a new RSA key and writes the public certificate to root.pem. It does not save the private key or change your system’s trust store.
The full program
The full program is the snippet at the top plus two checks: the certificate’s CA flag and self-signature. It takes no input files; each run creates a different key and certificate.
using System.Security.Cryptography; using RecursiveAI.OneCrypto; using RecursiveAI.OneCrypto.Issuance; using RecursiveAI.OneCrypto.X509; using var key = RSA.Create(2048); var root = CertificateAuthority.CreateRoot( DistinguishedName.Create( (KnownOids.CommonName, "Example Root")), X509Signer.FromRsa( key, HashAlgorithmName.SHA256), DateTimeOffset.UtcNow, TimeSpan.FromDays(365)); File.WriteAllText( "root.pem", root.Certificate.ToPem()); Console.WriteLine("Wrote root.pem"); Console.WriteLine($"CA: {root.Certificate.BasicConstraints!.IsCA}"); Console.WriteLine($"Self-signature: {root.Certificate.VerifySignature(root.Certificate).IsValid}");
dotnet run prints:
Wrote root.pem CA: True Self-signature: True
An invalid profile, key or validity is refused before a certificate is returned; the file write happens only after CreateRoot succeeds.
Variations
Save the private key. Add these lines to the snippet at the top while key is still in scope:
File.WriteAllText("root-key.pem", key.ExportPkcs8PrivateKeyPem()); Console.WriteLine("Wrote root-key.pem");
Keep root-key.pem secret: this PKCS#8 PEM file is unencrypted and lets its holder sign as your CA.
The modified snippet prints:
Wrote root.pem Wrote root-key.pem
Use an ECDSA P-256 root instead. This standalone snippet writes ec-root.pem:
using System.Security.Cryptography; using RecursiveAI.OneCrypto; using RecursiveAI.OneCrypto.Issuance; using RecursiveAI.OneCrypto.X509; using var key = ECDsa.Create(ECCurve.NamedCurves.nistP256); var root = CertificateAuthority.CreateRoot( DistinguishedName.Create( (KnownOids.CommonName, "Example Root")), X509Signer.FromECDsa(key), DateTimeOffset.UtcNow, TimeSpan.FromDays(365)); File.WriteAllText( "ec-root.pem", root.Certificate.ToPem()); Console.WriteLine("Wrote ec-root.pem");
Wrote ec-root.pemRelated recipes
The short answer
Generate a signing key, wrap it in an X509Signer, and call CertificateAuthority.CreateRoot with a distinguished name, a start time and a validity period. Save root.Certificate.ToPem() to distribute the public certificate. Keep the signing key separate: the certificate alone cannot sign another certificate.
This page uses the published RecursiveAI.OneCrypto 0.4.0-preview.2 package. It is a preview with an API that may change. The example uses a private development CA profile, not a public certificate issuance service. The issuance documentation describes the profiles and refusal rules.
What is the difference between a self-signed certificate and a root CA certificate?
“Self-signed” describes who signed the certificate: its own key. “CA” describes what that certificate is allowed to do. OneCrypto’s root profile writes the CA basic constraint and the keyCertSign and cRLSign key usages, so the result is a CA certificate rather than an ordinary server certificate. The profile table is in the issuance documentation.
A self-signed server certificate and a self-signed root are therefore not interchangeable inputs to CertificateAuthority. Its constructor rejects a certificate without the CA basic constraint or the certificate-signing key usage. It also rejects a signer whose public key does not match the CA certificate.
Use a root when you need a private issuer for other certificates. Use a TLS server profile when issuing the certificate that identifies a server. Creating a root does not give it the DNS names, purpose or trust needed for a server connection. The CSR recipe shows a separate leaf certificate with the TLS server profile and a DNS subject alternative name.
Can a self-signed certificate be trusted?
Trust is a separate decision from verifying a signature. A self-signature can prove that the certificate’s bytes match its key; it cannot tell a client that your organization is an acceptable issuer. RFC 5280 section 6.1.1 lists trust-anchor information as an input to path validation, not something a certificate grants itself.
For a private deployment, clients need an explicitly configured trust anchor under your administration. This program does not install one. Do not install the demonstration root as a shortcut to making an unrelated application trust arbitrary certificates. For development HTTPS, Microsoft’s self-signed certificate guide describes certificate generation and trust configuration as separate steps.
The Self-signature: True output is deliberately narrower than “trusted.” It checks only the certificate signature with the root’s own public key. No browser, operating-system trust store or remote service is consulted.
How do I generate a self-signed certificate?
The required inputs are the subject name, signing key, notBefore and validity. The example uses a common name of Example Root, an RSA key of 2048 bits and SHA-256. These match the standard policy’s minimum RSA key size and avoid SHA-1, which that policy refuses. The ECDSA variation uses the same issuance API with a different signer.
The CA reads no clock: the example supplies DateTimeOffset.UtcNow. The one-year period in the code is a sample choice, not a requirement. The root profile’s documented longest validity is 25 years; the documentation calls the non-TLS limits defaults for a development or private CA. Pick a lifetime for your deployment rather than treating the maximum as a recommendation.
Finally, decide whether this is a disposable root or a continuing issuer. The example deliberately disposes its in-memory key when the program ends. To issue certificates later, retain the key through an appropriate key-storage system and reload it with the corresponding root certificate. Sharing root.pem is not the same as sharing that private key; only the former is the public certificate.