---
title: "Issue a certificate from a CSR in C#"
url: https://recursiveai.org/onecrypto/cookbook/issue-certificate-from-csr/
description: "Issue a TLS certificate from a CSR in C# with OneCrypto. Check the request signature, apply a CA profile and run a downloadable private-CA example."
version: "0.4.0-preview.2"
---

# Issue a certificate from a CSR 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/issue-certificate-from-csr/issue-certificate-from-csr.zip)

A certificate signing request (CSR) asks a CA to certify a public key. This example issues a private 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.

`test-ca.pem` and `request.pem` are test-only inputs in the zip; the full program regenerates both. Never reuse its demonstration CA key.

```csharp
using RecursiveAI.OneCrypto;
using RecursiveAI.OneCrypto.Issuance;

// Test-only CA and CSR from the zip.
using var bundle = Crypto.Load("test-ca.pem");
var ca = new CertificateAuthority(
    bundle.Certificates[0],
    X509Signer.FromKey(
        bundle.PrivateKeys[0].Signing!));
var request =
    CertificateSigningRequest.FromPem(
    File.ReadAllText("request.pem"));
var leaf = ca.Issue(request,
    new CertificateIssueOptions {
    Profile = CertificateProfile.TlsServer,
    NotBefore = DateTimeOffset.UtcNow,
    Validity = TimeSpan.FromDays(30)
});
File.WriteAllText("leaf.pem", leaf.ToPem());
Console.WriteLine("Wrote leaf.pem");
```

```text
Wrote leaf.pem
```

Unzip [the example](https://recursiveai.org/onecrypto/cookbook/issue-certificate-from-csr/issue-certificate-from-csr.zip), then `dotnet run`.

## The full program

This program creates an isolated RSA CA and applicant key, makes a CSR, then issues a TLS server certificate. The CSR asks for DNS name `www.example.test` and `CA:TRUE`; the TLS profile deliberately issues a non-CA certificate. These are the request’s input fields:

```text
Subject: CN=www.example.test
Requested subject alternative name: DNS:www.example.test
Requested basic constraints: CA:TRUE
```

```csharp
using System.Security.Cryptography;
using RecursiveAI.OneCrypto;
using RecursiveAI.OneCrypto.Issuance;
using RecursiveAI.OneCrypto.X509;

// Generate an isolated demonstration CA and applicant key in memory.
var now = DateTimeOffset.UtcNow;
using var caKey = RSA.Create(2048);
var ca = CertificateAuthority.CreateRoot(
    DistinguishedName.Create(
        (KnownOids.CommonName, "Example Root")),
    X509Signer.FromRsa(caKey, HashAlgorithmName.SHA256),
    now.AddDays(-1), TimeSpan.FromDays(365));
using var applicantKey = RSA.Create(2048);
var request = CertificateSigningRequestBuilder.Create(
    new CertificateSigningRequestOptions {
        Subject = DistinguishedName.Create((KnownOids.CommonName, "www.example.test")),
        Extensions = [
            Extension.Create(new SubjectAlternativeName([GeneralName.Dns("www.example.test")]), false),
            Extension.Create(new BasicConstraints(true), true)
        ]
    }, X509Signer.FromRsa(applicantKey, HashAlgorithmName.SHA256));
File.WriteAllText("request.pem", request.ToPem());
// Test-only unencrypted key: never install or reuse this demonstration CA.
File.WriteAllText("test-ca.pem", ca.Certificate.ToPem() + caKey.ExportPkcs8PrivateKeyPem());

var options = new CertificateIssueOptions {
    Profile = CertificateProfile.TlsServer,
    NotBefore = now,
    Validity = TimeSpan.FromDays(30),
};
var leaf = ca.Issue(request, options);
File.WriteAllText("leaf.pem", leaf.ToPem());
Console.WriteLine("Wrote request.pem and leaf.pem");
Console.WriteLine($"CA: {leaf.BasicConstraints!.IsCA}");
Console.WriteLine($"Issuer signature: {leaf.VerifySignature(ca.Certificate).IsValid}");

// Damage only the signature, preserving the request's ASN.1 structure.
byte[] damaged = request.Encoded.ToArray();
damaged[^1] ^= 1;
try {
    ca.Issue(CertificateSigningRequest.Parse(damaged), options);
} catch (IssuanceException error) {
    Console.WriteLine($"Changed CSR: {error.Rule}");
}
```

`dotnet run` prints:

```text
Wrote request.pem and leaf.pem
CA: False
Issuer signature: True
Changed CSR: csr-signature
```

A CSR with a bad signature raises `IssuanceException` with rule `csr-signature`; this example writes the issued certificate only after `Issue` succeeds.

## Variations

**Override the requested DNS names.** Add this property to `CertificateIssueOptions` in the snippet at the top; keep its other properties:

```csharp
SubjectAlternativeNames = [GeneralName.Dns("approved.example.test")]
```

After `Issue` returns, also print the issued SAN:

```csharp
Console.WriteLine(leaf.SubjectAlternativeName!.Names[0].Value);
```

The modified snippet prints:

```text
Wrote leaf.pem
approved.example.test
```

**Check a request’s signature without issuing.** Uses `request.pem` from the download:

```csharp
using RecursiveAI.OneCrypto;

var request =
    CertificateSigningRequest.FromPem(
    File.ReadAllText("request.pem"));
Console.WriteLine(request.VerifySignature().IsValid);
```

```text
True
```

## Related recipes

- [Create a self-signed root certificate](https://recursiveai.org/onecrypto/cookbook/create-self-signed-root-certificate/)
- [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

Load your issuing CA certificate and its signing key, construct `CertificateAuthority`, parse the CSR with `CertificateSigningRequest.FromPem`, then call `ca.Issue(request, options)`. Set the certificate profile and issuance time explicitly. Write the returned certificate with `ToPem()`.

The runnable example uses `RecursiveAI.OneCrypto` **0.4.0-preview.2**, a preview package. It creates a demonstration CA locally; it does not submit the CSR to an external authority. Replace the demonstration issuer with your authorized CA and its key for a private issuance workflow. The [issuance documentation](https://recursiveai.org/onecrypto/docs/issuance/) defines what the library checks and what it takes from a request.

## How to generate a certificate from CSR?

A CSR is an input to issuance, not a certificate and not the applicant’s private key. OneCrypto takes the subject and public key from it, checks its signature, and then applies the selected profile. The CA’s private signing key signs the resulting certificate; the applicant keeps its own private key. The PKCS #10 model is described in [RFC 2986 section 3](https://www.rfc-editor.org/rfc/rfc2986#section-3).

The constructor checks that the issuer is actually a CA, permits certificate signing and matches the signer’s public key. A server certificate plus an unrelated key is not a substitute for an issuer. In the example the root and applicant are separate RSA keys, even though both are created in one process for convenience.

Choose `CertificateProfile.TlsServer` for a server certificate. That profile requires a DNS or IP subject alternative name (SAN), the names clients use to identify the service. If `SubjectAlternativeNames` is null, the issuer takes a SAN request from the CSR. The variation supplies an explicit allowlisted name instead. Do not assume that checking the CSR’s signature authorizes its requested DNS names: your issuance workflow must decide which names it will certify.

The profile controls other extensions. A requested `CA:TRUE` or extended key usage is ignored rather than blindly copied. That is why the example’s output says `CA: False` despite its CSR asking for CA status. The profile writes the certificate’s basic constraints, key usage and extended key usage. A caller may supply additional extensions, but repeating an extension the CA itself writes is refused.

Validity also belongs to the issuer’s decision. The sample chooses 30 days and supplies the current time. An explicitly requested validity beyond the profile limit or the issuer’s end date is refused, not silently shortened. When validity is omitted, the documented behavior is to use the profile’s longest period cut back to the issuer’s end date. The issuer must itself be valid at `NotBefore`.

## How to verify a CSR certificate?

For a CSR, check `request.VerifySignature().IsValid`, as in the variation. For issuance, `Issue` performs that check first and rejects a failed proof of possession with rule `csr-signature`. The full program flips one bit in the signature bytes, parses the still-structured request and catches exactly that rejection.

This checks possession of the corresponding signing key, not a person’s identity, control of a domain or authorization to receive a certificate. Those decisions must happen in the surrounding CA workflow before calling `Issue`.

For the issued certificate, `leaf.VerifySignature(ca.Certificate).IsValid` checks its signature against the issuer. That is the `Issuer signature: True` line in the output. It is not a trust-store lookup or a full certificate-path validation result. [RFC 5280 section 6.1.1](https://www.rfc-editor.org/rfc/rfc5280#section-6.1.1) treats the trust anchor and validation time as explicit inputs to path validation. A certificate issued by this demonstration CA does not become publicly trusted because its signature is correct.

The demonstration writes an unencrypted CA key solely to make the top snippet runnable after downloading. Treat `test-ca.pem` as public test material, not as a deployable issuer. Running the full program replaces it with another disposable key; no system trust store is modified.
