Certificate-Based Signatures
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 in Getting Started signs a first document with a PFX file. Certificates for tests are the subject of Self-Signed Certificates.
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.Exportableand create certificates in the Windows certificate store with an exportable key, as Self-Signed Certificates 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 explains how a timestamp keeps signatures verifiable after the certificate has expired.
How TX Text Control Takes the Certificate
A DigitalSignature represents a certificate for signing a PDF or PDF/A document. It has two constructors and no other members:
| Constructor | Used for |
|---|---|
DigitalSignature(X509Certificate2 x509, string timeServerURL) | Signing the whole document with SaveSettings.DigitalSignature |
DigitalSignature(X509Certificate2 x509, string timeServerURL, string signatureFieldName) | Signing one signature field with SaveSettings.SignatureFields |
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 with the Reason 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:
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Mvc;
using TXTextControl;
[]
[]
public class SignedDocumentController : ControllerBase
{
private readonly IConfiguration _configuration;
private readonly IWebHostEnvironment _environment;
public SignedDocumentController(IConfiguration configuration, IWebHostEnvironment environment)
{
_configuration = configuration;
_environment = environment;
}
[]
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:
ContentRootPathresolves the file relative to the application folder, which isn't always the working directory of the process, for example under IIS.X509KeyStorageFlags.MachineKeySetstores the key in the machine key set, because an IIS application pool identity doesn't necessarily have a loaded user profile.Exportableis the flag that the signing samples of Text Control use.- The configuration keys
Signing:CertificatePathandSigning:CertificatePasswordare 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:
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:
X509Storeopens the personal store of the current user. A service or an IIS application usesStoreLocation.LocalMachine, and its identity needs access to the private key.FindwithX509FindType.FindByThumbprintselects the certificate. Withtrueas the last parameter, it returns only valid certificates.HasPrivateKeymakes 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 following program reads the secret with the Azure.Identity and Azure.Security.KeyVault.Secrets packages:
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, 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:
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: create certificates for tests.
- Signing PDF Documents: sign generated documents with the certificate.
- RFC 3161 Timestamps: keep signatures verifiable after the certificate expires.
- Digital Signatures in Security: the security aspects of digital signatures.