Przejdź do głównej zawartości

Kontrakty / podpisywanie

Domena podpisywania obejmuje sześć kontraktów. Definiują one sposób tworzenia podpisu w formacie Cryptographic Message Syntax (CMS), stosowania znacznika czasu zgodnego z Request for Comments (RFC) 3161, podpisywania kluczem ze sprzętowego modułu bezpieczeństwa (HSM) oraz włączania walidacji długoterminowej (LTV). Core publikuje kontrakty; edycje Pro i Enterprise dostarczają implementacje produkcyjne.

Okno terminala
composer require nextpdf/core:^3

Podpis cyfrowy w formacie Portable Document Format (PDF) to struktura CMS SignedData przechowywana w słowniku podpisu. Wpis Contents zawiera strukturę zakodowaną zgodnie z Distinguished Encoding Rules (DER). Wpis ByteRange wskazuje zakresy bajtów objęte skrótem. Skrót obejmuje cały plik i wyklucza samą wartość podpisu; zobacz International Organization for Standardization (ISO) 32000-2 §12.8.1. Struktura CMS jest zgodna z RFC 5652 §5.1: wersja, algorytmy skrótu, opakowana zawartość oraz informacje o podpisującym.

SignerInterface to podstawowy kontrakt. Tworzy strukturę CMS SignedData dla zakresu bajtów, opatruje wartość podpisu znacznikiem czasu oraz zgłasza, czy obsługuje LTV. Obejmuje bazowe poziomy PDF Advanced Electronic Signatures (PAdES) od B-B do B-LTA, zdefiniowane przez European Telecommunications Standards Institute (ETSI) EN 319 142. Każdy wyższy poziom dodaje materiał walidacyjny. B-B zawiera podpis bazowy. B-T dodaje znacznik czasu podpisu. B-LT dodaje dane dotyczące unieważnień. B-LTA dodaje archiwalny znacznik czasu.

HsmSignerInterface podpisuje kluczem przechowywanym w sprzętowym module bezpieczeństwa. Klucz prywatny nigdy nie opuszcza granicy sprzętowej. Kontrakt zwraca certyfikat podpisującego oraz łańcuch certyfikatów zakodowane w DER, dzięki czemu warstwa CMS może zbudować strukturę. DeferredSignerInterface rozszerza SignerInterface na potrzeby podpisywania asynchronicznego. Wywołujący przesyła dane, otrzymuje identyfikator zadania, odpytuje o ukończenie i pobiera wynik. Użyj go, gdy zdalny HSM lub chmurowa usługa kluczy nie zwraca wyniku natychmiast.

TimestampProviderInterface żąda tokenu znacznika czasu zgodnego z RFC 3161. Protokół polega na wymianie żądanie–odpowiedź z urzędem znacznika czasu (Time-Stamping Authority, TSA); zobacz RFC 3161 §2.4. Wartość messageImprint w tokenie to skrót wartości podpisu; zobacz RFC 3161 §2.4.2. LtvManagerInterface włącza LTV. Zbiera łańcuch certyfikatów, pobiera odpowiedzi Online Certificate Status Protocol (OCSP) oraz listy unieważnionych certyfikatów (CRL), buduje magazyn zabezpieczeń dokumentu (Document Security Store, DSS) i dodaje znacznik czasu dokumentu dla B-LTA. CryptoPolicyInterface kontroluje dozwolone algorytmy skrótu, podpisu i szyfrowania oraz siłę kluczy, zanim zostanie uruchomiona jakakolwiek operacja kryptograficzna.

TypRodzajKluczowe składoweStabilnośćOd wersji
SignerInterfaceinterfejssign(string): SignatureResult, timestamp(string): string, supportsLtv(): boolstabilny1.0.0
HsmSignerInterfaceinterfejssign(string, string): string, getCertificateDer(), getCertificateChainDer(), getPublicKeyAlgorithm()stabilny1.0.0
DeferredSignerInterfaceinterfejssubmitForSigning(string): string, retrieveSignature(string), isComplete(string) (extends SignerInterface)eksperymentalny3.0.0
TimestampProviderInterfaceinterfejsgetTimestamp(string): stringeksperymentalny3.0.0
LtvManagerInterfaceinterfejsenableLtv(...), addDocumentTimestamp(...)stabilny1.10.0
CryptoPolicyInterfaceinterfejsisHashAlgorithmAllowed(), isSignatureAlgorithmAllowed(), isEncryptionAlgorithmAllowed(), isKeyStrengthAllowed(), getPreferredHashAlgorithm(), getName()stabilny1.9.0

SignerInterface::sign() zwraca obiekt NextPDF\Security\Signature\SignatureResult. Publiczna właściwość $cmsSignedData zawiera dane CMS zakodowane w DER. Publiczna właściwość $digestHex zawiera skrót SHA-256. Publiczna właściwość $timestampToken zawiera opcjonalny token TSA. Metody dostępowe to toHex() / toHexPadded(int) / getSize() / hasTimestamp(). HsmSignerInterface::sign() zwraca bajty zakodowane w DER dla Rivest-Shamir-Adleman (RSA) oraz surowe bajty r‖s dla Elliptic Curve Digital Signature Algorithm (ECDSA). Argument algorytmu przyjmuje identyfikatory OpenSSL.

examples/contracts/signing-quickstart.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\SignerInterface;
/**
* Sign a byte range with any SignerInterface implementation.
*
* @param SignerInterface $signer A core or Premium signer.
* @param string $byteRange The PDF byte range to sign.
*
* @return string Hex-encoded CMS SignedData for the PDF /Contents field.
*/
function signByteRange(SignerInterface $signer, string $byteRange): string
{
$result = $signer->sign($byteRange);
return $result->toHex();
}

SignerInterface::sign() zwraca obiekt SignatureResult. toHex() zwraca ciąg szesnastkowy, który komponent zapisujący umieszcza w polu /Contents; surowe bajty DER są dostępne w publicznej właściwości $cmsSignedData. Funkcja zależy od kontraktu, a nie od konkretnej klasy. Kontrakt spełniają zarówno testowy podpisujący z Core, jak i podpisujący HSM z edycji Premium.

examples/contracts/signing-production.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\CryptoPolicyInterface;
use NextPDF\Contracts\SignerInterface;
use NextPDF\Contracts\TimestampProviderInterface;
use NextPDF\Exception\NextPdfException;
use Psr\Log\LoggerInterface;
final readonly class TimestampedSigningService
{
public function __construct(
private SignerInterface $signer,
private TimestampProviderInterface $timestamps,
private CryptoPolicyInterface $policy,
private LoggerInterface $logger,
) {}
/**
* Sign a byte range, then timestamp the CMS structure.
*
* @param string $byteRange The PDF byte range to sign.
*
* @return array{cms: string, digest: string, tst: string}
*/
public function sign(string $byteRange): array
{
if (!$this->policy->isHashAlgorithmAllowed($this->policy->getPreferredHashAlgorithm())) {
throw new \LogicException('Preferred hash rejected by crypto policy.');
}
try {
$signature = $this->signer->sign($byteRange);
$token = $this->timestamps->getTimestamp($signature->cmsSignedData);
return [
'cms' => $signature->toHex(),
'digest' => $signature->digestHex,
'tst' => $token,
];
} catch (NextPdfException $e) {
$this->logger->error('Signing failed', [
'policy' => $this->policy->getName(),
'error' => $e->getMessage(),
]);
throw $e;
}
}
}

Do usługi wstrzykiwane są trzy kontrakty. Przed podpisaniem sprawdza ona politykę kryptograficzną. SignatureResult udostępnia bajty CMS w publicznej właściwości $cmsSignedData, skrót SHA-256 w $digestHex oraz ciąg szesnastkowy /Contents poprzez toHex(). Dostawca znacznika czasu otrzymuje bajty CMS i zwraca token zakodowany w DER. Blok catch zapisuje w dzienniku nazwę polityki i ponownie zgłasza wyjątek. Nigdy nie pomija błędu po cichu.

  • Skrót zakresu bajtów musi wykluczać wartość podpisu. Skrót obejmujący wpis Contents tworzy podpis, którego nigdy nie da się zweryfikować; zobacz ISO 32000-2 §12.8.1.
  • SignerInterface::supportsLtv() zgłasza możliwość, a nie stan. Podpisujący może obsługiwać LTV i mimo to tworzyć podpis B-B, gdy nie skonfigurowano usługi znacznika czasu.
  • DeferredSignerInterface::retrieveSignature() zwraca null do czasu ukończenia zadania. Najpierw odpytaj isComplete(), aby nie przesyłać ładunku przy każdym sprawdzeniu. Pobieranie jest idempotentne, gdy wynik już istnieje.
  • LtvManagerInterface::addDocumentTimestamp() musi działać po zapisaniu magazynu zabezpieczeń dokumentu. Wywołanie go jako pierwszego tworzy nieprawidłową strukturę B-LTA.
  • CryptoPolicyInterface zwraca true dla każdego algorytmu, gdy nie ustawiono żadnej polityki. W środowisku regulowanym ustaw jawną politykę; nie polegaj na otwartej wartości domyślnej.
  • HsmSignerInterface::getCertificateChainDer() wyklucza certyfikat podpisującego. Użyj getCertificateDer() dla certyfikatu końcowego podpisującego, a metody łańcucha dla certyfikatów pośrednich.

Koszt podpisywania wynika głównie z operacji kryptograficznej oraz ewentualnego przebiegu sieciowego, a nie z samego kontraktu. Lokalny podpis programowy zwykle zajmuje pojedyncze milisekundy. Podpis HSM dodaje cykl komunikacji z urządzeniem. Znacznik czasu dodaje przebieg sieciowy do urzędu znacznika czasu. Walidacja długoterminowa dodaje jedno pobranie OCSP lub CRL na każdy certyfikat w łańcuchu. Wartość performance_budget wynosząca 1500 ms czasu rzeczywistego obejmuje pojedynczy podpis ze znacznikiem czasu ze zdalnego TSA na rozgrzanym połączeniu. Walidacja długoterminowa w przypadku wolnego punktu końcowego unieważnień przekracza ten budżet i powinna działać poza ścieżką żądania. Profil odtwarzalności to structural, a nie bitwise. Znacznik czasu osadza moment podpisania, więc dwa uruchomienia różnią się bajtami znacznika czasu, podczas gdy struktura dokumentu pozostaje identyczna.

Kontrakty podpisywania są podstawową granicą kryptograficzną silnika, dlatego model zagrożeń jest jawny. Pierwszą kwestią jest ochrona klucza: HsmSignerInterface przechowuje klucz prywatny wewnątrz granicy sprzętowej, a kontrakt nigdy nie ujawnia materiału klucza. Drugą jest osłabienie algorytmów: CryptoPolicyInterface blokuje słabe skróty i krótkie klucze przed operacją, pozwalając wdrożeniu wymusić profil Federal Information Processing Standards (FIPS) 140-3 lub electronic identification, authentication, and trust services (eIDAS) bez rozgałęziania silnika. Trzecią jest zaufanie do znacznika czasu: token RFC 3161 jest tak godny zaufania, jak urząd znacznika czasu, więc kontrakt dostawcy można wstrzyknąć, a wdrożenie wskazuje własny urząd. Czwartą jest walidacja długoterminowa: materiał dotyczący unieważnień pobiera się w chwili podpisywania i przechowuje w magazynie zabezpieczeń dokumentu, dzięki czemu weryfikacja przetrwa wygaśnięcie certyfikatu. Traktuj każde wejście podpisującego jako niezaufane. Silnik oblicza zakres bajtów; nigdy nie jest on przyjmowany od wywołującego. Ta strona jest oznaczona jako export_control_class: legal-review-required, ponieważ kontrakty regulują podpisywanie kryptograficzne. Tekst parafrazuje wszystkie źródła normatywne i nie cytuje żadnego, zgodnie z higieną cytowań.

TwierdzenieNormaKlauzulaDowód
Wpis Contents słownika podpisu przechowuje wartość podpisu jako strukturę CMS SignedData zakodowaną w DER lub jako TimeStampToken.ISO 32000-2§12.8.1
Skrót jest obliczany dla zakresu bajtów zdefiniowanego przez tablicę ByteRange i wyklucza wartość podpisu.ISO 32000-2§12.8.1,
Magazyn zabezpieczeń dokumentu przechowuje materiał walidacji długoterminowej z informacjami powiązanymi z walidacją (validation-related information, VRI) oraz wpisami OCSP, CRL i certyfikatów.ISO 32000-2§12.8.4.3,
Walidacja długoterminowa PAdES umieszcza dane walidacyjne w słownikach DSS i VRI.ETSI EN 319 142-2§6.3,
Token znacznika czasu RFC 3161 wiąże skrót wartości podpisu za pomocą messageImprint w wymianie żądanie–odpowiedź z TSA.RFC 3161§2.4.2, §2.4,
Sekwencja CMS SignedData zawiera wersję, algorytmy skrótu, opakowaną zawartość oraz informacje o podpisującym.RFC 5652§5.1

Wszystkie klauzule są sparafrazowane. NextPDF nie odtwarza tekstu normatywnego. Autorytatywne brzmienie znajduje się w opublikowanych normach.

Core publikuje i zamraża kontrakty podpisywania. Edycje Pro i Enterprise dostarczają produkcyjne implementacje dla HsmSignerInterface, LtvManagerInterface oraz podpisującego odroczonego, w tym sprzętową integrację Public-Key Cryptography Standards #11 (PKCS#11) oraz PAdES B-LT i B-LTA. Core rozwiązuje te implementacje w czasie wykonania za pomocą class_exists() i rzutuje je na kontrakt, dzięki czemu silnik open source nie ma żadnej zależności komercyjnej, a API nie zmienia się przy aktualizacji.