Signing PDF Documents
A signature is added in the last step of saving: TextControl or ServerTextControl signs the PDF while it writes it. Every document your Windows Forms application saves as PDF can therefore leave it signed, whether a user typed it in the editor or your code merged it from a template. The sections below sign the document of the editor through the Save As dialog box, sign a merged invoice in code, sign into memory and handle the errors that signing can raise.
Sign the Whole Document in Getting Started signs a first document with a self-signed test certificate. Signing single signature fields instead of the whole document is the subject of Signature Fields and Signature Appearance.
How Signing Works in TX Text Control
Three things come together:
- A DigitalSignature holds the certificate. Its constructor takes an
X509Certificate2with a private key and the URL of a time server. With an empty string instead of a URL, the signature carries the date and time of the computer. - SaveSettings.DigitalSignature takes the
DigitalSignature. The signature covers the whole document and has no visible mark on the pages. - A PDF format. The signature is created when the document is saved as
StreamType.AdobePDForStreamType.AdobePDFA, or the same values ofBinaryStreamType. For other formats, the property has no meaning.
These overloads of TextControl.Save take SaveSettings and can save a signed document. ServerTextControl has the same overloads without the dialog box:
| Overload | Saves the document to |
|---|---|
Save(streamType, saveSettings) | A file the user chooses in the Save As dialog box |
Save(path, streamType, saveSettings) | A file |
Save(fileStream, streamType, saveSettings) | An open FileStream |
Save(out binaryData, binaryStreamType, saveSettings) | A byte array |
The samples below load certificate.pfx with the password 123, the test certificate that Getting Started creates. X509CertificateLoader is available in .NET 9 and higher; in a .NET 8 or .NET Framework project, use new X509Certificate2("certificate.pfx", "123", X509KeyStorageFlags.Exportable) instead. Form1.cs needs these using directives:
using System.Security.Cryptography.X509Certificates;
using TXTextControl;
using TXTextControl.DocumentServer;
Let the User Save a Signed PDF
The Save As dialog box of TextControl lets the user choose the folder and the file name, and the SaveSettings passed to it sign the file. Add a button named saveSignedPdfButton to the form and double-click it:
private void saveSignedPdfButton_Click(object sender, EventArgs e)
{
// Load the certificate with its private key
X509Certificate2 certificate = X509CertificateLoader.LoadPkcs12FromFile(
"certificate.pfx", "123", X509KeyStorageFlags.Exportable);
SaveSettings saveSettings = new SaveSettings()
{
DocumentTitle = "Signed Document",
DigitalSignature = new DigitalSignature(certificate, "")
};
// The Save As dialog box offers PDF and PDF/A
if (textControl1.Save(StreamType.AdobePDF | StreamType.AdobePDFA, saveSettings) == DialogResult.OK)
{
MessageBox.Show(this, "Saved and signed: " + saveSettings.SavedFile);
}
}
The code works as follows:
- The
StreamTypevalues combined with|are the file types the dialog box offers. - The same
SaveSettingscarry the document properties, such as DocumentTitle, and the signature. SavereturnsDialogResult.OKwhen the user saved the file. SavedFile then holds its path.
Open the file in a PDF reader: the signature is listed in its signature panel, and nothing on the pages marks it.
Sign a Merged Document in Code
Generated documents are the other typical case: a template is merged with data, and the result is signed as it's saved. A ServerTextControl does this in the background, without touching the document in the editor. The following handler merges invoice.json into the template invoice.docx of Merge Data into Fields and saves a signed PDF/A document:
private void signInvoiceButton_Click(object sender, EventArgs e)
{
X509Certificate2 certificate = X509CertificateLoader.LoadPkcs12FromFile(
"certificate.pfx", "123", X509KeyStorageFlags.Exportable);
using (ServerTextControl tx = new ServerTextControl())
{
// Initialize the new instance before using it
tx.Create();
// Import the MS Word merge fields and merge blocks of the template
LoadSettings loadSettings = new LoadSettings
{
ApplicationFieldFormat = ApplicationFieldFormat.MSWord,
LoadSubTextParts = true
};
tx.Load("invoice.docx", StreamType.WordprocessingML, loadSettings);
using (MailMerge mailMerge = new MailMerge { TextComponent = tx })
{
mailMerge.MergeJsonData(File.ReadAllText("invoice.json"));
}
// Sign the merged invoice while it is saved as PDF/A
SaveSettings saveSettings = new SaveSettings
{
DocumentTitle = "Invoice",
CreatorApplication = "Invoice Application",
DigitalSignature = new DigitalSignature(certificate, "")
};
tx.Save("invoice-signed.pdf", StreamType.AdobePDFA, saveSettings);
}
}
The code works as follows:
- MailMerge replaces the merge fields of the template with the values of the JSON data, as Merge Data into Fields explains.
- CreatorApplication and
DocumentTitleare stored in the document properties of the PDF. StreamType.AdobePDFAcreates a PDF/A document for the archive, as PDF/A describes. The signature works the same way withAdobePDF.
To create one signed document per record, save each merged document with the same SaveSettings. One certificate can be passed to any number of DigitalSignature objects. MailMerge also saves the merge result itself with SaveDocument and SaveDocumentToMemory, which take SaveSettings as well.
Sign Into Memory and Handle Errors
To upload a signed document, store it in a database or attach it to an e-mail, save it into a byte array instead of a file. Signing can fail for two reasons that concern the signature itself. Both throw a FilterException when the document is saved, and its Reason tells them apart:
| Reason | Meaning |
|---|---|
InvalidCertificate | The certificate is invalid or has no private key. |
InvalidTimeServer | The time server doesn't exist or returns invalid data. |
The following method signs the document of the editor with a trusted timestamp and returns the PDF, or null after it has shown the error:
private byte[] SignDocument(X509Certificate2 certificate)
{
SaveSettings saveSettings = new SaveSettings()
{
DigitalSignature = new DigitalSignature(certificate, "http://timestamp.digicert.com")
};
try
{
// Sign the document of the editor into a byte array
textControl1.Save(out byte[] pdf, BinaryStreamType.AdobePDF, saveSettings);
return pdf;
}
catch (FilterException exception) when (exception.Reason == FilterException.FilterError.InvalidCertificate)
{
MessageBox.Show(this, "The certificate is invalid or has no private key.");
}
catch (FilterException exception) when (exception.Reason == FilterException.FilterError.InvalidTimeServer)
{
MessageBox.Show(this, "The time server doesn't exist or returned invalid data.");
}
return null;
}
The values of Reason are listed in the FilterException.FilterError enumeration. To catch a wrong certificate before anything is saved, check it first, as Check a Certificate Before Signing shows. What to do when no time server answers is described in Timestamp Configuration.
Verify the Signatures in Code
TX Text Control creates signatures but doesn't verify them. To check the signatures of a PDF that your application receives or archives, use the TXTextControl.PDF.Validation package from nuget.org. It targets .NET 10, as Validate the Accessible PDF describes with its installation and license. Besides its PDF/UA report, it lists every signature of a PDF with the result of a cryptographic check.
The following handler lets the user choose a PDF and shows, for each signature, whether the signed bytes are unchanged. Form1.cs needs using System.Text;, using TXTextControl.PDF.Validation; and using TXTextControl.PDF.Validation.Model;:
private void verifySignaturesButton_Click(object sender, EventArgs e)
{
using (OpenFileDialog dialog = new OpenFileDialog() { Filter = "PDF documents (*.pdf)|*.pdf" })
{
if (dialog.ShowDialog(this) != DialogResult.OK)
{
return;
}
SignatureDiagnostics signatures = PdfUaValidator.Validate(dialog.FileName).Diagnostics.Signatures;
if (!signatures.HasSignatures)
{
MessageBox.Show(this, "The document isn't signed.", "Signatures");
return;
}
StringBuilder result = new StringBuilder();
foreach (SignatureDiagnostic signature in signatures.Items)
{
if (!signature.SignatureBytesIntact)
{
result.AppendLine("Invalid signature: " + signature.FailureReason);
}
else if (!signature.CoversWholeDocument)
{
result.AppendLine($"{signature.SignerSubject}, {signature.SigningTime}: intact, but the file was extended after it");
}
else
{
result.AppendLine($"{signature.SignerSubject}, {signature.SigningTime}: intact, the file is unchanged since signing");
}
}
MessageBox.Show(this, result.ToString(), "Signatures");
}
}
The code works as follows:
PdfUaValidator.Validatereads the file, andDiagnostics.Signaturesholds the results of its signatures.HasSignaturesandCounttell whether and how many signatures there are;AllSignatureBytesIntactandAllCoverWholeDocumentsummarize them.- For each
SignatureDiagnostic,SignatureBytesIntactis the result of the cryptographic check of the signature over the bytes it covers. When it fails,FailureReasonexplains why, and the signer and signing time are empty. CoversWholeDocumentisfalsewhen the file holds bytes the signature doesn't cover.SignerSubjectis the subject of the signer's certificate, such asCN=MyTXCert, andSigningTimethe signing time stored in the signature.
The package checks the integrity of each signature, not the certificate: it doesn't say whether the certificate was trusted, valid or revoked. For that, open the document in a validator such as Adobe Acrobat Reader. After a change to a signed PDF, Acrobat reports the signature as invalid and the document as altered since it was signed:

The blog article Validate Digital Signatures and the Integrity of PDF Documents in C# .NET changes a signed file on purpose and shows both results.
After the Document Is Signed
- Any change invalidates the signature. Keep signed documents as they are: after a change to the file, PDF readers no longer confirm that the document is unchanged since it was signed.
- Finish the content before signing. Fill in form fields, and flatten them if they must not change, before the document is saved, so that the signature covers the final values. Exporting Interactive and Flattened Forms describes the options.
- Sign the documents you create. A PDF created elsewhere is converted into an editable document when TX Text Control loads it, and saving creates a new PDF from that conversion, as PDF Import and Its Limitations explains. Keep documents in an editable format such as DOCX or the TX Text Control format until they're final, and sign when you create the PDF.
- Combine a signature with a password if the content is confidential. A signature doesn't hide the content. Passwords and permissions are also properties of
SaveSettings, as Passwords and Access Permissions describes. PDF/A doesn't allow encryption, so save a protected document asAdobePDF.
Next Steps
- Certificates: let the user choose a certificate from the Windows certificate store.
- Signature Fields and Signature Appearance: show visible signatures in named fields.
- Timestamp Configuration: add a trusted signing time.
- The blog article PDF Security Explained: Passwords, Permissions, Encryption and Digital Signatures in C# .NET: the protections compared.