Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Tc Lib Pdf Sign Laravel Package

tecnickcom/tc-lib-pdf-sign

A PHP library for adding digital signatures to PDFs in the tc-lib-pdf ecosystem. Apply X.509 certificate signatures, timestamps, and signature metadata to documents with a lightweight API suited to automated PDF signing workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and confirm the environment first.

composer require tecnickcom/tc-lib-pdf-sign
php -m | grep -E 'openssl|mbstring|zlib'

The library is a set of primitives, not a turnkey service. It has no Laravel service provider, no facade, and no config file — you are expected to wire it up yourself. Before writing any application code, read in this order:

  1. vendor/tecnickcom/tc-lib-pdf-sign/src/ — the class list is the documentation. Look for the top-level signing/field classes and the Exception types they throw.
  2. vendor/tecnickcom/tc-lib-pdf-sign/test/ — the test suite is the only reliable usage reference; it shows the exact constructor signatures and call order.
  3. The README.md plus the parent project tc-lib-pdf wiki, which documents the surrounding PDF object model and is often more current than this package's own docs.
  4. The LICENSE file. Composer reports NOASSERTION; tc-lib packages are typically LGPL-3.0-or-later, so confirm before shipping in a closed-source product.

First use case: sign an existing PDF from an Artisan command.

// app/Console/Commands/SignPdf.php  (sketch — confirm class/method names against vendor/src)
$cert = file_get_contents(config('pdf-sign.cert_path'));   // PEM, X.509
$key  = file_get_contents(config('pdf-sign.key_path'));    // PEM private key
$pdf  = file_get_contents(storage_path('app/incoming/doc.pdf'));

$signer = new \Com\Tecnick\Pdf\Sign\...(...);  // see src/ for the entry point
$signed = $signer->...($pdf, $cert, $key);     // detached PKCS#7 over the byte range

Storage::disk('local')->put('signed/doc.pdf', $signed);

Generate a throwaway cert to get moving without waiting on a CA:

openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes \
  -subj "/C=US/O=Acme/CN=Test Signer"

Verify the output with pdfsig signed.pdf (poppler-utils) or Adobe Reader — not by re-opening it in a PDF editor, which will silently invalidate the signature.

Implementation Patterns

Wrap it behind an interface. With zero dependents and zero stars, treat this package as a replaceable implementation detail. Define App\Contracts\PdfSigner (sign(string $pdf, Certificate $cert): string, verify(string $pdf): SignatureInfo) and bind the tc-lib implementation in a service provider. If the package stalls or the LGPL becomes a problem, you swap one binding.

Keep certificates in config, never in the repo.

// config/pdf-sign.php
return [
    'cert_path' => env('PDF_SIGN_CERT'),   // absolute path, outside the webroot
    'key_path'  => env('PDF_SIGN_KEY'),
    'key_passphrase' => env('PDF_SIGN_KEY_PASS'),
    'tsa_url'   => env('PDF_SIGN_TSA_URL'), // RFC 3161 timestamp authority
    'reason'    => 'Contract execution',
    'location'  => 'Berlin, DE',
];

Mount the key from a secret manager or an encrypted volume, and chmod 600 it. Reading the key material from an environment variable as a string also works and avoids filesystem permissions entirely; either way, never Storage::disk('public').

Sign in a queued job. Signing is CPU-bound and may make an outbound TSA request, so it does not belong in a request cycle:

class SignDocumentJob implements ShouldQueue
{
    public function handle(PdfSigner $signer): void
    {
        $signed = $signer->sign($this->document->path(), $this->certificate());
        $this->document->update([
            'signed_path' => $path,
            'signed_at'   => now(),
            'sha256'      => hash_file('sha256', $path),
        ]);
    }
}

Store the post-signing SHA-256 alongside the record. It is cheap, and it gives you an independent integrity check that does not depend on the signing library at all.

Combine with tc-lib-pdf for generate-then-sign. If you also render the document with tecnickcom/tc-lib-pdf, do not sign in the same call chain. Write the PDF to a temp file, let it settle, then run the signing pass. Two passes are easier to debug and let you re-sign (see gotchas) without regenerating.

Incremental / multi-signature workflows. A second signature must be appended as an incremental update, never by rewriting the file. Model this explicitly in your schema: a document_signatures table with level and parent_hash, so you can reconstruct the signing chain for audit.

Integration tips.

  • Use Storage::path() to get a real filesystem path; the library works on byte streams, not Laravel disks.
  • Bind the signer as a singleton only if the underlying object is stateless across calls — verify by reading the source before assuming.
  • Emit a domain event (DocumentSigned) after the job so downstream listeners (notifications, webhooks, archival) do not couple to the signing code.

Gotchas and Tips

  • Byte ranges are the whole ballgame. A PDF signature covers a declared byte range. Anything that rewrites the file after signing — pdftk, Ghostscript, an optimizer, even a naive file_put_contents of the same content — breaks it. Treat signed PDFs as immutable blobs and stream them read-only.
  • Signature placeholder sizing. You must reserve byte space for the PKCS#7 blob before you know its size. Long certificate chains (CA + intermediate + TSA token) blow past tight placeholders. Reserve generously and test with your real production chain, not a self-signed cert.
  • PEM vs DER vs PKCS#12. OpenSSL expects PEM here. Users will hand you .p12/.pfx files from their CA. Convert up front:
    openssl pkcs12 -in bundle.p12 -out key.pem -nodes -nocerts
    openssl pkcs12 -in bundle.p12 -out cert.pem -nokeys -clcerts
    
    Handle password-protected private keys via the passphrase parameter, not by shelling out.
  • Container openssl.cnf. Minimal Docker images (alpine, distroless) frequently ship OpenSSL without a config file, causing TSA or extension-related failures that never reproduce locally. Set openssl_config explicitly or use php:8.x-cli with the openssl extension properly configured.
  • TSA calls are network calls. PAdES timestamps require HTTP to a third-party. Set timeouts, handle failures gracefully (fall back to a plain PKCS#7 signature and flag it), and never let a TSA timeout silently produce an unsigned document — check the return value, do not assume success.
  • PAdES ≠ PKCS#7. They are different conformance levels. If a counterparty or regulator requires "PAdES-B" or "PAdES-LT", confirm the package emits the right profile; do not assume the default is compliant.
  • Verification is separate from signing. Do not self-verify by round-tripping through the same code. Add poppler-utils to CI and assert pdfsig exits clean, and manually open one output in Adobe Reader before you ship.
  • No documentation means read the source. Expect to spend an hour in vendor/ before your first successful signature. Add a comment in your wrapper pointing at the exact vendor file and version you coded against.
  • Pin the version. The metadata shows a future-dated release (2026-10-17) and no dependents — a signal that release hygiene and semver discipline are uncertain. Use an exact constraint ("tecnickcom/tc-lib-pdf-sign": "1.0.0" style) or commit composer.lock and add a CI smoke test that signs a fixture PDF, so an upstream change fails your build rather than your production signing.
  • Error handling is your job. The library throws low-level exceptions without user-friendly messages. Catch them at the wrapper boundary, log the raw message, and surface something generic. A failed signature must fail the job loudly — an unsigned document that looks signed is the worst possible outcome.
  • Extension points. Visible signature fields take an appearance rectangle and optional image; if you need a company logo, that is configured per-field at creation time, not retrofitted later. Similarly, signature metadata (reason, location, contact) is baked in at signing, not editable afterward — get it right the first time.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc