TX Text Control .NET for WPF

Multiple Signatures

Topic

An agreement between a discloser and a vendor needs two signatures that can be told apart and checked one by one. In TX Text Control, each of them is a signature field with its own name, and each field is signed with its own DigitalSignature: its own certificate, its own timestamp, its own entry in the signature panel of a PDF reader. All of them are applied together, in the save that turns the finished document into a PDF.

The fields themselves, with their images and signer data, are described in Signature Fields and Signature Appearance. The examples use an agreement whose signature fields are named sign_discloser, sign_vendor and init_vendor; users insert such fields as Signature Fields in Document Editor describes.

How Several Signatures Are Written

  • An array of signatures. SaveSettings.SignatureFields takes one DigitalSignature per field. Each has its own certificate and time server URL, so one field can carry a trusted timestamp and another the computer's time.
  • A single save. TX Text Control creates signatures only when it saves a document as PDF. A PDF that it loads becomes an editable document, as PDF Import and Its Limitations explains, so signatures aren't added to an existing PDF one by one. The document collects the signature images of its signers in an editable format, and the last save signs all fields.
  • Separate entries. PDF readers list every signature with the name of its field and a validation status of its own.
  • Fields, not the document signature. SaveSettings.DigitalSignature signs once, with one certificate. Several signatures need signature fields.

Sign Several Fields in One Save

The following handler signs the agreement in the editor with two certificates. The discloser's field gets a trusted timestamp; the vendor's signature and initials get the date and time of the computer. It needs using System.Security.Cryptography.X509Certificates;, using System.Windows; and using TXTextControl; in MainWindow.xaml.cs.

private void signAgreementButton_Click(object sender, RoutedEventArgs e)
{
    // The discloser's and the vendor's certificates
    X509Certificate2 discloserCertificate = X509CertificateLoader.LoadPkcs12FromFile(
        "certificate.pfx", "123", X509KeyStorageFlags.Exportable);
    X509Certificate2 vendorCertificate = X509CertificateLoader.LoadPkcs12FromFile(
        "vendor.pfx", "123", X509KeyStorageFlags.Exportable);

    string[] names = { "sign_discloser", "sign_vendor", "init_vendor" };

    foreach (string name in names)
    {
        SignatureField field = textControl1.SignatureFields.GetItem(name);

        if (field == null)
        {
            MessageBox.Show(this, $"The document has no signature field named {name}.");
            return;
        }

        // The image shown in the field (filter index 4 is PNG)
        field.Image = new SignatureImage($"{name}.png", 4);
    }

    SaveSettings saveSettings = new SaveSettings()
    {
        SignatureFields = new DigitalSignature[]
        {
            // With a trusted timestamp
            new DigitalSignature(discloserCertificate, "http://timestamp.digicert.com", "sign_discloser"),

            // With the date and time of this computer
            new DigitalSignature(vendorCertificate, "", "sign_vendor"),
            new DigitalSignature(vendorCertificate, "", "init_vendor")
        }
    };

    textControl1.Save("nda-signed.pdf", StreamType.AdobePDFA, saveSettings);
}

The code works as follows:

  • vendor.pfx is a second test certificate, made the same way as certificate.pfx in Create a Self-Signed Certificate for Testing. The computer that signs needs the private key of every certificate it uses.
  • SignatureFieldCollection.GetItem returns null for a name the document doesn't contain, so the handler stops before it saves an incomplete agreement. SignatureField.Image sets the image of each field from a PNG file named after it.
  • Each DigitalSignature names its field in the third parameter and takes a time server URL or an empty string in the second.
  • The Save call creates one PDF/A document with three signatures.

X509CertificateLoader needs .NET 9 or higher. The constructor to use with .NET 8 and the .NET Framework is shown in Load the Certificate From a PFX File.

Collect Signatures From Several People

When the parties sign one after the other, each with the same WPF application on their own computer, the document travels in the TX Text Control format until the organization certifies it:

  1. Prepare the document with one named field and the signer data per signer, and save it in the TX Text Control format, for example as nda.tx.

  2. Let each signer add their signature. The signer opens the document, puts the image of their signature into their field, as Add a Signature Image shows, and saves the document for the next signer:

    private void saveForNextSignerButton_Click(object sender, RoutedEventArgs e)
    {
        // Keep the signatures of this signer in the TX Text Control format
        textControl1.Save("nda.tx", StreamType.InternalUnicodeFormat);
    }
    
  3. Certify the document once every field has its image, by signing all fields in one save.

The handler for the last step signs every named field with the organization's certificate and a trusted timestamp. While a field has no image, it names the missing fields instead of creating the PDF:

private void certifyButton_Click(object sender, RoutedEventArgs e)
{
    X509Certificate2 certificate = X509CertificateLoader.LoadPkcs12FromFile(
        "certificate.pfx", "123", X509KeyStorageFlags.Exportable);

    List<string> missing = new List<string>();
    List<DigitalSignature> signatures = new List<DigitalSignature>();

    foreach (SignatureField field in textControl1.SignatureFields)
    {
        if (string.IsNullOrEmpty(field.Name))
        {
            continue;
        }

        // A field without an image hasn't been signed by its signer yet
        if (field.Image == null)
        {
            missing.Add(field.Name);
            continue;
        }

        signatures.Add(new DigitalSignature(certificate, "http://timestamp.digicert.com", field.Name));
    }

    if (missing.Count > 0)
    {
        MessageBox.Show(this, "Not signed yet: " + string.Join(", ", missing));
        return;
    }

    SaveSettings saveSettings = new SaveSettings()
    {
        SignatureFields = signatures.ToArray()
    };

    textControl1.Save("nda-signed.pdf", StreamType.AdobePDFA, saveSettings);
}

The code works as follows:

  • Image is null for a field without a signature image, so the loop knows which signers are still missing. List<T> needs using System.Collections.Generic;.
  • Every complete field gets a DigitalSignature with the organization's certificate. If each signer's certificate is available on the certifying computer, pass that certificate for their field instead, as in the previous section.
  • Only a complete document becomes a PDF/A document. Until then it stays in the TX Text Control format, where it can still be changed.

Between two signers, the images in the fields prove nothing: until the certificates are applied, anyone who can open and save nda.tx can change them. Store the file where only the signers can change it.

Check Documents With Several Signatures

In Adobe Acrobat Reader, the Signatures panel shows each signature of the PDF with the name of its field, and a click on a signed field opens its validation status. How field signatures and a document signature look in the panel is compared in the blog article Understanding the Differences: Signing a PDF Document vs. Signing Signature Fields in a PDF in C#.

Next Steps