NAME

PDF::Sign - Sign PDF files with CMS/CAdES signatures and RFC3161 timestamps

VERSION

Version 0.01

SYNOPSIS

use PDF::Sign qw(config :sign :ts);
# or selectively:
use PDF::Sign qw(config :sign);   # config prepare_file sign_file cms_sign
use PDF::Sign qw(config :ts);     # config prepare_ts ts_file ts_query tsa_fetch

# configure (recommended)
config(
    osslcmd     => '/usr/local/bin/openssl',  # optional, default: 'openssl'
    x509_pem    => '/path/to/cert.pem',
    x509_chain  => '/path/to/chain.pem',      # optional
    privkey_pem => '/path/to/privkey.pem',
    tsaserver   => 'http://timestamp.digicert.com',
    tmpdir      => '/tmp',
    debug       => 0,
);

# sign
my $pdf = PDF::API2->open('input.pdf');
prepare_file($pdf, 0);              # 0 = invisible, 1 = visible widget
my $signed = sign_file($pdf);

# timestamp
my $pdf2 = PDF::API2->from_string($signed);
prepare_ts($pdf2);
my $timestamped = ts_file($pdf2);

open(my $fh, '>:raw', 'output.pdf') or die $!;
print $fh $timestamped;
close $fh;

DESCRIPTION

PDF::Sign provides functions to apply CMS/CAdES digital signatures and RFC3161 timestamps to PDF files, producing PAdES-compliant output.

Requires an external openssl binary for signing operations. TSA requests use curl if available, falling back to LWP::UserAgent.

CONFIGURATION

All configuration is done via package variables (our), settable from the calling script:

$PDF::Sign::osslcmd

Path to the openssl binary. Default: openssl (from PATH).

$PDF::Sign::x509_pem

Path to the signer certificate PEM file. Required for signing.

$PDF::Sign::x509_chain

Path to the certificate chain PEM file. Optional.

$PDF::Sign::privkey_pem

Path to the private key PEM file. Required for signing.

$PDF::Sign::tsaserver

URL of the TSA server. Default: http://timestamp.digicert.com.

$PDF::Sign::tmpdir

Directory for temporary files. Default: current working directory.

$PDF::Sign::siglen

Signature buffer size in bytes. Default: 8192 (1024*8). Increase if signatures are truncated.

$PDF::Sign::debug

Enable debug output. Default: 0.

FUNCTIONS

config(%args)

Configure PDF::Sign in one call. All keys are optional — only provided keys are updated. Recommended over setting package variables directly.

config(
    osslcmd     => '/usr/local/bin/openssl',
    x509_pem    => '/path/to/cert.pem',
    x509_chain  => '/path/to/chain.pem',
    privkey_pem => '/path/to/privkey.pem',
    tsaserver   => 'http://timestamp.digicert.com',
    tmpdir      => '/tmp',
    siglen      => 8192,
    debug       => 1,
);

If osslcmd is changed, the openssl version is re-detected automatically.

Package variables ($PDF::Sign::x509_pem etc.) still work for backwards compatibility.

prepare_file($pdf, $presign, $reason)

Prepares the AcroForm structure for a CMS/CAdES signature. $presign = 1 adds a visible widget on page 1; $presign = 0 adds an invisible field. $reason is optional.

sign_file($pdf)

Applies the CMS/CAdES signature. Returns the signed PDF as a string.

prepare_ts($pdf)

Prepares the AcroForm structure for a RFC3161 DocTimeStamp. Appends to an existing AcroForm if a signature field already exists.

ts_file($pdf)

Applies the RFC3161 timestamp. Returns the timestamped PDF as a string.

verify_signatures($pdf_path, %args)

Reads and verifies all digital signatures in a PDF file. Returns an arrayref of hashrefs, one per signature field found:

my $sigs = verify_signatures('signed.pdf');
for my $sig (@$sigs) {
    printf "Type:      %s\n", $sig->{type};       # cms | tsa
    printf "SubFilter: %s\n", $sig->{subfilter};
    printf "Signer:    %s\n", $sig->{signer};     # cms only
    printf "Signed at: %s\n", $sig->{signed_at};  # cms only
    printf "TSA at:    %s\n", $sig->{tsa_at};     # tsa only
    printf "Valid:     %s\n", $sig->{valid} ? 'YES' : 'NO';
    printf "Error:     %s\n", $sig->{error} if $sig->{error};
}

Optional args:

verify_signatures('signed.pdf', ca_bundle => '/etc/ssl/certs/ca-bundle.crt');

Without ca_bundle, verification uses -noverify (no CA chain check — cryptographic integrity only). With ca_bundle, full chain verification is performed.

cms_sign(%args)

Low-level: invokes openssl cms to sign a file stream. Args: signer, inkey, in, certfile (optional). Returns raw DER signature bytes.

ts_query(%args)

Low-level: generates a .tsq timestamp query file via openssl ts. Args: in (file to timestamp), out (output .tsq path).

tsa_fetch(%args)

Low-level: sends .tsq to TSA server, returns TimeStampToken DER bytes. Args: tsq (input .tsq path), tsa_url. Uses curl if available, falls back to LWP::UserAgent.

DEPENDENCIES

AUTHOR

Massimiliano Citterio

Originally inspired by Martin Schuette <info@mschuette.name> (2012) https://mschuette.name/files/pdfsign.pl

LICENSE

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself (GPL/Artistic License).

See http://dev.perl.org/licenses/ for more information.

The original code by Martin Schuette is covered by the BSD 2-Clause License retained in the source.

1 POD Error

The following errors were encountered while parsing the POD:

Around line 1008:

Non-ASCII character seen before =encoding in '—'. Assuming UTF-8