Podpisywanie pliku PDF z użyciem PAdES B-B i rozszerzanie go do PAdES B-T
W skrócie
Dział zatytułowany „W skrócie”Skorzystaj z tego przepisu, aby utworzyć podpis Portable Document Format (PDF) Advanced Electronic Signatures (PAdES) B-B: strukturę Cryptographic Message Syntax (CMS) SignedData z podpisanymi atrybutami (content-type, message-digest, signing-time). Następnie rozszerz ten podpis do PAdES B-T, dodając jeden znacznik czasu RFC 3161 signature-time-stamp. B-T to B-B plus pojedynczy znacznik czasu; nie jest to osobna klasa podpisu. Granica zaufania jest określona wprost: utworzenie podpisu to nie to samo, co uznanie go przez weryfikatora za prawidłowy.
Zastrzeżenie U-1. NextPDF nie potwierdza żadnej niezależnej certyfikacji ETSI EN 319 142-1 dla PAdES B-T. EN 319 142-1 nie znajduje się w korpusie weryfikacyjnym; wymaganie B-T
signature-time-stampzweryfikowano względem ETSI EN 319 122-1 §5.3 wraz z RFC 3161, RFC 5652, RFC 5816 oraz ISO 32000-2 §12.8. Obsługa profilu B-T nie jest certyfikacją zgodności ani ważności prawnej; takiego rozstrzygnięcia dokonuje niezależny walidator.
B-LT i B-LTA (materiał walidacyjny Document Security Store (DSS), pętla archival-timestamp) są poza zakresem tego przepisu i nie należą do omawianego tutaj obszaru podpisywania Core/Pro.
Instalacja
Dział zatytułowany „Instalacja”composer require nextpdf/core:^3Rozszerzenie ext-openssl musi być włączone, ponieważ CertificateInfo przetwarza klucze za pośrednictwem OpenSSL. B-T wymaga też osiągalnego punktu końcowego RFC 3161 urzędu znacznika czasu (Time Stamping Authority, TSA) oraz klienta HTTP zgodnego z PHP Standards Recommendation (PSR)-18.
Przegląd koncepcyjny
Dział zatytułowany „Przegląd koncepcyjny”Podpis PAdES B-B przechowuje strukturę CMS SignedData zakodowaną w Distinguished Encoding Rules (DER) we wpisie Contents słownika podpisu; wartość Contents to łańcuch szesnastkowy dopełniony względem skrótu zakresu bajtów (ISO 32000-2 §12.8.1). Skrót obejmuje plik i wyklucza samą wartość podpisu (ISO 32000-2 §12.8.1).
PAdES B-T dodaje dokładnie jeden znacznik czasu RFC 3161 signature-time-stamp. Odcisk wiadomości znacznika czasu to skrót oktetów wartości podpisu SignerInfo, bez przedrostka znacznika ani długości Abstract Syntax Notation One (ASN.1) (ETSI EN 319 122-1 §5.3; RFC 3161 Dodatek A). Token jest przenoszony jako nieobjęty podpisem atrybut id-aa-timeStampToken, z identyfikatorem obiektu (OID) 1.2.840.113549.1.9.16.2.14 (RFC 3161 Dodatek A), umieszczony w SignerInfo.unsignedAttrs [1] IMPLICIT (RFC 5652 §5.3). Ponieważ nieobjęte podpisem atrybuty nie są chronione przez podpis (RFC 5652 §5.4), podpisany skrót B-B, /ByteRange oraz bajty podpisu B-B pozostają niezmienione — B-T jedynie dołącza znacznik czasu. Certyfikat TSA jest identyfikowany za pomocą ESSCertIDv2 (RFC 5816 aktualizuje RFC 3161).
Zastrzeżenie U-1 (powtórzone przy deklaracji B-T). NextPDF nie potwierdza żadnej niezależnej certyfikacji ETSI EN 319 142-1 dla PAdES B-T. EN 319 142-1 nie znajduje się w korpusie weryfikacyjnym; wymaganie B-T
signature-time-stampzweryfikowano względem ETSI EN 319 122-1 §5.3 wraz z RFC 3161, RFC 5652, RFC 5816 oraz ISO 32000-2 §12.8. Obsługa profilu B-T nie jest certyfikacją zgodności ani ważności prawnej; niezależny walidator dokonuje takiego rozstrzygnięcia.
Poziom SignatureLevel::PAdES_B_T jest dostępny w Core: SignatureLevel::PAdES_B_T->requiresTimestamp() ma wartość true, ->isAvailableInEnvironment() ma wartość true, a ->requiresDss() ma wartość false — B-T nie wymaga Document Security Store. B-T ≠ B-LT ≠ B-LTA: znacznik czasu podpisu nie dodaje materiału walidacyjnego ani archiwalnego znacznika czasu; są to osobne, wyższe poziomy, które nie są tu tworzone.
Poniższy diagram przedstawia przepływ B-B, a następnie B-T w kolejności używanej przez silnik. ByteRange jest obliczany dopiero po zapisaniu całego pliku, więc końcowe przesunięcia nie zmieniają bajtów objętych skrótem. Następnie B-T dołącza jeden token RFC 3161 jako nieobjęty podpisem atrybut, pozostawiając podpisany skrót B-B nienaruszony.
Powierzchnia API
Dział zatytułowany „Powierzchnia API”Punktem wejścia konfiguracji jest Document::setSignature(CertificateInfo $certInfo, SignatureLevel $level = SignatureLevel::PAdES_B_B, ?TsaClient $tsaClient = null). To wywołanie zapisuje w dokumencie zamiar podpisania. Silnik podpisywania PAdES w Core (NextPDF\Security\Signature\DigitalSigner) tworzy podpis kryptograficzny. Ponieważ zestaw testów integracyjnych testuje ten silnik, a uruchamialny przykład korzysta z niego bezpośrednio, wynikiem jest rzeczywisty, możliwy do przetworzenia obiekt CMS. SignatureLevel::PAdES_B_T wymaga niepustego TsaClient; skonstruowanie sygnatariusza B-T bez tego klienta zgłasza SignatureException.
API wysokopoziomowe — jedno wywołanie, podpisany wynik
Dział zatytułowany „API wysokopoziomowe — jedno wywołanie, podpisany wynik”Najszybsza ścieżka to API wysokopoziomowe: skonfiguruj podpis w dokumencie, a następnie go zserializuj. Wewnętrznie korzysta ono z tego samego silnika PAdES w Core (DigitalSigner). To cienka warstwa ułatwiająca pracę nad niżejpoziomowym przebiegiem opisanym poniżej, a nie osobna ścieżka kodu.
<?php
declare(strict_types=1);
use NextPDF\Core\Document;use NextPDF\Security\Signature\CertificateInfo;use NextPDF\Security\Signature\SignatureLevel;use NextPDF\Security\Timestamp\TsaClient;
$certInfo = CertificateInfo::fromPkcs12( p12Path: __DIR__ . '/signer.p12', password: 'p12-passphrase',);
// PAdES B-B end to end: configure, then serialise.$doc = Document::createStandalone();$doc->addPage();$doc->setFont('helvetica', '', 12);$doc->cell(0, 10, 'Signed end to end.', newLine: true);$doc->setSignature(certInfo: $certInfo, level: SignatureLevel::PAdES_B_B);$doc->save(__DIR__ . '/signed.pdf'); // or output() to stream, getPdfData() for bytes
// PAdES B-T: pass a TsaClient on the same call — one RFC 3161// signature-time-stamp is added (see the TsaClient hardening notes below).$doc->setSignature( certInfo: $certInfo, level: SignatureLevel::PAdES_B_T, tsaClient: $tsa,);$doc->save(__DIR__ . '/signed-bt.pdf');Podobnie jak output() i getPdfData(), save() zapisuje wpis /Contents jako strukturę CMS SignedData zakodowaną w DER pod SubFilter ETSI.CAdES.detached (ISO 32000-2 §12.8, §12.7.5.5; RFC 5652). Wynik jest weryfikowalny na poziomie CMS — to poprawnie sformułowany obiekt CMS SignedData, który potrafi odczytać parser CMS — co nie jest tym samym, co zgodność z profilem bazowym ETSI EN 319 142-1 ani ważność prawna; takich rozstrzygnięć dokonuje niezależny walidator (zob. zastrzeżenie U-1 powyżej). Dla B-T wywołanie wysokopoziomowe dodaje dokładnie ten pojedynczy znacznik czasu RFC 3161 signature-time-stamp opisany w przeglądzie koncepcyjnym; przekazanie TsaClient to jedyna różnica względem B-B.
Skorzystaj z niżejpoziomowego przebiegu DigitalSigner poniżej, gdy potrzebujesz bezpośredniej kontroli nad algorytmem, danymi zakresu bajtów lub SignatureResult.
Przykład kodu — Szybki start
Dział zatytułowany „Przykład kodu — Szybki start”<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Security\Signature\CertificateInfo;use NextPDF\Security\Signature\DigitalSigner;use NextPDF\Security\Signature\SignatureAlgorithm;use NextPDF\Security\Signature\SignatureLevel;
$certInfo = CertificateInfo::fromPkcs12( p12Path: __DIR__ . '/signer.p12', password: 'p12-passphrase',);
// PAdES B-B — a CMS SignedData, no timestamp.$signer = new DigitalSigner( certInfo: $certInfo, level: SignatureLevel::PAdES_B_B, algorithm: SignatureAlgorithm::Pkcs1v15,);$result = $signer->sign($byteRangeData);
echo $result->hasTimestamp() ? "B-T\n" : "B-B (no timestamp)\n";Przykład kodu — Produkcja
Dział zatytułowany „Przykład kodu — Produkcja”Ten samodzielny program działa w ramach szkieletu cookbook. Odpowiada plikowi examples/36-sign-pades-b-b-and-b-t.php. Buduje dokument, konfiguruje go do podpisu PAdES, a następnie podpisuje na poziomie B-B i ponownie na poziomie B-T z klientem TSA. W środowisku produkcyjnym skieruj TsaClient na rzeczywisty punkt końcowy RFC 3161 przez wzmocnionego klienta PSR-18: klienta HTTP świadomego bezpieczeństwa, który przypina SubjectPublicKeyInfo (SPKI) TSA i bezpiecznie rozwiązuje Domain Name System (DNS). Aby program pozostał offline i deterministyczny, używa testowego, fałszywego klienta TSA z repozytorium. Fałszywy klient TSA zwraca strukturalnie poprawny RFC 3161 TimeStampResp.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;use NextPDF\Security\Signature\CertificateInfo;use NextPDF\Security\Signature\DigitalSigner;use NextPDF\Security\Signature\SignatureAlgorithm;use NextPDF\Security\Signature\SignatureLevel;use NextPDF\Security\Timestamp\TsaClient;use NextPDF\Tests\Support\FakeTsaHttpClient;
// In your application, build CertificateInfo from your own signing material:// CertificateInfo::fromPkcs12($p12Path, $passphrase) — a .p12/.pfx bundle// CertificateInfo::fromFiles($certPem, $keyPem, $pass) — separate PEM files// This program uses the repository RSA-2048 test fixtures so it is offline.$certDir = __DIR__ . '/tests/Fixtures/Certificates';$certPath = $certDir . '/test-rsa-2048-cert.pem';$keyPath = $certDir . '/test-rsa-2048-key.pem';
if (!is_file($certPath) || !is_file($keyPath)) { fwrite(STDERR, "Certificate fixtures absent. Run tests/Fixtures/Certificates/generate.sh\n"); exit(1);}
$certInfo = new CertificateInfo( certificate: (string) file_get_contents($certPath), privateKey: (string) file_get_contents($keyPath),);
// Build the document and record the signing intent on it. The ByteRange// digest input is the document bytes with the /Contents placeholder// excluded (ISO 32000-2 §12.8); getPdfData() yields the bytes to hash.$doc = Document::createStandalone();$doc->setTitle('Signed Invoice 2026-0042');$doc->setAuthor('NextPDF Cookbook');$doc->addPage();$doc->setFont('helvetica', '', 12);$doc->cell(0, 10, 'This document is configured for a PAdES signature.', newLine: true);$doc->setSignature(certInfo: $certInfo, level: SignatureLevel::PAdES_B_B);
$byteRangeData = $doc->getPdfData();
// --- PAdES B-B: a CMS SignedData, no timestamp ---$bb = (new DigitalSigner( certInfo: $certInfo, level: SignatureLevel::PAdES_B_B, algorithm: SignatureAlgorithm::Pkcs1v15,))->sign($byteRangeData);
// --- PAdES B-T: B-B + one RFC 3161 signature-time-stamp ---// In production, build the TsaClient with your TSA endpoint and a hardened// PSR-18 client (use the security-aware HTTP client for SSRF/DNS pinning):// $tsa = new TsaClient(// tsaUrl: 'https://tsa.example.com/timestamp',// httpClient: $hardenedPsr18Client,// );// Here the offline fake TSA client keeps the program network-free.$tsa = new TsaClient( tsaUrl: 'https://tsa.example.com/timestamp', httpClient: new FakeTsaHttpClient(),);$bt = (new DigitalSigner( certInfo: $certInfo, tsaClient: $tsa, level: SignatureLevel::PAdES_B_T, algorithm: SignatureAlgorithm::Pkcs1v15,))->sign($byteRangeData);
// B-T = B-B + a single timestamp token. The B-B signed digest is unchanged;// $bt->timestampToken holds the DER-encoded RFC 3161 token.printf("PAdES B-B CMS: %d bytes, timestamp=%s\n", $bb->getSize(), $bb->hasTimestamp() ? 'yes' : 'no');printf( "PAdES B-T CMS: %d bytes, timestamp=%s (%d-byte RFC 3161 token)\n", $bt->getSize(), $bt->hasTimestamp() ? 'yes' : 'no', strlen($bt->timestampToken),);echo "B-T = B-B + one RFC 3161 signature-time-stamp (unsigned attribute).\n";
// The harness sets NEXTPDF_COOKBOOK_OUTPUT and runs this script under the// semantic profile (the signed CMS/timestamp bytes are inherently// non-reproducible and are asserted by the PHPUnit harness, not a byte hash).$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');file_put_contents($out !== false && $out !== '' ? $out : __DIR__ . '/signed-invoice.pdf', $byteRangeData);Oczekiwane STDOUT (rozmiary różnią się w zależności od certyfikatu i tokenu TSA):
PAdES B-B CMS: <n> bytes, timestamp=noPAdES B-T CMS: <n> bytes, timestamp=yes (<m>-byte RFC 3161 token)B-T = B-B + one RFC 3161 signature-time-stamp (unsigned attribute).Zastrzeżenie U-1 (umieszczone przy produkcyjnej deklaracji B-T). NextPDF nie potwierdza żadnej niezależnej certyfikacji ETSI EN 319 142-1 dla PAdES B-T. EN 319 142-1 nie znajduje się w korpusie weryfikacyjnym; wymaganie B-T
signature-time-stampzweryfikowano względem ETSI EN 319 122-1 §5.3 wraz z RFC 3161, RFC 5652, RFC 5816 oraz ISO 32000-2 §12.8. Obsługa profilu B-T nie jest certyfikacją zgodności ani ważności prawnej; takiego rozstrzygnięcia dokonuje niezależny walidator.
Przypadki brzegowe i pułapki
Dział zatytułowany „Przypadki brzegowe i pułapki”- B-T bez klienta TSA. Skonstruowanie sygnatariusza B-T
DigitalSignerbezTsaClientzgłaszaSignatureException(TSA jest wymagany dla B-T). Zadbaj o konfigurację TSA przed podpisaniem. - Osiągalność TSA. Dla każdego podpisu B-T wykonuje na żywo wymianę RFC 3161. Przerwa w działaniu TSA oznacza brak podpisu B-T. Zastosuj wyłącznik (circuit breaker) oraz umowę o poziomie usług (SLA) TSA dopasowaną do wymaganej przepustowości;
TsaClientprzyjmuje wyłącznik. - Wzmacnianie klienta HTTP TSA. Skieruj
TsaClientna klienta PSR-18, który przypina SubjectPublicKeyInfo (SPKI) TSA (format RFC 7469) i bezpiecznie rozwiązuje Domain Name System (DNS);TsaClient::extractPublicKeyPin()wyprowadza przypięcie z certyfikatu TSA. - B-T to nie B-LT/B-LTA. Znacznik czasu podpisu nie osadza materiału walidacyjnego (certyfikatów, Online Certificate Status Protocol (OCSP), listy unieważnionych certyfikatów (CRL)) ani archiwalnego znacznika czasu. Są to poziomy B-LT/B-LTA, których ten przepis nie tworzy.
- Konflikt linearyzacji.
enableLinearization()oraz skonfigurowany podpis wzajemnie się wykluczają — każde z tych wywołań zgłaszaInvalidConfigException, gdy to drugie jest już ustawione. - Klucze HSM. W przypadku klucza przechowywanego w sprzętowym module bezpieczeństwa (HSM) zbuduj
CertificateInfoza pomocąCertificateInfo::fromHsm(); klucz prywatny nigdy nie trafia do pamięci procesu. Kontrakt sygnatariusza PKCS#11 należy do Core; działający dostawca jest w edycji Premium.
Wydajność
Dział zatytułowany „Wydajność”Podpis B-B to lokalna operacja CMS. B-T dodaje jedną synchroniczną wymianę HTTP RFC 3161 z TSA na każdy podpis. Przy zadaniach wsadowych uwzględnij opóźnienie TSA oraz limity szybkości. Użyj TsaClient zabezpieczonego wyłącznikiem.
Uwagi dotyczące bezpieczeństwa
Dział zatytułowany „Uwagi dotyczące bezpieczeństwa”Utworzony podpis nie jest podpisem zaufanym. To, czy podpis przejdzie weryfikację, zależy od certyfikatu, jego punktu zakotwiczenia zaufania oraz polityki weryfikatora, a te pozostają poza zakresem tej biblioteki. Szyfrowanie chroni poufność, a nie integralność; podpisywanie chroni integralność i autentyczność, a nie poufność. Traktuj kontrolę nad kluczami jako główne ryzyko: programowy klucz w pamięci procesu jest tylko tak bezpieczny, jak host.
Rezydencja danych i ograniczanie ryzyka PII
Dział zatytułowany „Rezydencja danych i ograniczanie ryzyka PII”Operacja podpisywania odbywa się w procesie; bajty dokumentu i klucz prywatny nie opuszczają hosta z wyjątkiem wymiany typu B-T z TSA, która wysyła wyłącznie odcisk wiadomości (skrót wartości podpisu), nigdy zawartości dokumentu (RFC 3161 §2.4.1 MessageImprint). Do TSA nie jest przesyłany żaden tekst dokumentu ani dane umożliwiające identyfikację osoby (PII). Wybierz TSA, którego jurysdykcja odpowiada polityce rezydencji danych.
Bezpieczna telemetria i czyszczenie dzienników
Dział zatytułowany „Bezpieczna telemetria i czyszczenie dzienników”DigitalSigner przyjmuje opcjonalny rejestrator PSR-3. Rejestrowane są algorytm i poziom, a nie materiał klucza ani bajty podpisu. Parametry password w CertificateInfo i TsaClient są oznaczone jako #[SensitiveParameter], więc hasła są usuwane ze śladów stosu. Nie rejestruj SignatureResult::$cmsSignedData ani $timestampToken.
Model zagrożeń
Dział zatytułowany „Model zagrożeń”Uwzględnione: zmodyfikowanie danych wejściowych po podpisaniu (wykrywane przez skrót zakresu bajtów), kompromitacja klucza (poza zakresem biblioteki, ponieważ kontrola nad kluczami to odpowiedzialność integratora), podszywanie się pod TSA (łagodzone przez przypinanie SPKI w kliencie HTTP TSA) oraz obniżenie poziomu między poziomami (enum poziomu jest jawny; silnik nie obniża po cichu B-T do B-B). Nie potwierdza się: braku podatności ani tego, że jakikolwiek powstały podpis jest prawnie ważny.
Zachowanie w trybie FIPS
Dział zatytułowany „Zachowanie w trybie FIPS”Prymitywy podpisywania są dostarczane przez OpenSSL. W kompilacji OpenSSL zweryfikowanej pod kątem Federal Information Processing Standards (FIPS) operacje RSA/ECDSA i SHA-256 przebiegają przez dostawcę FIPS; NextPDF sam nie potwierdza walidacji FIPS. CryptoCapabilities raportuje prymitywy dostępne na hoście; zweryfikuj łańcuch dostawców OpenSSL we własnym wdrożeniu.
Zgodność
Dział zatytułowany „Zgodność”| Stwierdzenie | Specyfikacja | Klauzula | reference_id |
|---|---|---|---|
| Skrót zakresu bajtów obejmuje plik i wyklucza wartość podpisu. | ISO 32000-2 | §12.8.1 | |
Contents zawiera DER CMS SignedData; Contents znacznika czasu dokumentu zawiera TimeStampToken. | ISO 32000-2 | §12.8.1 | |
Contents to łańcuch szesnastkowy dopełniony względem skrótu zakresu bajtów. | ISO 32000-2 | §12.8.1 | |
| Odcisk signature-time-stamp to skrót oktetów wartości podpisu SignerInfo (bez ASN.1 tag/length). | ETSI EN 319 122-1 | §5.3 | |
| Wartość signature-time-stamp to SignatureTimeStampToken. | ETSI EN 319 122-1 | §6 | |
MessageImprint ::= SEQUENCE { hashAlgorithm, hashedMessage }. | RFC 3161 | §2.4.1 | |
Odcisk znacznika czasu podpisu to skrót pola podpisu SignerInfo; SignatureTimeStampToken ::= TimeStampToken. | RFC 3161 | Dod. A | |
OID id-aa-timeStampToken to 1.2.840.113549.1.9.16.2.14. | RFC 3161 | Dod. A | |
SignerInfo niesie unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL. | RFC 5652 | §5.3 | |
| Nieobjęte podpisem atrybuty nie są chronione przez podpis; podpisany skrót B-B pozostaje niezmieniony. | RFC 5652 | §5.4 | |
| RFC 5816 aktualizuje RFC 3161; ESSCertIDv2 identyfikuje certyfikat TSA bez SHA-1. | RFC 5816 | §1 |
Ten przepis opisuje, jak NextPDF tworzy podpis B-B i B-T. Nie stwierdza, że jakikolwiek powstały podpis jest prawnie ważny ani że spełniono wymagania zgodności z PAdES; takich rozstrzygnięć dokonuje niezależny walidator.
Kontekst komercyjny
Dział zatytułowany „Kontekst komercyjny”PAdES B-LT i B-LTA (materiał walidacyjny DSS oraz pętla archival-timestamp) oraz kontrola nad kluczami PKCS#11 HSM są dostępne w edycjach Pro i Enterprise. Ten przepis obejmuje wyłącznie B-B i B-T; wyższe poziomy to odrębne, osobno weryfikowane funkcje, które pozostają poza zakresem tego materiału.