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?
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
- Long-Term Validity: PAdES signatures can be validated long after the signing, ensuring document integrity over time.
- Compliance: PAdES is compliant with European eIDAS regulation, making it a robust choice for legal documents.
- 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.
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.