---
title: "Create a self-signed root certificate in C#"
url: https://recursiveai.org/onecrypto/cookbook/create-self-signed-root-certificate/
description: "Create a private root CA in C# with OneCrypto: generate a signing key, write a self-signed PEM certificate and run the downloadable .NET 10 example."
version: "0.4.0-preview.2"
---

# Create a self-signed root certificate in C#

**OneCrypto** is a free, open-source cryptography library for .NET from Recursive AI. MIT or Apache-2.0, preview, not yet independently audited, no native dependencies.

[NuGet](https://www.nuget.org/packages/RecursiveAI.OneCrypto) · [GitHub](https://github.com/recursive-ai-oss/onecrypto-csharp) · [Docs](https://recursiveai.org/onecrypto/docs/) · [Download example (.zip)](https://recursiveai.org/onecrypto/cookbook/create-self-signed-root-certificate/create-self-signed-root-certificate.zip)

A root CA signs other certificates. This example creates a private test root, not a publicly trusted TLS certificate.

```shell
dotnet add package RecursiveAI.OneCrypto --prerelease
```

Requires [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0) (net10.0); .NET 9 and earlier aren’t supported.

```csharp
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");
```

```text
Wrote root.pem
```

Unzip [the example](https://recursiveai.org/onecrypto/cookbook/create-self-signed-root-certificate/create-self-signed-root-certificate.zip), 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.

```csharp
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:

```text
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:

```csharp
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:

```text
Wrote root.pem
Wrote root-key.pem
```

**Use an ECDSA P-256 root instead.** This standalone snippet writes `ec-root.pem`:

```csharp
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");
```

```text
Wrote ec-root.pem
```

## Related recipes

- [Issue a certificate from a CSR](https://recursiveai.org/onecrypto/cookbook/issue-certificate-from-csr/)
- [Load a PEM private key](https://recursiveai.org/onecrypto/cookbook/load-pem-private-key/)
- [Convert PEM to PFX](https://recursiveai.org/onecrypto/cookbook/convert-pem-to-pfx/)

## 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](https://recursiveai.org/onecrypto/docs/issuance/) 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](https://recursiveai.org/onecrypto/docs/issuance/#profiles).

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](https://recursiveai.org/onecrypto/cookbook/issue-certificate-from-csr/) 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](https://www.rfc-editor.org/rfc/rfc5280#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](https://learn.microsoft.com/en-us/dotnet/core/additional-tools/self-signed-certificates-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.
