Zum Inhalt springen

Contracts / Signatur

Die Signing-Domain umfasst sechs Contracts. Sie definieren, wie Sie eine CMS-Signatur erzeugen, einen RFC 3161-Zeitstempel anwenden, mit einem Hardware-Schlüssel signieren und die Langzeitvalidierung aktivieren. Core veröffentlicht die Contracts; die Pro- und Enterprise-Editionen liefern den Produktivcode.

Terminal-Fenster
composer require nextpdf/core:^3

Eine digitale PDF-Signatur ist eine CMS-SignedData-Struktur, die im Signatur-Dictionary gespeichert wird. Der Eintrag Contents enthält die DER-codierte Struktur. Der Eintrag ByteRange benennt die Byte-Bereiche, die der Digest abdeckt. Der Digest umfasst die gesamte Datei und schließt den Signaturwert selbst aus; siehe ISO 32000-2 §12.8.1. Die CMS-Struktur folgt RFC 5652 §5.1: einer Sequenz aus Version, Digest-Algorithmen, eingekapseltem Inhalt und Signer-Information.

SignerInterface ist der zentrale Contract. Er erzeugt die CMS-SignedData für einen Byte-Bereich, versieht einen Signaturwert mit einem Zeitstempel und meldet, ob er Langzeitvalidierung unterstützt. Er bildet die PAdES-Baseline-Stufen B-B bis B-LTA ab, wie in ETSI EN 319 142 definiert. Jede höhere Stufe ergänzt weiteres Material. B-B enthält die Basis-Signatur. B-T ergänzt einen Signatur-Zeitstempel. B-LT ergänzt Sperrdaten. B-LTA ergänzt einen Archiv-Zeitstempel.

HsmSignerInterface signiert mit einem Schlüssel, der in einem Hardware-Security-Modul gespeichert ist. Der private Schlüssel verlässt die Hardwaregrenze nicht. Der Contract gibt das Signer-Zertifikat und die Zertifikatskette in DER-Form zurück, damit die CMS-Schicht die Struktur aufbauen kann. DeferredSignerInterface erweitert SignerInterface für asynchrones Signieren. Der Aufrufer übermittelt Daten, erhält eine Job-Kennung, fragt den Abschluss ab und ruft anschließend das Ergebnis ab. Verwenden Sie ihn, wenn ein entferntes HSM oder ein Cloud-Schlüsseldienst nicht sofort ein Ergebnis zurückgibt.

TimestampProviderInterface fordert ein RFC 3161-Zeitstempel-Token an. Das Protokoll besteht aus einer Anfrage und einer Antwort, die mit einer Time-Stamping Authority ausgetauscht werden; siehe RFC 3161 §2.4. Der messageImprint im Token ist ein Hash des Signaturwerts; RFC 3161 §2.4.2. LtvManagerInterface aktiviert die Langzeitvalidierung. Er sammelt die Zertifikatskette, holt OCSP- und CRL-Antworten ein, baut den Document Security Store auf und fügt einen Dokument-Zeitstempel für B-LTA hinzu. CryptoPolicyInterface steuert, welche Hash-, Signatur- und Verschlüsselungsalgorithmen sowie welche Schlüsselstärken erlaubt sind, bevor eine kryptografische Operation ausgeführt wird.

TypArtZentrale MitgliederStabilitätSeit
SignerInterfaceinterfacesign(string): SignatureResult, timestamp(string): string, supportsLtv(): boolstable1.0.0
HsmSignerInterfaceinterfacesign(string, string): string, getCertificateDer(), getCertificateChainDer(), getPublicKeyAlgorithm()stable1.0.0
DeferredSignerInterfaceinterfacesubmitForSigning(string): string, retrieveSignature(string), isComplete(string) (erweitert SignerInterface)experimental3.0.0
TimestampProviderInterfaceinterfacegetTimestamp(string): stringexperimental3.0.0
LtvManagerInterfaceinterfaceenableLtv(...), addDocumentTimestamp(...)stable1.10.0
CryptoPolicyInterfaceinterfaceisHashAlgorithmAllowed(), isSignatureAlgorithmAllowed(), isEncryptionAlgorithmAllowed(), isKeyStrengthAllowed(), getPreferredHashAlgorithm(), getName()stable1.9.0

SignerInterface::sign() gibt ein NextPDF\Security\Signature\SignatureResult zurück. Die öffentliche Eigenschaft $cmsSignedData enthält das DER-codierte CMS. Die öffentliche Eigenschaft $digestHex enthält den SHA-256-Digest. Die öffentliche Eigenschaft $timestampToken enthält das optionale TSA-Token. Die Accessoren sind toHex() / toHexPadded(int) / getSize() / hasTimestamp(). HsmSignerInterface::sign() gibt DER-codierte Bytes für RSA und rohe r‖s-Bytes für ECDSA zurück. Das Algorithmusargument verwendet OpenSSL-Bezeichner.

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() gibt ein SignatureResult zurück. toHex() gibt die Hex-Zeichenkette zurück, die der Writer in das Feld /Contents einsetzt; die rohen DER-Bytes liegen in der öffentlichen Eigenschaft $cmsSignedData. Die Funktion hängt vom Contract ab, nicht von einer konkreten Klasse. Sowohl ein Core-Test-Signer als auch ein Premium-HSM-Signer erfüllen ihn.

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;
}
}
}

Der Dienst injiziert drei Contracts. Die Crypto-Policy wird vor der Signieroperation konsultiert. SignatureResult stellt die CMS-Bytes in der öffentlichen Eigenschaft $cmsSignedData bereit, den SHA-256-Digest in $digestHex sowie toHex() für die Hex-Zeichenkette von /Contents. Der Timestamp-Provider erhält die CMS-Bytes und gibt ein DER-codiertes Token zurück. Der Catch-Block protokolliert den Policy-Namen und wirft die Ausnahme erneut. Er verschluckt den Fehler nie.

  • Der Byte-Bereichs-Digest muss den Signaturwert ausschließen. Ein Digest, der den Eintrag Contents abdeckt, erzeugt eine Signatur, die sich niemals verifizieren lässt; ISO 32000-2 §12.8.1.
  • SignerInterface::supportsLtv() meldet die Fähigkeit, nicht den Zustand. Ein Signer kann die Langzeitvalidierung unterstützen und trotzdem eine B-B-Signatur erzeugen, wenn kein Zeitstempeldienst konfiguriert ist.
  • DeferredSignerInterface::retrieveSignature() liefert null, bis der Job abgeschlossen ist. Fragen Sie zuerst isComplete() ab, um nicht bei jeder Prüfung die Nutzlast zu übertragen. Der Abruf ist idempotent, sobald ein Ergebnis vorliegt.
  • LtvManagerInterface::addDocumentTimestamp() muss ausgeführt werden, nachdem der Document Security Store geschrieben wurde. Wird sie zuerst aufgerufen, entsteht eine ungültige B-LTA-Struktur.
  • CryptoPolicyInterface liefert true für jeden Algorithmus, wenn keine Policy gesetzt ist. Setzen Sie in einer regulierten Umgebung eine explizite Policy; verlassen Sie sich nicht auf den offenen Standardwert.
  • HsmSignerInterface::getCertificateChainDer() schließt das Signer-Zertifikat aus. Verwenden Sie getCertificateDer() für das Signer-Blattzertifikat und die Kettenmethode für die Zwischenzertifikate.

Die Signierkosten werden von der kryptografischen Operation und einem etwaigen Netzwerk-Roundtrip dominiert, nicht vom Contract. Eine lokale Software-Signatur liegt im einstelligen Millisekundenbereich. Eine HSM-Signatur fügt den Geräte-Roundtrip hinzu. Ein Zeitstempel fügt einen Netzwerk-Roundtrip zur Time-Stamping Authority hinzu. Die Langzeitvalidierung fügt einen OCSP- oder CRL-Abruf pro Zertifikat in der Kette hinzu. Das performance_budget von 1500 ms Wall-Time deckt eine einzelne zeitgestempelte Signatur mit einer entfernten TSA bei warmer Verbindung ab. Eine Langzeitvalidierung über einen langsamen Sperr-Endpunkt überschreitet es und sollte außerhalb des Request-Pfads laufen. Das Reproduzierbarkeitsprofil ist structural, nicht bitwise. Ein Zeitstempel bettet den Signaturzeitpunkt ein, sodass sich zwei Läufe in den Zeitstempel-Bytes unterscheiden, während die Dokumentstruktur identisch bleibt.

Die Signing-Contracts sind die primäre kryptografische Grenze der Engine; deshalb ist das Bedrohungsmodell explizit. Die Schlüsselverwahrung ist das erste Anliegen: HsmSignerInterface hält den privaten Schlüssel innerhalb der Hardwaregrenze, und der Contract gibt nie Schlüsselmaterial preis. Algorithmus-Downgrades sind das zweite Anliegen: CryptoPolicyInterface blockiert schwache Hashes und kurze Schlüssel vor der Operation, sodass ein Deployment ein FIPS 140-3- oder eIDAS-Profil ohne Fork der Engine durchsetzen kann. Das Vertrauen in Zeitstempel ist das dritte Anliegen: Ein RFC 3161-Token ist nur so vertrauenswürdig wie die Time-Stamping Authority, daher ist der Provider-Contract injizierbar, und ein Deployment legt seine eigene Authority fest. Die Langzeitvalidierung ist das vierte Anliegen: Sperrmaterial wird zum Signierzeitpunkt geholt und im Document Security Store abgelegt, sodass die Verifikation den Ablauf des Zertifikats überdauert. Behandeln Sie jede Signer-Eingabe als nicht vertrauenswürdig. Der Byte-Bereich wird von der Engine berechnet, nie vom Aufrufer übernommen. Diese Seite ist mit export_control_class: legal-review-required markiert, weil die Contracts das kryptografische Signieren steuern. Die Prosa paraphrasiert alle normativen Quellen und zitiert keine davon, entsprechend der Zitierhygiene.

AussageStandardAbschnittNachweis
Der Signaturwert wird DER-codiert im Eintrag Contents des Signatur-Dictionarys gespeichert, als CMS SignedData oder als TimeStampToken.ISO 32000-2§12.8.1
Der Digest wird über den Byte-Bereich berechnet, der durch das Array ByteRange definiert ist, und schließt den Signaturwert aus.ISO 32000-2§12.8.1,
Langzeitvalidierungsmaterial wird im Document Security Store mit VRI-, OCSP-, CRL- und Zertifikatseinträgen geführt.ISO 32000-2§12.8.4.3,
Die PAdES-Langzeitvalidierung legt Validierungsdaten in den DSS- und VRI-Dictionarys ab.ETSI EN 319 142-2§6.3,
Ein RFC 3161-Zeitstempel-Token bindet über messageImprint einen Hash des Signaturwerts; der Austausch erfolgt über eine TSA-Anfrage und -Antwort.RFC 3161§2.4.2, §2.4,
Die CMS-SignedData-Sequenz trägt Version, Digest-Algorithmen, eingekapselten Inhalt und Signer-Information.RFC 5652§5.1

Alle Klauseln sind paraphrasiert. NextPDF gibt keinen normativen Text wieder. Konsultieren Sie die veröffentlichten Standards für den maßgeblichen Wortlaut.

Core veröffentlicht die Signing-Contracts und friert sie ein. Die Produktivimplementierungen hinter HsmSignerInterface, LtvManagerInterface und dem Deferred-Signer werden in den Pro- und Enterprise-Editionen ausgeliefert, einschließlich PKCS#11-Hardware-Integration sowie PAdES B-LT und B-LTA. Core löst sie zur Laufzeit mit class_exists() auf und castet auf den Contract, sodass die Open-Source-Engine keine kommerzielle Abhängigkeit trägt und sich die API beim Upgrade nicht ändert.