v1.0.0 - The core, without the framework it grew up in
Signet PDF signs and verifies PDF signatures in PHP: PAdES B-B to B-LTA, by incremental update, with no framework.
It is the core of lsnepomuceno/laravel-a1-pdf-sign, extracted so it can be used from Symfony, Slim, a plain script, or another library. That package remains and becomes the Laravel integration over this one.
composer require lsnepomuceno/signet-pdfWhat it does
- Signs by appending a revision, never by rebuilding the document. The original bytes survive byte for byte, so annotations, form fields and every earlier signature stay intact and a second signature does not invalidate the first (ISO 32000-1 §7.5.6).
- PAdES profiles
legacy,pades-b-b,pades-b-t,pades-b-lt,pades-b-lta, including the Document Security Store and the archive timestamp. - Verifies cryptographically. "Valid" means the CMS actually verifies.
- Certification signatures (
/DocMDP) and field locks (/Lock), enforced rather than merely written. - ICP-Brasil identities read out of the certificate's own extensions.
- A command line:
signet sign,verify,fields,check.verify --jsonputs the verdict in the exit status, so a build in any language can gate on it.
What "framework agnostic" means here
Five rules, one per piece of code that had to move, in 0100. The core returns no HTTP, reads no configuration, has no container, opens no connection or process outside its two contracts, and keeps a fluent builder of its own.
A token walk in tests/Project/ArchTest.php fails on any Illuminate, Laravel or Orchestra import in src/. It is a walk rather than an arch rule because an arch rule can only be pointed at symbols that exist, and the whole point is that these do not.
Symfony is the only framework vendor (0101): process, http-client, uid, console. One exception, argued and recorded: psr/log, for the optional audit trail.
Moving from the Laravel package
UPGRADE.md maps it. The short version: the namespace becomes LSNepomuceno\Signet\, the facade becomes an object you construct, and configuration becomes value objects.
Certificates encrypted by either package open in the other. Support\OpensslEncrypter writes the same envelope Illuminate\Encryption\Encrypter does, byte for byte, because an application moving between the two cannot re-encrypt material whose plaintext it no longer holds.
Signed output is unchanged. The bytes this package emits are the bytes the Laravel package emitted, which is what samples/ and the pdfsig cross-check are for.
Verification
627 tests on PHP 8.4 and 8.5, PHPStan at level max with no baseline, type coverage at 100%, and zero warnings: phpunit.xml fails the run on any diagnostic, and a call whose failure is an expected answer goes through Support\Probe::run() rather than the @ operator, which does not actually stop PHPUnit reporting it.
What the suite is checked against, none of which ships: qpdf for structure, veraPDF for PDF/A and PDF/UA, pyHanko for /DocMDP, poppler's pdfsig as an independent reader, and the PDF Association's Arlington PDF Model, which checks each object against the specification's own grammar.