# Signing PDF Documents

> Sign the whole PDF or PDF/A document with DigitalSignature and SaveSettings: merged documents, byte arrays, ASP.NET Core, errors and what happens after signing.

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

---

Signing is the last step of saving: `ServerTextControl` applies the signature while it writes the PDF, so every document that your code creates, converts or merges can leave the application signed. The signing options, a signed merge result, an ASP.NET Core action that returns a signed PDF and the errors to handle are covered in the sections below.

A first signed document, with a self-signed test certificate, is created in [Sign the Whole Document](https://docs.textcontrol.com/tx-text-control-dotnet-server/getting-started/pdf/sign/#sign-the-whole-document) in Getting Started. Signing single signature fields instead of the whole document is the subject of [Signature Fields](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/signature-fields/llms-full.txt).

### How Signing Works in TX Text Control

Three things come together when a document is signed:

- **A [DigitalSignature](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/digitalsignature/llms-full.txt)** holds the certificate. Its [constructor](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/digitalsignature/constructor/llms-full.txt) takes an `X509Certificate2` with a private key and the URL of a time server. With `null` or an empty string instead of a URL, the current date and time of the computer are used; with a URL, the signature gets a trusted timestamp, as [RFC 3161 Timestamps](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/timestamps/llms-full.txt) describes.
- **[SaveSettings.DigitalSignature](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/savesettings/digitalsignature-property/llms-full.txt)** takes the `DigitalSignature`. The signature covers the whole document and isn't visible on the pages; PDF readers list it in their signature panel.
- **A PDF format.** The signature is created when the document is saved with `StreamType.AdobePDF` or `StreamType.AdobePDFA`, or the same values of `BinaryStreamType`. For other formats, such as DOCX, the property has no meaning.

Open a signed document in Adobe Acrobat Reader to see the result. The bar above the page states whether all signatures are valid, and the **Signatures** panel lists the signature with the name of its certificate. Because a whole-document signature has no visible field, the panel names its field as an invisible signature, and without a time server it notes that the signing time comes from the clock of the signing computer:

![Adobe Acrobat Reader with the Signatures panel of a PDF signed by TX Text Control: the signature is valid, the document hasn't been modified, and the field is Signature1 (invisible signature)](https://docs.textcontrol.com/assets/img/tx-text-control-dotnet-server/acrobat-signature-valid.5c2a0c2d.webp)

These methods take `SaveSettings` and can save a signed document:

| Method | Saves the document to |
|---|---|
| [ServerTextControl.Save](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/servertextcontrol/save-method/llms-full.txt) with a path | A file |
| `ServerTextControl.Save` with a `FileStream` | An open file stream |
| `ServerTextControl.Save` with `out byte[]` | A byte array |
| [MailMerge.SaveDocument](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol-documentserver/mailmerge/savedocument-method/llms-full.txt), [MailMerge.SaveDocumentToMemory](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol-documentserver/mailmerge/savedocumenttomemory-method/llms-full.txt) | A file or a byte array, after a merge |

Where certificates come from and what they need is described in [Certificate-Based Signatures](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/certificate-based/llms-full.txt).

### Sign a Merged Document

Generated documents are the typical case: a template is merged with data, and the result is signed as it's saved. The following program merges *data.json* into the invoice template *template.docx* of [Simple Mail Merge](https://docs.textcontrol.com/tx-text-control-dotnet-server/getting-started/mail-merge/simple/llms-full.txt) and saves a signed PDF/A document:

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

// Load the certificate including its private key
X509Certificate2 certificate = X509CertificateLoader.LoadPkcs12FromFile(
    "certificate.pfx",
    "123",
    X509KeyStorageFlags.Exportable);

string jsonData = File.ReadAllText("data.json");

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();

    // Load the template with its merge fields and merge blocks
    LoadSettings loadSettings = new LoadSettings()
    {
        ApplicationFieldFormat = ApplicationFieldFormat.MSWord,
        LoadSubTextParts = true
    };

    tx.Load("template.docx", StreamType.WordprocessingML, loadSettings);

    using (MailMerge mailMerge = new MailMerge { TextComponent = tx })
    {
        mailMerge.MergeJsonData(jsonData);
    }

    // Sign the complete document while it's saved as PDF/A
    SaveSettings saveSettings = new SaveSettings()
    {
        DocumentTitle = "Invoice INV-2025-001",
        CreatorApplication = "Invoice Service",
        DigitalSignature = new DigitalSignature(certificate, null)
    };

    tx.Save("invoice-signed.pdf", StreamType.AdobePDFA, saveSettings);
}
```

The code works as follows:

- The merge replaces the merge fields of the template with the data, as [Simple Mail Merge](https://docs.textcontrol.com/tx-text-control-dotnet-server/getting-started/mail-merge/simple/llms-full.txt) explains.
- The same `SaveSettings` carry the document properties, [DocumentTitle](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/savesettings/documenttitle-property/llms-full.txt) and [CreatorApplication](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/savesettings/creatorapplication-property/llms-full.txt), and the signature.
- `StreamType.AdobePDFA` creates a PDF/A document for the archive; [PDF/A, PDF/UA and Digital Signatures](https://docs.textcontrol.com/tx-text-control-dotnet-server/pdf/pdf-a/#pdf-a-pdf-ua-and-digital-signatures) describes what a signature adds to it.
- `X509CertificateLoader` requires .NET 9 or later. In a .NET 8 project, use `new X509Certificate2("certificate.pfx", "123", X509KeyStorageFlags.Exportable)`.

To create one signed document per record, pass the same `SaveSettings` to `Save` in the loop of [Create One Document or Separate Documents](https://docs.textcontrol.com/tx-text-control-dotnet-server/mail-merge/overview/#create-one-document-or-separate-documents). The same certificate can be passed to several `DigitalSignature` objects.

### Return a Signed PDF From ASP.NET Core

A web application signs documents in memory and returns them in the response. The following controller action receives a DOCX file, converts it and returns it as a signed PDF:

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

public class SignedPdfController : Controller
{
    [HttpPost]
    public IActionResult Sign(IFormFile document)
    {
        // Copy the uploaded DOCX file into a byte array
        byte[] data;

        using (MemoryStream memoryStream = new MemoryStream())
        {
            document.CopyTo(memoryStream);
            data = memoryStream.ToArray();
        }

        using (X509Certificate2 certificate = X509CertificateLoader.LoadPkcs12FromFile(
            Path.Combine(AppContext.BaseDirectory, "certificate.pfx"),
            "123",
            X509KeyStorageFlags.Exportable))
        using (ServerTextControl tx = new ServerTextControl())
        {
            tx.Create();

            try
            {
                tx.Load(data, BinaryStreamType.WordprocessingML);
            }
            catch (FilterException exception)
            {
                return BadRequest($"The document could not be loaded: {exception.Reason}");
            }

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

            // Sign the document while it's saved into a byte array
            byte[] pdf;
            tx.Save(out pdf, BinaryStreamType.AdobePDF, saveSettings);

            return File(pdf, "application/pdf",
                Path.GetFileNameWithoutExtension(document.FileName) + ".pdf");
        }
    }
}
```

The code works as follows:

- The upload is copied into a byte array, because `Load` has no overload for the stream of an upload, as [Load Uploaded Documents in ASP.NET Core](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/loading/#load-uploaded-documents-in-asp-net-core) explains.
- `AppContext.BaseDirectory` finds *certificate.pfx* in the output folder of the application. The certificate is loaded for the request and disposed afterward. In production, the file and the password come from the configuration, as [Load the Certificate in ASP.NET Core](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/certificate-based/#load-the-certificate-in-asp-net-core) shows.
- `Save` with `out byte[]` and `BinaryStreamType.AdobePDF` signs the document in memory, and `File` returns it.

### Handle Certificate and Time Server Errors

Signing can fail for two reasons that concern the signature itself. Both throw a [FilterException](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/filterexception/llms-full.txt) when the document is saved, and its [Reason](https://docs.textcontrol.com/tx-text-control-dotnet-server/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 program signs a document with a timestamp and reports both errors:

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

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

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

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Text = "This document is signed with a trusted timestamp.";

    SaveSettings saveSettings = new SaveSettings()
    {
        DigitalSignature = new DigitalSignature(certificate, "http://timestamp.digicert.com")
    };

    try
    {
        tx.Save("signed.pdf", StreamType.AdobePDF, saveSettings);
        Console.WriteLine("signed.pdf created");
    }
    catch (FilterException exception) when (exception.Reason == FilterException.FilterError.InvalidCertificate)
    {
        Console.WriteLine("The certificate is invalid or has no private key.");
    }
    catch (FilterException exception) when (exception.Reason == FilterException.FilterError.InvalidTimeServer)
    {
        Console.WriteLine("The time server doesn't exist or returned invalid data.");
    }
}
```

Checking `HasPrivateKey` before the document is created turns a wrong certificate file into a clear message early. [Check a Certificate Before Signing](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/certificate-based/#check-a-certificate-before-signing) adds a check of the validity period.

### Combine Signatures With Passwords

A signature shows whether a document was changed and who signed it, but it doesn't hide the content, as [What a Digital Signature Proves](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/overview/#what-a-digital-signature-proves) explains. To encrypt a signed document as well, set the passwords and permissions of [Protect a PDF With Passwords and Permissions](https://docs.textcontrol.com/tx-text-control-dotnet-server/pdf/creation/#protect-a-pdf-with-passwords-and-permissions) in the same `SaveSettings` and save with `AdobePDF`, because PDF/A doesn't allow encryption. 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/) compares the protections.

### After the Document Is Signed

- **Any change invalidates the signature.** Archive signed documents without modifying them. Even a change to the metadata of the file makes validators report the signature as invalid, as [Signature Validation](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/validation/llms-full.txt) shows.
- **Finish the content before signing.** Fill in and flatten form fields before the document is saved, so that the signature covers the final values, as [Flatten, Then Sign](https://docs.textcontrol.com/tx-text-control-dotnet-server/pdf/forms/flatten/#flatten-then-sign) describes.
- **Sign the documents you generate.** A PDF created elsewhere is rebuilt when `ServerTextControl` loads it, and saving it creates a new PDF from that reconstruction, as [Load and Modify a PDF](https://docs.textcontrol.com/tx-text-control-dotnet-server/getting-started/pdf/load-modify/llms-full.txt) explains. Keep documents in an editable format such as DOCX until they're final, and sign when you create the PDF.

### Next Steps

- [Signature Fields](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/signature-fields/llms-full.txt): show visible signatures in named fields.
- [RFC 3161 Timestamps](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/timestamps/llms-full.txt): add a trusted signing time.
- [Certificate-Based Signatures](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/certificate-based/llms-full.txt): load certificates from the store or Azure Key Vault.
- [Signature Validation](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/validation/llms-full.txt): check the signed documents in code.

---

## Nearby

- **Previous:** [Overview](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/overview/llms-full.txt)
- **Next:** [Certificate-Based Signatures](https://docs.textcontrol.com/tx-text-control-dotnet-server/digital-signatures/certificate-based/llms-full.txt)
