# Signing PDF Documents

> Sign the TextControl document or a merged invoice as PDF in Windows Forms with DigitalSignature, handle FilterException and verify the signatures in code.

- **Product:** TX Text Control .NET for Windows Forms
- **Section:** Documentation
- **Kind:** Topic
- **Path:** TX Text Control .NET for Windows Forms > Digital Signatures > Signing PDF Documents
- **4504 words**
- **Web URL:** https://docs.textcontrol.com/tx-text-control-dotnet-winforms/digital-signatures/signing/
- **LLMs URL:** https://docs.textcontrol.com/tx-text-control-dotnet-winforms/digital-signatures/signing/llms.txt
- **LLMs-Full URL:** https://docs.textcontrol.com/tx-text-control-dotnet-winforms/digital-signatures/signing/llms-full.txt

---

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](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/getting-started/pdf/sign/#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](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/digital-signatures/signature-fields/llms-full.txt).

### How Signing Works in TX Text Control

Three things come together:

- **A [DigitalSignature](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/reference/txtextcontrol/digitalsignature/llms-full.txt)** holds the certificate. Its [constructor](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/reference/txtextcontrol/digitalsignature/constructor/llms-full.txt) takes an `X509Certificate2` with 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](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/reference/txtextcontrol/savesettings/digitalsignature-property/llms-full.txt)** 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.AdobePDF` or `StreamType.AdobePDFA`, or the same values of `BinaryStreamType`. For other formats, the property has no meaning.

These overloads of [TextControl.Save](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/reference/txtextcontrol/textcontrol/save-method/llms-full.txt) take `SaveSettings` and can save a signed document. [ServerTextControl](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/reference/txtextcontrol/servertextcontrol/llms-full.txt) 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:

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

```csharp
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 `StreamType` values combined with `|` are the file types the dialog box offers.
- The same `SaveSettings` carry the document properties, such as [DocumentTitle](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/reference/txtextcontrol/savesettings/documenttitle-property/llms-full.txt), and the signature.
- `Save` returns `DialogResult.OK` when the user saved the file. [SavedFile](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/reference/txtextcontrol/savesettings/savedfile-property/llms-full.txt) 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](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/getting-started/mail-merge/fields/llms-full.txt) and saves a signed PDF/A document:

```csharp
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](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/reference/txtextcontrol-documentserver/mailmerge/llms-full.txt) replaces the merge fields of the template with the values of the JSON data, as [Merge Data into Fields](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/getting-started/mail-merge/fields/llms-full.txt) explains.
- [CreatorApplication](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/reference/txtextcontrol/savesettings/creatorapplication-property/llms-full.txt) and `DocumentTitle` are stored in the document properties of the PDF.
- `StreamType.AdobePDFA` creates a PDF/A document for the archive, as [PDF/A](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/pdf/pdf-a/llms-full.txt) describes. The signature works the same way with `AdobePDF`.

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](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/reference/txtextcontrol-documentserver/mailmerge/savedocument-method/llms-full.txt) and [SaveDocumentToMemory](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/reference/txtextcontrol-documentserver/mailmerge/savedocumenttomemory-method/llms-full.txt), 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](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/reference/txtextcontrol/filterexception/llms-full.txt) when the document is saved, and its [Reason](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/reference/txtextcontrol/filterexception/reason-property/llms-full.txt) 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:

```csharp
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](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/reference/txtextcontrol/filterexception/filtererror-enumeration/llms-full.txt) enumeration. To catch a wrong certificate before anything is saved, check it first, as [Check a Certificate Before Signing](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/digital-signatures/certificates/#check-a-certificate-before-signing) shows. What to do when no time server answers is described in [Timestamp Configuration](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/digital-signatures/timestamps/#handle-an-unavailable-time-server).

### 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](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/pdf/creation-conversion/#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;`:

```csharp
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.Validate` reads the file, and `Diagnostics.Signatures` holds the results of its signatures. `HasSignatures` and `Count` tell whether and how many signatures there are; `AllSignatureBytesIntact` and `AllCoverWholeDocument` summarize them.
- For each `SignatureDiagnostic`, `SignatureBytesIntact` is the result of the cryptographic check of the signature over the bytes it covers. When it fails, `FailureReason` explains why, and the signer and signing time are empty.
- `CoversWholeDocument` is `false` when the file holds bytes the signature doesn't cover. `SignerSubject` is the subject of the signer's certificate, such as `CN=MyTXCert`, and `SigningTime` the 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:

![Adobe Acrobat with the Signatures panel open for a PDF changed after signing: the signature of MyTXCert is invalid because the document has been altered or corrupted since it was signed.](https://docs.textcontrol.com/assets/img/tx-text-control-dotnet-winforms/signature-invalid-altered.3af6a6b3.webp)

The blog article [Validate Digital Signatures and the Integrity of PDF Documents in C# .NET](https://www.textcontrol.com/blog/2025/11/14/validate-digital-signatures-and-the-integrity-of-pdf-documents-in-csharp-dotnet/) 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](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/pdf/forms/exporting/llms-full.txt) 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](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/pdf/import/llms-full.txt) 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](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/pdf/passwords/llms-full.txt) describes. PDF/A doesn't allow encryption, so save a protected document as `AdobePDF`.

### Next Steps

- [Certificates](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/digital-signatures/certificates/llms-full.txt): let the user choose a certificate from the Windows certificate store.
- [Signature Fields and Signature Appearance](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/digital-signatures/signature-fields/llms-full.txt): show visible signatures in named fields.
- [Timestamp Configuration](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/digital-signatures/timestamps/llms-full.txt): add a trusted signing time.
- The blog article [PDF Security Explained: Passwords, Permissions, Encryption and Digital Signatures in C# .NET](https://www.textcontrol.com/blog/2026/06/08/pdf-security-explained-passwords-permissions-encryption-and-digital-signatures-in-csharp-dotnet/): the protections compared.

---

## Nearby

- **Previous:** [Overview](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/digital-signatures/overview/llms-full.txt)
- **Next:** [Certificates](https://docs.textcontrol.com/tx-text-control-dotnet-winforms/digital-signatures/certificates/llms-full.txt)
