PHP library to digitally sign PDFs using A1 certificates (.pfx/.p12) with a simple, developer-friendly API.
PDF parsing and incremental serialization live in the sibling package signerphp/pdf-core. This package owns signing, timestamps, PAdES, LTV, validation and protection. The public API is SignerPHP\PdfSigner\Presentation\Signer.
If you need backend PDF signing with cryptographic validity, this library provides a direct flow to:
- apply digital signatures to PDF files
- include signer metadata
- add visible signatures (image)
- apply RFC3161 timestamping (TSA)
- sign the same PDF multiple times (incremental flow)
- PKCS#12 (
.pfx/.p12) digital signature - Fluent builder API (
Signer::signer()) - Invisible signature
- Visible signature with background image (
PNG/JPEG), contain-scaled and centred - Visible signature with drawn signature image on a configurable sub-area of the bbox
- Visible signature with custom PDF content stream (text, graphics) in the n2 layer
- Automatic default visible appearance (built-in fallback)
- Signature metadata (
name,contactInfo,reason,location) - DocMDP certification (levels 1, 2 and 3)
- Brazil policy mode (
br-iti) signing preset - PAdES Baseline-B profile mode (SubFilter
ETSI.CAdES.detached) - PAdES Baseline-T profile mode (PAdES-B + required timestamp)
- PAdES Baseline-LT profile mode (PAdES-T + embedded DSS/Certs)
- PAdES Baseline-LTA profile mode (PAdES-LT + extra archival timestamp)
- Multiple signatures in the same document
- Optional RFC3161 timestamping
- RFC3161 timestamping with public default TSA when enabled (
withTimestamp()) - PDF permission protection (for example, block content copying)
- Validation of existing digital signatures in PDF files
- PHP
^8.2 ext-opensslext-curl- recommended:
ext-zlibandext-fileinfo
Install with Composer:
composer require signerphp/pdf-signerComposer also installs signerphp/pdf-core, which provides PDF parsing and incremental serialization.
signerphp/pdf-signer
|
+-- signerphp/pdf-core PDF parse, xref, ByteRange, incremental update
|
+-- CMS / PAdES / LTV signature container, timestamp, DSS
|
+-- SignatureProvider private-key operation (local OpenSSL by default)
pdf-core never depends on pdf-signer. Local PKCS#12 signing is the default; CSC/HSM providers are not part of the public API yet.
<?php
use SignerPHP\PdfSigner\Presentation\Signer;
$signedPdf = Signer::signer()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withCertificatePath('/tmp/certificate.pfx', 'secret-password')
->sign();
file_put_contents('/tmp/output-signed.pdf', $signedPdf);By default, the library applies a fallback visible appearance with a styled built-in stamp (internal image + default position) for simpler usage.
If you already have PKCS#12 in memory, use content instead of a file path:
$pkcs12 = file_get_contents('/tmp/certificate.pfx');
$signedPdf = Signer::signer()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withCertificateContent($pkcs12, 'secret-password')
->sign();Use the prepare/complete lifecycle when a private key is held by another process, HSM, or signing service. Preparation returns the exact CMS signed attributes and their digest together with an opaque, serializable state. The external signer must return a binary RSA PKCS#1 v1.5 signature or DER-encoded ECDSA signature, as indicated by the payload; CMS/PAdES assembly remains inside this library.
use SignerPHP\PdfSigner\Presentation\Signer;
$prepared = Signer::externalSigner()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withCertificate(file_get_contents('/tmp/certificate.pem'))
->withPadesBaselineB()
->prepare();
// Persist $prepared->state when the external operation is asynchronous.
// Inspect $prepared->payload for the input, encoding and algorithms required.
$rawSignature = $externalSigner->sign(
$prepared->payload->input(),
$prepared->payload->inputType,
$prepared->payload->digestAlgorithm,
$prepared->payload->signatureAlgorithm,
$prepared->payload->signatureEncoding,
);
$signedPdf = Signer::externalSigner()->complete(
$prepared->state,
$rawSignature,
);completeBase64() accepts a base64-encoded raw signature. The prepared state contains the unsigned PDF and public certificate, but never a private key. Store it with the same access controls used for the source document.
The VIDaaS module keeps OAuth, certificate discovery and RAW signing isolated behind provider-neutral remote-signing contracts. The access token authorizes use of the cloud-held private key; certificate discovery returns the public certificate and its remote identifier.
use SignerPHP\PdfSigner\Infrastructure\Native\Service\CurlHttpClient;
use SignerPHP\PdfSigner\Infrastructure\Remote\Vidaas\VidaasAuthorization;
use SignerPHP\PdfSigner\Infrastructure\Remote\Vidaas\VidaasCertificateDiscovery;
use SignerPHP\PdfSigner\Infrastructure\Remote\Vidaas\VidaasClient;
use SignerPHP\PdfSigner\Infrastructure\Remote\Vidaas\VidaasPkce;
use SignerPHP\PdfSigner\Infrastructure\Remote\Vidaas\VidaasSignatureProvider;
use SignerPHP\PdfSigner\Presentation\Signer;
$verifier = VidaasPkce::generateVerifier();
$http = new CurlHttpClient();
$authorization = new VidaasAuthorization($http, $clientId, $clientSecret);
$authorizationUrl = $authorization->authorizationUrl(
codeChallenge: VidaasPkce::challenge($verifier),
redirectUri: $callbackUrl,
state: $state,
);
// After the authorization callback:
$token = $authorization->exchangeAuthorizationCode(
$authorizationCode,
$verifier,
$callbackUrl,
);
$client = new VidaasClient($token->value, $http);
$certificate = (new VidaasCertificateDiscovery($client))->certificates()[0];
$builder = Signer::externalSigner()
->withPdfContent($pdf)
->withCertificate(
$certificate->certificate->certificatePem,
$certificate->certificate->chainPem,
)
->withPadesBaselineB();
$prepared = $builder->prepare();
$signature = (new VidaasSignatureProvider($client))->sign($prepared->payload, $certificate);
$signedPdf = $builder->complete($prepared->state, $signature->bytes);When discovery returns more than one certificate, present their remote identifiers to the user and pass the selected RemoteCertificate through the same flow.
For mobile approval, call startPush() with the certificate holder's CPF, poll pollPush() until its result is approved, then pass its authorizationToken to exchangePushAuthorizationToken(). A runnable example covering QR Code, push, certificate selection, and PDF signing is available in examples/providers/vidaas.
The IntegraICP v3 adapter supports S256 authorization, provider clearances, authorized certificate discovery and RAW signing through the same remote-signing contracts. See the integration guide for a complete example and production requirements.
<?php
use SignerPHP\PdfSigner\Application\DTO\SignatureActorDto;
use SignerPHP\PdfSigner\Application\DTO\SignatureMetadataDto;
use SignerPHP\PdfSigner\Presentation\Signer;
$metadata = new SignatureMetadataDto(
reason: 'Contract approval',
location: 'Sao Paulo - BR',
actor: new SignatureActorDto(
name: 'Maria Silva',
contactInfo: 'maria@company.com'
)
);
$signedPdf = Signer::signer()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withCertificatePath('/tmp/certificate.pfx', 'secret-password')
->withMetadata($metadata)
->sign();The image is placed in the n0 layer and scaled with contain logic (aspect ratio preserved, centred inside the bbox).
<?php
use SignerPHP\PdfSigner\Application\DTO\SignatureAppearanceDto;
use SignerPHP\PdfSigner\Presentation\Signer;
$appearance = new SignatureAppearanceDto(
backgroundImagePath: '/tmp/stamp.png',
rect: [350, 770, 500, 830], // [x1, y1, x2, y2]
page: 0 // 0-based index
);
$signedPdf = Signer::signer()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withCertificatePath('/tmp/certificate.pfx', 'secret-password')
->withAppearance($appearance)
->sign();A base64-encoded string is also accepted:
$appearance = new SignatureAppearanceDto(
backgroundImagePath: base64_encode(file_get_contents('/tmp/stamp.png')),
rect: [350, 770, 500, 830],
page: 0
);To show the signer's drawn PNG on a specific area of the bbox (e.g. left half), use signatureImagePath and signatureImageFrame. This places the image as an external XObject in the n2 layer, independently from the n0 background.
<?php
use SignerPHP\PdfSigner\Application\DTO\SignatureAppearanceDto;
use SignerPHP\PdfSigner\Presentation\Signer;
$bboxW = 150; // urx - llx
$bboxH = 60; // ury - lly
$appearance = new SignatureAppearanceDto(
backgroundImagePath: null, // no background stamp
rect: [350, 770, 500, 830],
page: 0,
signatureImagePath: '/tmp/drawn-signature.png',
signatureImageFrame: [0.0, 0.0, $bboxW / 2, (float) $bboxH], // left half
);
$signedPdf = Signer::signer()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withCertificatePath('/tmp/certificate.pfx', 'secret-password')
->withAppearance($appearance)
->sign();To embed arbitrary PDF text or graphics operators in the n2 layer — for example, the signer's name, date and reason — pass a SignatureAppearanceXObjectDto with the stream and its resource dictionary.
<?php
use SignerPHP\PdfSigner\Application\DTO\SignatureAppearanceDto;
use SignerPHP\PdfSigner\Application\DTO\SignatureAppearanceXObjectDto;
use SignerPHP\PdfSigner\Presentation\Signer;
$stream = "BT\n/F1 9 Tf\n0 0 0 rg\n4 44 Td\n(Signed by: Maria Silva) Tj\n0 -12 Td\n(Date: 2026-03-04) Tj\nET\n";
$appearance = new SignatureAppearanceDto(
backgroundImagePath: '/tmp/stamp.png',
rect: [350, 770, 500, 830],
page: 0,
xObject: new SignatureAppearanceXObjectDto(
stream: $stream,
resources: [
'Font' => [
'F1' => ['Type' => '/Font', 'Subtype' => '/Type1', 'BaseFont' => '/Helvetica'],
],
],
),
);
$signedPdf = Signer::signer()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withCertificatePath('/tmp/certificate.pfx', 'secret-password')
->withAppearance($appearance)
->sign();<?php
use SignerPHP\PdfSigner\Presentation\Signer;
$signedPdf = Signer::signer()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withCertificatePath('/tmp/certificate.pfx', 'secret-password')
->withoutDefaultAppearance()
->sign();Use the signed output as input for the next signature:
<?php
use SignerPHP\PdfSigner\Presentation\Signer;
$step1 = Signer::signer()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withCertificatePath('/tmp/signer-a.pfx', 'password-a')
->sign();
$step2 = Signer::signer()
->withPdfContent($step1)
->withCertificatePath('/tmp/signer-b.pfx', 'password-b')
->sign();
file_put_contents('/tmp/output-multi-signed.pdf', $step2);To enable timestamping with default configuration, call withTimestamp().
In this case, the library uses a public default TSA (https://freetsa.org/tsr).
<?php
use SignerPHP\PdfSigner\Presentation\Signer;
$signedPdf = Signer::signer()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withCertificatePath('/tmp/certificate.pfx', 'secret-password')
->withTimestamp()
->sign();If you want a custom TSA only for this signing flow, use withTimestamp(new TimestampOptionsDto(...)).
The hashAlgorithm field accepts HashAlgorithm (recommended) or a compatible string (sha256, sha384, sha512, sha224, sha1).
<?php
use SignerPHP\PdfSigner\Application\DTO\TimestampOptionsDto;
use SignerPHP\PdfSigner\Presentation\Signer;
$signedPdf = Signer::signer()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withCertificatePath('/tmp/certificate.pfx', 'secret-password')
->withDefaultTimestampProfile(new TimestampOptionsDto(
tsaUrl: 'https://timestamp.your-provider.com',
hashAlgorithm: 'sha256',
certReq: true,
username: null,
password: null,
timeoutSeconds: 15
))
->sign();<?php
use SignerPHP\PdfSigner\Presentation\Signer;
$signedPdf = Signer::signer()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withCertificatePath('/tmp/certificate.pfx', 'secret-password')
->withoutTimestamp()
->sign();For SaaS/API flows, you can run a real TSA routine (not only endpoint ping) using the timestamp facade:
<?php
use SignerPHP\PdfSigner\Application\DTO\TimestampOptionsDto;
use SignerPHP\PdfSigner\Presentation\Signer;
$tsa = Signer::timestamp()
->withOptions(new TimestampOptionsDto(
tsaUrl: 'https://freetsa.org/tsr',
hashAlgorithm: 'sha256',
));
$connection = $tsa->testConnection();
if (! $connection->success) {
throw new RuntimeException('TSA test failed: '.($connection->message ?? 'unknown error'));
}
$tokenHex = $tsa
->withContent('probe-content')
->requestTokenHex();<?php
use SignerPHP\PdfSigner\Presentation\Signer;
$signedPdf = Signer::signer()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withCertificatePath('/tmp/certificate.pfx', 'secret-password')
->withPadesBaselineB()
->sign();<?php
use SignerPHP\PdfSigner\Presentation\Signer;
$signedPdf = Signer::signer()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withCertificatePath('/tmp/certificate.pfx', 'secret-password')
->withPadesBaselineT()
->sign();In PAdES-T mode, timestamp must be active (for example, withTimestamp(...)).
<?php
use SignerPHP\PdfSigner\Presentation\Signer;
$signedPdf = Signer::signer()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withCertificatePath('/tmp/certificate.pfx', 'secret-password')
->withPadesBaselineLT()
->sign();In PAdES-LT mode, besides timestamping, the library applies DSS enrichment with certificates extracted from CMS/RFC3161 signatures.
<?php
use SignerPHP\PdfSigner\Presentation\Signer;
$signedPdf = Signer::signer()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withCertificatePath('/tmp/certificate.pfx', 'secret-password')
->withPadesBaselineLTA()
->sign();In PAdES-LTA mode, after LT enrichment, the library adds one extra archival Document Timestamp on the final document revision.
<?php
use SignerPHP\PdfSigner\Application\DTO\CertificationLevel;
use SignerPHP\PdfSigner\Presentation\Signer;
$signedPdf = Signer::signer()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withCertificatePath('/tmp/certificate.pfx', 'secret-password')
->withCertificationLevel(CertificationLevel::FormFillAndSignatures)
->sign();Available levels:
CertificationLevel::NoChangesAllowed(1): no changes allowed after certification.CertificationLevel::FormFillAndSignatures(2): allows form filling and additional signatures.CertificationLevel::FormFillSignaturesAndAnnotations(3): allows forms, signatures, and annotations.
Default behavior:
- Without
withCertificationLevel(...), DocMDP is not explicitly set (null). - In
withBrazilPolicy(...), the library forcesCertificationLevel::FormFillAndSignatures(level2).
<?php
use SignerPHP\PdfSigner\Application\DTO\BrazilSignaturePolicyOptionsDto;
use SignerPHP\PdfSigner\Presentation\Signer;
$signedPdf = Signer::signer()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withCertificatePath('/tmp/certificate.pfx', 'secret-password')
->withBrazilPolicy(new BrazilSignaturePolicyOptionsDto(
tsaUrl: 'https://tsa.your-icpbrasil-provider.com',
hashAlgorithm: 'sha256',
timeoutSeconds: 20,
certReq: true,
))
->sign();This preset applies PAdES-LTA + DocMDP=2 + explicit policy timestamp.
To switch quickly to another TSA:
$policy = BrazilSignaturePolicyOptionsDto::tsa('https://tsa.your-provider.com')
->withHashAlgorithm('sha256')
->withTimeoutSeconds(20);SERPRO support (homologation):
- OAuth2 token:
https://gateway.apiserpro.serpro.gov.br/token - ASN.1 timestamp endpoint:
https://gateway.apiserpro.serpro.gov.br/apitimestamp/v1/stamps-asn1
SERPRO helper example:
<?php
use SignerPHP\PdfSigner\Application\DTO\BrazilSignaturePolicyOptionsDto;
use SignerPHP\PdfSigner\Presentation\Signer;
$signedPdf = Signer::signer()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withCertificatePath('/tmp/certificate.pfx', 'secret-password')
->withBrazilPolicy(BrazilSignaturePolicyOptionsDto::serpro(
consumerKey: 'YOUR_CONSUMER_KEY',
consumerSecret: 'YOUR_CONSUMER_SECRET',
hashAlgorithm: 'sha256',
timeoutSeconds: 20
))
->sign();<?php
use SignerPHP\PdfSigner\Application\DTO\ProtectionOptionsDto;
use SignerPHP\PdfSigner\Presentation\Signer;
$protectedPdf = Signer::protection()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withProtection(ProtectionOptionsDto::preventCopy(
ownerPassword: 'owner-secret',
userPassword: ''
))
->protect();
file_put_contents('/tmp/output-protected.pdf', $protectedPdf);Use this flow to avoid ordering mistakes and ensure the signature is applied on the already protected PDF.
<?php
use SignerPHP\PdfSigner\Application\DTO\ProtectionOptionsDto;
use SignerPHP\PdfSigner\Presentation\Signer;
$signedProtectedPdf = Signer::signer()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withProtection(ProtectionOptionsDto::preventCopy(
ownerPassword: 'owner-secret',
userPassword: ''
))
->withCertificatePath('/tmp/certificate.pfx', 'secret-password')
->protectThenSign();
file_put_contents('/tmp/output-protected-signed.pdf', $signedProtectedPdf);<?php
use SignerPHP\PdfSigner\Presentation\Signer;
$validation = Signer::validation()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->validate();
if (! $validation->hasSignatures) {
// PDF has no signatures
}
if ($validation->allValid) {
// all signatures were verified
}<?php
use SignerPHP\PdfSigner\Presentation\Signer;
$validation = Signer::validation()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->enableTrustChainValidation('/etc/ssl/certs/ca-certificates.crt') // optional; if omitted, tries the system default bundle
->validate();
foreach ($validation->entries as $entry) {
var_dump($entry->trustValid); // true|false|null
}<?php
use SignerPHP\PdfSigner\Presentation\Signer;
$validation = Signer::validation()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withBrazilPolicy('/path/to/icp-brasil-bundle.pem')
->validate();withBrazilPolicy(...) trust store precedence:
- If
trustStorePathis provided, it is used directly. - If
trustStorePathisnull, the library automatically builds/updates a local cached ICP-Brasil bundle and uses it.
If trustStorePath is null, br-iti mode builds an ICP-Brasil trust anchors bundle automatically in local cache:
- default directory:
sys_get_temp_dir()/signer-php/trust-anchors - default URLs:
http://acraiz.icpbrasil.gov.br/Certificado_AC_Raiz.crthttp://acraiz.icpbrasil.gov.br/credenciadas/RAIZ/ICP-Brasilv2.crthttp://acraiz.icpbrasil.gov.br/credenciadas/RAIZ/ICP-Brasilv5.crthttp://acraiz.icpbrasil.gov.br/credenciadas/RAIZ/ICP-Brasilv6.crthttp://acraiz.icpbrasil.gov.br/credenciadas/RAIZ/ICP-Brasilv7.crt
In br-iti mode, validation also verifies ICP-Brasil PAdES policy list (LPA):
https://politicas.icpbrasil.gov.br/LPA_PAdES.derhttps://politicas.icpbrasil.gov.br/LPA_PAdES.p7s
You can override these URLs:
<?php
use SignerPHP\PdfSigner\Application\DTO\BrazilPolicyLpaUrlsDto;
use SignerPHP\PdfSigner\Application\DTO\BrazilTrustAnchorsOptionsDto;
use SignerPHP\PdfSigner\Presentation\Signer;
$validation = Signer::validation()
->withPdfContent(file_get_contents('/tmp/input.pdf'))
->withBrazilPolicy(
'/path/to/icp-brasil-bundle.pem',
new BrazilPolicyLpaUrlsDto(
lpaUrlAsn1Pades: 'https://your-endpoint/LPA_PAdES.der',
lpaUrlAsn1SignaturePades: 'https://your-endpoint/LPA_PAdES.p7s',
),
new BrazilTrustAnchorsOptionsDto(
directory: '/tmp/my-trust-anchors-cache',
urls: [
'http://acraiz.icpbrasil.gov.br/Certificado_AC_Raiz.crt',
'http://acraiz.icpbrasil.gov.br/credenciadas/RAIZ/ICP-Brasilv7.crt',
],
),
)
->validate();trustValid = true: certificate chain is valid for the trust store in use.trustValid = false: chain is invalid, incomplete, or not trusted by the trust store in use.trustValid = null: trust chain validation was not executed for that signature.policyValid = true: PAdES LPA check (ICP-Brasil or overridden URLs) passed.policyValid = false: policy/LPA check failed for that signature.policyValid = null: policy mode was not requested in the flow.
Besides signing, the project provides bin/signer-inspect for technical diagnostics of signed PDFs.
php bin/signer-inspect --input=/tmp/output-signed.pdfphp bin/signer-inspect --input=/tmp/output-signed.pdf --jsonMain inspection fields:
inferred_profile: inferred profile (pades-baseline-b,t,lt,lta).features: presence ofDSS,VRI,DocMDP,OCSPs,CRLs.revocation_endpoints: OCSP/CRL endpoints per discovered certificate.revocation_risk_summary: connectivity/missing-endpoint risk flags.
This inspection helps explain warnings reported by external validators (for example CRL/OCSP connectivity issues).
Use bin/signer-doctor to quickly validate runtime dependencies (PHP version, required extensions, OpenSSL binary and openssl ts, qpdf, and temporary directory access).
php bin/signer-doctorphp bin/signer-doctor --json- Sign with
--policy=br-itiusingbin/signer-sign. - Validate programmatically with
Signer::validation()->withBrazilPolicy(...). - Inspect final PDF with
bin/signer-inspect --json. - If revocation warnings appear, check
revocation_risk_summaryand endpoint availability for OCSP/CRL URLs.
This project already includes docker-compose.yml and Dockerfile for the app service.
docker network create kool_globaldocker compose up -d --builddocker compose exec app composer installdocker compose exec app php ./vendor/bin/pest --configuration phpunit.xml testsdocker compose downThe project provides bin/signer-sign, bin/signer-inspect, and bin/signer-doctor.
php bin/signer-sign \
--input=/tmp/input.pdf \
--output=/tmp/output-signed.pdf \
--cert=/tmp/certificate.pfx \
--password='secret-password'php bin/signer-sign \
--input=/tmp/input.pdf \
--output=/tmp/output-signed.pdf \
--cert=/tmp/certificate.pfx \
--password='secret-password' \
--pades-baseline-b \
--timestamp-url='https://tsa.example.com' \
--timestamp-hash='sha256' \
--timestamp-timeout=20php bin/signer-sign \
--input=/tmp/input.pdf \
--output=/tmp/output-certified.pdf \
--cert=/tmp/certificate.pfx \
--password='secret-password' \
--certification-level=2php bin/signer-sign \
--input=/tmp/input.pdf \
--output=/tmp/output-br-iti.pdf \
--cert=/tmp/certificate.pfx \
--password='secret-password' \
--policy=br-iti \
--policy-serpro-consumer-key='YOUR_CONSUMER_KEY' \
--policy-serpro-consumer-secret='YOUR_CONSUMER_SECRET' \
--policy-timestamp-hash='sha256' \
--policy-timestamp-timeout=20php bin/signer-sign \
--input=/tmp/input.pdf \
--output=/tmp/output-signed-lt.pdf \
--cert=/tmp/certificate.pfx \
--password='secret-password' \
--pades-baseline-lt \
--timestamp-url='https://freetsa.org/tsr' \
--timestamp-hash='sha256' \
--timestamp-timeout=20php bin/signer-sign \
--input=/tmp/input.pdf \
--output=/tmp/output-signed-lta.pdf \
--cert=/tmp/certificate.pfx \
--password='secret-password' \
--pades-baseline-lta \
--timestamp-url='https://freetsa.org/tsr' \
--timestamp-hash='sha256' \
--timestamp-timeout=20php bin/signer-sign --helpSignerPHP\\PdfSigner\\Domain\\Exception\\InvalidCertificateExceptionSignerPHP\\PdfSigner\\Domain\\Exception\\SignProcessExceptionSignerPHP\\PdfSigner\\Domain\\Exception\\SignerException
- For RFC3161 timestamping, host
opensslmust supportopenssl ts. - For PDF permissions protection, host
qpdfmust be installed. - If you use the public default TSA, consider availability/SLA for production and prefer a dedicated provider.
- A digital signature covers specific PDF bytes (
ByteRange). Because of that, operations that rewrite file bytes should run before signing. - Validation checks
ByteRangeintegrity and CMS/PKCS#7 cryptographic validity with OpenSSL. Optionally, it can validate trust chain (enableTrustChainValidation(...)) and Brazil policy (withBrazilPolicy(...)). - PAdES profiles:
- Baseline-B:
SubFilter /ETSI.CAdES.detached - Baseline-T: Baseline-B + RFC3161
- Baseline-LT: Baseline-T +
DSS(Certs,OCSPs,CRLs,VRI) with best-effort evidence collection depending on chain endpoint availability - Baseline-LTA: Baseline-LT + additional archival
Document Timestamp
- Baseline-B:
- Default appearance uses
page = 0and an internal default rectangle; for full control usewithAppearance(...). - Supported PNG bit depths: 1, 2, 4, 8, and 16 bits per channel. 16-bit RGBA and grayscale-alpha PNGs are fully supported including SMask alpha extraction.
- PDF parsing (classic xref, xref streams, object streams,
/Prev, hybrid/XRefStm) is implemented insignerphp/pdf-core.
vendor/bin/pest --configuration phpunit.xml testscomposer pint:check
composer pint:fix
composer security:audit
composer tests:coverage
composer psalm
composer deptrac
composer infection