Understanding PAdES: A Complete Guide to Secure Digital Signatures

In the digital age, securing documents is more critical than ever. One way to ensure the authenticity and integrity of digital documents is through digital signatures. Among various standards, PAdES (PDF Advanced Electronic Signatures) stands out for securing PDF documents. In this guide, we will delve into what PAdES is, how it works, and how you can implement it using UniDoc.

What is PAdES?

What is PAdES?

PAdES stands for PDF Advanced Electronic Signatures. It is a set of standards that extends PDF digital signatures, making them compliant with European regulations for electronic signatures. PAdES enhances the security and longevity of digital signatures, ensuring they remain valid over long periods.

Key Benefits of PAdES

  1. Long-Term Validity: PAdES signatures can be validated long after the signing, ensuring document integrity over time.
  2. Compliance: PAdES is compliant with European eIDAS regulation, making it a robust choice for legal documents.
  3. Enhanced Security: PAdES incorporates timestamping, which provides proof of the signing time, further enhancing security.

How PAdES Works

PAdES works by embedding digital signatures into PDF documents. These signatures are created using a combination of private keys, public certificates, and timestamping services. The process ensures that any changes to the document after signing can be detected, and the signature can be validated even years later.

Types of PAdES Signatures:

  • PAdES-B: Basic signature providing document integrity.
  • PAdES-T: Includes a trusted timestamp.
  • PAdES-LT: Adds validation data to the document, ensuring long-term validation.
  • PAdES-LTA: Long-Term Archiving, which includes periodic timestamping to keep the document valid over time.

Types of PAdES Signatures

Implementing PAdES with UniDoc

UniDoc provides a comprehensive solution for working with PDF documents, including implementing PAdES signatures. Below, we will walk through the steps to create a PAdES B-LTA compatible digital signature using UniDoc.

Step-by-Step Guide

Step 1: Set Up Your Environment

Before you begin, you need to configure your environment variables with your UniDoc license key. This ensures you have access to all the necessary features.

For Linux/Mac:

export UNIDOC_LICENSE_API_KEY=YOUR_API_KEY_HERE

For Windows:

set UNIDOC_LICENSE_API_KEY=YOUR_API_KEY_HERE

Step 2: Install UniPDF Library

Make sure you have unipdf library installed in your Go project. If not, you can install it using:

go get -u github.com/unidoc/unipdf/v3

Step 3: Example Code Overview

The following code demonstrates how to create a PAdES B-LTA compatible digital signature for a PDF file. We’ll break down the code into understandable sections.

package main

import (
    "bytes"
    "crypto"
    "crypto/rsa"
    "crypto/x509"
    "encoding/pem"
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "time"

    "golang.org/x/crypto/pkcs12"
    "github.com/unidoc/unipdf/v3/annotator"
    "github.com/unidoc/unipdf/v3/common/license"
    "github.com/unidoc/unipdf/v3/core"
    "github.com/unidoc/unipdf/v3/model"
    "github.com/unidoc/unipdf/v3/model/sighandler"
)

func init() {
    err := license.SetMeteredKey(os.Getenv(`UNIDOC_LICENSE_API_KEY`))
    if err != nil {
        panic(err)
    }
}

const usagef = "Usage: %s PFX_FILE PASSWORD PEM_FILE INPUT_PDF_PATH OUTPUT_PDF_PATH\n"

func main() {
    args := os.Args
    if len(args) < 6 {
        fmt.Printf(usagef, os.Args[0])
        return
    }

    pfxPath := args[1]
    password := args[2]
    pemPath := args[3]
    inputPath := args[4]
    outputPath := args[5]

    // Load private key and certificate from PFX file
    pfxData, err := ioutil.ReadFile(pfxPath)
    if err != nil {
        log.Fatalf("Error reading PFX file: %v\n", err)
    }

    priv, cert, err := pkcs12.Decode(pfxData, password)
    if err != nil {
        log.Fatalf("Error decoding PFX file: %v\n", err)
    }

    // Load CA Certificate from PEM file
    caCertData, err := ioutil.ReadFile(pemPath)
    if err != nil {
        log.Fatalf("Error reading PEM file: %v\n", err)
    }

    certDERBlock, _ := pem.Decode(caCertData)

    cacert, err := x509.ParseCertificate(certDERBlock.Bytes)
    if err != nil {
        log.Fatalf("Error parsing CA Certificate: %v\n", err)
    }

    // Create PDF reader
    file, err := os.Open(inputPath)
    if err != nil {
        log.Fatalf("Error opening input PDF: %v\n", err)
    }

    defer file.Close()

    reader, err := model.NewPdfReader(file)
    if err != nil {
        log.Fatalf("Error creating PDF reader: %v\n", err)
    }

    // Create PDF appender
    appender, err := model.NewPdfAppender(reader)
    if err != nil {
        log.Fatalf("Error creating PDF appender: %v\n", err)
    }

    // Timestamp server URL
    timestampServerURL := "https://freetsa.org/tsr"// Create signature handler

    handler, err := sighandler.NewEtsiPAdESLevelLT(priv.(*rsa.PrivateKey), cert, cacert, timestampServerURL, appender)
    if err != nil {
        log.Fatalf("Error creating signature handler: %v\n", err)
    }

    // Create PDF signature
    signature := model.NewPdfSignature(handler)
    signature.SetName("PAdES B-LTA Signature")
    signature.SetReason("Testing PAdES signature")
    signature.SetDate(time.Now(), "")

    err = signature.Initialize()
    if err != nil {
        log.Fatalf("Error initializing signature: %v\n", err)
    }

    // Create signature field
    opts := annotator.NewSignatureFieldOpts()
    opts.FontSize = 10
    opts.Rect = []float64{10, 25, 75, 60}

    field, err := annotator.NewSignatureField(
        signature,
        []*annotator.SignatureLine{
            annotator.NewSignatureLine("Name", "John Doe"),
            annotator.NewSignatureLine("Date", "2023.05.08"),
            annotator.NewSignatureLine("Reason", "PAdES signature test"),
        },
        opts,
    )
    if err != nil {
        log.Fatalf("Error creating signature field: %v\n", err)
    }

    field.T = core.MakeString("Self signed PDF")

    // Sign the PDF
    err = appender.Sign(1, field)
    if err != nil {
        log.Fatalf("Error signing PDF: %v\n", err)
    }

    // Write to buffer
    buffer := bytes.NewBuffer(nil)

    err = appender.Write(buffer)
    if err != nil {
        log.Fatalf("Error writing to buffer: %v\n", err)
    }

    // Second pass to save DSS/VRI information
    pdf2, err := model.NewPdfReader(bytes.NewReader(buffer.Bytes()))
    if err != nil {
        log.Fatalf("Error creating PDF reader for second pass: %v\n", err)
    }

    appender2, err := model.NewPdfAppender(pdf2)
    if err != nil {
        log.Fatalf("Error creating PDF appender for second pass: %v\n", err)
    }

    appender2.SetDSS(appender.GetDSS())
    buf2 := bytes.NewBuffer(nil)

    err = appender2.Write(buf2)
    if err != nil {
        log.Fatalf("Error writing second pass buffer: %v\n", err)
    }

    // Document timestamp for B-LTA compatibility
    pdf3, err := model.NewPdfReader(bytes.NewReader(buf2.Bytes()))
    if err != nil {
        log.Fatalf("Error creating PDF reader for timestamp: %v\n", err)
    }

    appender3, err := model.NewPdfAppender(pdf3)
    if err != nil {
        log.Fatalf("Error creating PDF appender for timestamp: %v\n", err)
    }

    handler, err = sighandler.NewDocTimeStamp(timestampServerURL, crypto.SHA512)
    if err != nil {
        log.Fatalf("Error creating timestamp handler: %v\n", err)
    }

    signature = model.NewPdfSignature(handler)
    signature.SetName("Test Signature")
    signature.SetDate(time.Now(), "")

    err = signature.Initialize()
    if err != nil {
        log.Fatalf("Error initializing timestamp signature: %v\n", err)
    }

    opts = annotator.NewSignatureFieldOpts()
    opts.Rect = []float64{0, 0, 0, 0}

    sigField, err := annotator.NewSignatureField(
        signature,
        []*annotator.SignatureLine{
            annotator.NewSignatureLine("Name", "Jane Doe"),
            annotator.NewSignatureLine("Reason", "Document Timestamp"),
        },
        opts,
    )
    if err != nil {
        log.Fatalf("Error creating timestamp signature field: %v\n", err)
    }

    err = appender3.Sign(1, sigField)
    if err != nil {
        log.Fatalf("Error signing with timestamp: %v\n", err)
    }

    err = appender3.WriteToFile(outputPath)
    if err != nil {
        log.Fatalf("Error writing final output file: %v\n", err)
    }

    log.Printf("PDF successfully signed. Output path: %s\n", outputPath)
}

Breaking Down the Code

Loading License and Parsing Arguments

The code starts by setting the license key and parsing the command-line arguments. This ensures the program has the necessary credentials and inputs to proceed.

func init() {
    err := license.SetMeteredKey(os.Getenv(`UNIDOC_LICENSE_API_KEY`))
    if err != nil {
        panic(err)
    }
}

const usagef = "Usage: %s PFX_FILE PASSWORD PEM_FILE INPUT_PDF_PATH OUTPUT_PDF_PATH\n"

func main() {
    args := os.Args
    if len(args) < 6 {
        fmt.Printf(usagef, os.Args[0])
        return
    }

    pfxPath := args[1]
    password := args[2]
    pemPath := args[3]
    inputPath := args[4]
    outputPath := args[5]
}

Loading Certificates and Keys

Next, the code loads the private key and certificate from the PFX file, and the CA certificate from the PEM file.

// Load private key and certificate from PFX file
pfxData, err := ioutil.ReadFile(pfxPath)
if err != nil {
    log.Fatalf("Error reading PFX file: %v\n", err)
}

priv, cert, err := pkcs12.Decode(pfxData, password)
if err != nil {
    log.Fatalf("Error decoding PFX file: %v\n", err)
}

// Load CA Certificate from PEM file
caCertData, err := ioutil.ReadFile(pemPath)
if err != nil {
    log.Fatalf("Error reading PEM file: %v\n", err)
}

certDERBlock, _ := pem.Decode(caCertData)

cacert, err := x509.ParseCertificate(certDERBlock.Bytes)
if err != nil {
    log.Fatalf("Error parsing CA Certificate: %v\n", err)
}

Creating PDF Reader and Appender

The PDF reader and appender are created to handle the PDF file.

// Create PDF reader
file, err := os.Open(inputPath)
if err != nil {
    log.Fatalf("Error opening input PDF: %v\n", err)
}

defer file.Close()

reader, err := model.NewPdfReader(file)
if err != nil {
    log.Fatalf("Error creating PDF reader: %v\n", err)
}

// Create PDF appender
appender, err := model.NewPdfAppender(reader)
if err != nil {
    log.Fatalf("Error creating PDF appender: %v\n", err)
}

Creating the Signature Handler

The signature handler is created using the private key, certificate, and timestamp server URL.

// Timestamp server URL
timestampServerURL := "https://freetsa.org/tsr"// Create signature handler

handler, err := sighandler.NewEtsiPAdESLevelLT(priv.(\*rsa.PrivateKey), cert, cacert, timestampServerURL, appender)
if err != nil {
    log.Fatalf("Error creating signature handler: %v\n", err)
}

Creating and Initializing the Signature

A new PDF signature is created and initialized.

// Create PDF signature
signature := model.NewPdfSignature(handler)
signature.SetName("PAdES B-LTA Signature")
signature.SetReason("Testing PAdES signature")
signature.SetDate(time.Now(), "")

err = signature.Initialize()
if err != nil {
    log.Fatalf("Error initializing signature: %v\n", err)
}

Creating the Signature Field

The signature field is created and added to the PDF.

// Create signature field
opts := annotator.NewSignatureFieldOpts()
opts.FontSize = 10
opts.Rect = []float64{10, 25, 75, 60}

field, err := annotator.NewSignatureField(
    signature,
    []*annotator.SignatureLine{
        annotator.NewSignatureLine("Name", "John Doe"),
        annotator.NewSignatureLine("Date", "2023.05.08"),
        annotator.NewSignatureLine("Reason", "PAdES signature test"),
    },

    opts,
)
if err != nil {
    log.Fatalf("Error creating signature field: %v\n", err)
}

field.T = core.MakeString("Self signed PDF")

// Sign the PDF
err = appender.Sign(1, field)
if err != nil {
    log.Fatalf("Error signing PDF: %v\n", err)
}

Writing to Buffer and Second Pass

The document is written to a buffer, and a second pass is performed to save DSS/VRI information.

// Write to buffer
buffer := bytes.NewBuffer(nil)

err = appender.Write(buffer)
if err != nil {
    log.Fatalf("Error writing to buffer: %v\n", err)
}

// Second pass to save DSS/VRI information
pdf2, err := model.NewPdfReader(bytes.NewReader(buffer.Bytes()))
if err != nil {
    log.Fatalf("Error creating PDF reader for second pass: %v\n", err)
}

appender2, err := model.NewPdfAppender(pdf2)
if err != nil {
    log.Fatalf("Error creating PDF appender for second pass: %v\n", err)
}

appender2.SetDSS(appender.GetDSS())
buf2 := bytes.NewBuffer(nil)

err = appender2.Write(buf2)
if err != nil {
    log.Fatalf("Error writing second pass buffer: %v\n", err)
}

Adding Document Timestamp

A document timestamp is added for B-LTA compatibility.

// Document timestamp for B-LTA compatibility
pdf3, err := model.NewPdfReader(bytes.NewReader(buf2.Bytes()))
if err != nil {
    log.Fatalf("Error creating PDF reader for timestamp: %v\n", err)
}

appender3, err := model.NewPdfAppender(pdf3)
if err != nil {
    log.Fatalf("Error creating PDF appender for timestamp: %v\n", err)
}

handler, err = sighandler.NewDocTimeStamp(timestampServerURL, crypto.SHA512)
if err != nil {
    log.Fatalf("Error creating timestamp handler: %v\n", err)
}

signature = model.NewPdfSignature(handler)
signature.SetName("Test Signature")
signature.SetDate(time.Now(), "")

err = signature.Initialize()
if err != nil {
    log.Fatalf("Error initializing timestamp signature: %v\n", err)
}

opts = annotator.NewSignatureFieldOpts()
opts.Rect = []float64{0, 0, 0, 0}

sigField, err := annotator.NewSignatureField(
    signature,
    []*annotator.SignatureLine{
        annotator.NewSignatureLine("Name", "Jane Doe"),
        annotator.NewSignatureLine("Reason", "Document Timestamp"),
    },
    opts,
)

if err != nil {
    log.Fatalf("Error creating timestamp signature field: %v\n", err)
}

err = appender3.Sign(1, sigField)
if err != nil {
    log.Fatalf("Error signing with timestamp: %v\n", err)
}

err = appender3.WriteToFile(outputPath)
if err != nil {
    log.Fatalf("Error writing final output file: %v\n", err)
}

log.Printf("PDF successfully signed. Output path: %s\n", outputPath)

Running the Code

To run the code, use the following command:

go run pdf_sign_pades_b_lta.go <FILE.PFX> <PASSWORD> <FILE.PEM> <INPUT_PDF_PATH> <OUTPUT_PDF_PATH>

Conclusion

PAdES provides a robust and secure way to sign PDF documents, ensuring long-term validity and compliance with European standards. With UniDoc, implementing PAdES signatures becomes straightforward, thanks to its powerful and easy-to-use API.

By following this guide, you can confidently create PAdES B-LTA compatible digital signatures, securing your PDF documents for the long term.

For more information on working with PDFs and digital signatures, visit UniDoc to explore our extensive documentation and examples, or start immediately with a free trial.