# Certificate-Based Signatures

> Sign PDF documents with X.509 certificates: requirements, DigitalSignature, PFX data, the Windows certificate store, Azure Key Vault and certificate checks.

- **Product:** TX Text Control .NET Server
- **Section:** Documentation
- **Kind:** Topic
- **Path:** TX Text Control .NET Server > Digital Signatures > Certificate-Based Signatures
- **4191 words**
- **Web URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/certificate-based/
- **LLMs URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/certificate-based/llms.txt
- **LLMs-Full URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/certificate-based/llms-full.txt

---

A digital signature is only as trustworthy as the certificate behind it. An X.509 certificate binds a public key to a person or an organization, and the certificate authority that issued it vouches for that binding. TX Text Control takes the certificate as a .NET `X509Certificate2` object, so you load it with the APIs of .NET and hand it to a `DigitalSignature`. The certificate can come from a file, from memory, from the Windows certificate store or from Azure Key Vault.

[Digitally Sign a PDF](https://docs.textcontrol.com/tx-text-control-dotnet-server/getting-started/pdf/sign/llms-full.txt) in Getting Started signs a first document with a PFX file. Certificates for tests are the subject of [Self-Signed Certificates](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/self-signed/llms-full.txt).

### What a Signing Certificate Needs

- **A private key.** The signature is created with the private key, so the certificate must come with it. A PFX file, also called PKCS#12, contains the certificate and the key, which is why the samples use PFX files.
- **An exportable key.** The samples of Text Control load signing certificates with `X509KeyStorageFlags.Exportable` and create certificates in the Windows certificate store with an exportable key, as [Self-Signed Certificates](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/self-signed/llms-full.txt) shows.
- **A certificate authority that recipients trust.** Signatures that other people must trust need a certificate from a certificate authority (CA). Self-signed certificates are for tests and internal use.
- **A valid period.** Validators check whether the certificate was valid when the document was signed, so sign only with a certificate that hasn't expired. [RFC 3161 Timestamps](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/timestamps/llms-full.txt) explains how a timestamp keeps signatures verifiable after the certificate has expired.

### How TX Text Control Takes the Certificate

A [DigitalSignature](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/digitalsignature/llms-full.txt) represents a certificate for signing a PDF or PDF/A document. It has two [constructors](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/digitalsignature/constructor/llms-full.txt) and no other members:

| Constructor | Used for |
|---|---|
| `DigitalSignature(X509Certificate2 x509, string timeServerURL)` | Signing the whole document with [SaveSettings.DigitalSignature](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/savesettings/digitalsignature-property/llms-full.txt) |
| `DigitalSignature(X509Certificate2 x509, string timeServerURL, string signatureFieldName)` | Signing one signature field with [SaveSettings.SignatureFields](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/savesettings/signaturefields-property/llms-full.txt) |

The constructors don't take file names, passwords or store locations. Loading the certificate is up to your code, and the certificate is used when the document is saved. If it's invalid or has no private key, `Save` throws a [FilterException](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/filterexception/llms-full.txt) with the [Reason](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/filterexception/reason-property/llms-full.txt) `InvalidCertificate`.

Since .NET 9, `X509CertificateLoader` loads PFX files and data, and the `X509Certificate2` constructors that take a file name or a byte array are obsolete. The samples use `X509CertificateLoader`; in a .NET 8 project, use the constructor with the same arguments.

### Load the Certificate in ASP.NET Core

In a web application, the certificate file and its password come from the configuration, not from the code. The following controller reads both, signs a document and returns the PDF:

```csharp
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Mvc;
using TXTextControl;

[ApiController]
[Route("api/[controller]")]
public class SignedDocumentController : ControllerBase
{
    private readonly IConfiguration _configuration;
    private readonly IWebHostEnvironment _environment;

    public SignedDocumentController(IConfiguration configuration, IWebHostEnvironment environment)
    {
        _configuration = configuration;
        _environment = environment;
    }

    [HttpGet]
    public IActionResult Get()
    {
        string certificatePath = Path.Combine(
            _environment.ContentRootPath,
            _configuration["Signing:CertificatePath"] ?? "certificate.pfx");
        string? password = _configuration["Signing:CertificatePassword"];

        // MachineKeySet: IIS application pool identities may have no loaded user profile
        using X509Certificate2 certificate = X509CertificateLoader.LoadPkcs12FromFile(
            certificatePath,
            password,
            X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);

        byte[] pdf;

        using (ServerTextControl tx = new ServerTextControl())
        {
            tx.Create();
            tx.Text = "This document is digitally signed.";

            SaveSettings saveSettings = new SaveSettings()
            {
                DigitalSignature = new DigitalSignature(certificate, null)
            };

            tx.Save(out pdf, BinaryStreamType.AdobePDF, saveSettings);
        }

        return File(pdf, "application/pdf", "signed.pdf");
    }
}
```

The code works as follows:

- `ContentRootPath` resolves the file relative to the application folder, which isn't always the working directory of the process, for example under IIS.
- `X509KeyStorageFlags.MachineKeySet` stores the key in the machine key set, because an IIS application pool identity doesn't necessarily have a loaded user profile. `Exportable` is the flag that the signing samples of Text Control use.
- The configuration keys `Signing:CertificatePath` and `Signing:CertificatePassword` are names of your choice. Keep passwords in user secrets, environment variables or another secret store, and don't commit PFX files or passwords to source control. A different certificate per environment, for testing, staging and production, then needs no code changes.

### Load the Certificate From the Windows Certificate Store

On Windows, certificates are often installed in the certificate store instead of being kept as files. The following program finds a certificate by its thumbprint and signs with it:

```csharp
using System.Security.Cryptography.X509Certificates;
using TXTextControl;

// The thumbprint of the signing certificate, for example from the configuration
string thumbprint = "6BA35B742656FB2EC48B09116ABAE5123082F116";

X509Certificate2? certificate = null;

using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
    store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

    // false: also find certificates without a trusted chain, such as test certificates
    X509Certificate2Collection found = store.Certificates.Find(
        X509FindType.FindByThumbprint, thumbprint, false);

    foreach (X509Certificate2 candidate in found)
    {
        if (candidate.HasPrivateKey)
        {
            certificate = candidate;
            break;
        }
    }
}

if (certificate == null)
{
    Console.WriteLine("No certificate with a private key was found.");
    return;
}

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Text = "This document is digitally signed.";

    SaveSettings saveSettings = new SaveSettings()
    {
        DigitalSignature = new DigitalSignature(certificate, null)
    };

    tx.Save("signed.pdf", StreamType.AdobePDF, saveSettings);
}
```

The code works as follows:

- `X509Store` opens the personal store of the current user. A service or an IIS application uses `StoreLocation.LocalMachine`, and its identity needs access to the private key.
- `Find` with `X509FindType.FindByThumbprint` selects the certificate. With `true` as the last parameter, it returns only valid certificates.
- `HasPrivateKey` makes sure the certificate can sign.

The machine store `LocalMachine\My` isn't available on Linux, where certificates are loaded from files or a secret store instead.

### Load the Certificate From Azure Key Vault

Azure Key Vault stores certificates together with their private keys. The certificate itself, in the `Cer` property of a Key Vault certificate, contains only the public key and can't sign. The complete certificate with the key is stored in the secret of the same name. In the Azure portal, the certificates of a key vault are listed under **Objects** > **Certificates**, and their secrets under **Objects** > **Secrets**:

![The Certificates page of the key vault txtestvault in the Azure portal, listing the enabled certificate txselfcert with its thumbprint and expiration date](https://docs.textcontrol.com/assets/img/tx-text-control-dotnet-server/key-vault-certificates.0e286f24.webp)

The following program reads the secret with the `Azure.Identity` and `Azure.Security.KeyVault.Secrets` packages:

```csharp
using System.Security.Cryptography.X509Certificates;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using TXTextControl;

string keyVaultUrl = "https://txtestvault.vault.azure.net/";
string certificateName = "txselfcert";

// The secret with the name of the certificate holds the certificate and its private key
SecretClient client = new SecretClient(new Uri(keyVaultUrl), new DefaultAzureCredential());
var secret = await client.GetSecretAsync(certificateName);

byte[] pfxData = Convert.FromBase64String(secret.Value.Value);

using X509Certificate2 certificate = X509CertificateLoader.LoadPkcs12(
    pfxData,
    null,
    X509KeyStorageFlags.Exportable);

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Text = "This document is digitally signed.";

    SaveSettings saveSettings = new SaveSettings()
    {
        DigitalSignature = new DigitalSignature(certificate, null)
    };

    tx.Save("signed.pdf", StreamType.AdobePDF, saveSettings);
}
```

The identity that the application authenticates with through `DefaultAzureCredential` needs permission to read the secret, and the policy of the Key Vault certificate has to mark its key as exportable. Otherwise, the private key isn't part of the secret. The open-source e-signature application [SignFabric](https://github.com/TextControl/SignFabric), built with TX Text Control, takes its signing certificates from PFX files or from Azure Key Vault.

### Check a Certificate Before Signing

A check before signing turns configuration mistakes into clear messages. The following program prints the certificate details, refuses certificates without a private key or with an expired validity period and handles the `InvalidCertificate` error:

```csharp
using System.Security.Cryptography.X509Certificates;
using TXTextControl;

X509Certificate2 certificate = X509CertificateLoader.LoadPkcs12FromFile(
    "certificate.pfx", "123", X509KeyStorageFlags.Exportable);

Console.WriteLine($"Subject: {certificate.Subject}");
Console.WriteLine($"Issuer: {certificate.Issuer}");
Console.WriteLine($"Valid until: {certificate.NotAfter}");

if (!certificate.HasPrivateKey)
{
    Console.WriteLine("The certificate has no private key and can't sign.");
    return;
}

if (certificate.NotAfter < DateTime.Now)
{
    Console.WriteLine("The certificate has expired.");
    return;
}

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Text = "This document is digitally signed.";

    SaveSettings saveSettings = new SaveSettings()
    {
        DigitalSignature = new DigitalSignature(certificate, null)
    };

    try
    {
        tx.Save("signed.pdf", StreamType.AdobePDF, saveSettings);
    }
    catch (FilterException exception) when (exception.Reason == FilterException.FilterError.InvalidCertificate)
    {
        Console.WriteLine("The certificate is invalid or has no private key.");
    }
}
```

`Subject` names the owner of the certificate and `Issuer` the certificate authority that issued it; for a self-signed certificate, both are the same. Adobe Acrobat Reader shows the same details for each signature in its **Signatures** panel.

### Next Steps

- [Self-Signed Certificates](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/self-signed/llms-full.txt): create certificates for tests.
- [Signing PDF Documents](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/signing/llms-full.txt): sign generated documents with the certificate.
- [RFC 3161 Timestamps](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/timestamps/llms-full.txt): keep signatures verifiable after the certificate expires.
- [Digital Signatures](https://docs.textcontrol.com/tx-text-control-dotnet-server/security/signatures/llms-full.txt) in Security: the security aspects of digital signatures.

---

## Nearby

- **Previous:** [Signing PDF Documents](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/signing/llms-full.txt)
- **Next:** [Signature Fields](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/signature-fields/llms-full.txt)
