コンテンツにスキップ

契約 / 署名

署名ドメインには 6 つの契約が含まれます。これらは、CMS 署名の生成方法、RFC 3161 タイムスタンプの適用方法、ハードウェア鍵による署名方法、長期検証の有効化方法を定義します。Core は契約を公開し、Pro エディションと Enterprise エディションが本番コードを提供します。

Terminal window
composer require nextpdf/core:^3

PDF のデジタル署名は、署名辞書に格納される CMS SignedData 構造です。Contents エントリーには、DER エンコードされた構造が格納されます。ByteRange エントリーは、ダイジェストの対象となるバイト範囲を指定します。ダイジェストはファイル全体を対象とし、署名値そのものは除外します(ISO 32000-2 §12.8.1 を参照)。CMS 構造は RFC 5652 §5.1 に従い、バージョン、ダイジェストアルゴリズム、カプセル化されたコンテンツ、署名者情報のシーケンスで構成されます。

SignerInterface は中核となる契約です。バイト範囲に対する CMS SignedData を生成し、署名値にタイムスタンプを適用し、長期検証をサポートするかどうかを報告します。これは、ETSI EN 319 142 で定義されている PAdES ベースラインレベル B-B から B-LTA までに対応します。レベルが上がるごとに要素が追加されます。B-B は基本署名を保持します。B-T は署名タイムスタンプを追加します。B-LT は失効データを追加します。B-LTA はアーカイブタイムスタンプを追加します。

HsmSignerInterface は、Hardware Security Module 内に保持された鍵で署名します。秘密鍵がハードウェア境界の外に出ることはありません。この契約は、CMS レイヤーが構造を構築できるように、署名者証明書と証明書チェーンを DER 形式で返します。DeferredSignerInterface は、非同期署名のために SignerInterface を拡張します。呼び出し元はデータを送信してジョブ識別子を受け取り、完了をポーリングしたうえで結果を取得します。リモートの HSM やクラウド鍵サービスが結果をすぐに返さない場合に使用します。

TimestampProviderInterface は、RFC 3161 タイムスタンプトークンを要求します。このプロトコルは、Time-Stamping Authority とのリクエストとレスポンスのやり取りです(RFC 3161 §2.4 を参照)。トークン内の messageImprint は、署名値のハッシュです(RFC 3161 §2.4.2)。LtvManagerInterface は長期検証を有効にします。証明書チェーンを収集し、OCSP および CRL レスポンスを取得し、Document Security Store を構築し、B-LTA 用のドキュメントタイムスタンプを追加します。CryptoPolicyInterface は、暗号処理が実行される前に、どのハッシュ、署名、暗号化アルゴリズム、鍵強度を許可するかを制御します。

種別主要メンバー安定性導入バージョン
SignerInterfaceinterfacesign(string): SignatureResult, timestamp(string): string, supportsLtv(): bool安定版1.0.0
HsmSignerInterfaceinterfacesign(string, string): string, getCertificateDer(), getCertificateChainDer(), getPublicKeyAlgorithm()安定版1.0.0
DeferredSignerInterfaceinterfacesubmitForSigning(string): string, retrieveSignature(string), isComplete(string)SignerInterface を拡張)実験的3.0.0
TimestampProviderInterfaceinterfacegetTimestamp(string): string実験的3.0.0
LtvManagerInterfaceinterfaceenableLtv(...), addDocumentTimestamp(...)安定版1.10.0
CryptoPolicyInterfaceinterfaceisHashAlgorithmAllowed(), isSignatureAlgorithmAllowed(), isEncryptionAlgorithmAllowed(), isKeyStrengthAllowed(), getPreferredHashAlgorithm(), getName()安定版1.9.0

SignerInterface::sign()NextPDF\Security\Signature\SignatureResult を返します。public な $cmsSignedData プロパティには、DER エンコードされた CMS が格納されます。public な $digestHex プロパティには、SHA-256 ダイジェストが格納されます。public な $timestampToken プロパティには、オプションの TSA トークンが格納されます。アクセサーは toHex() / toHexPadded(int) / getSize() / hasTimestamp() です。HsmSignerInterface::sign() は、RSA の場合は DER エンコードされたバイト列を返し、ECDSA の場合は生の r‖s バイト列を返します。アルゴリズム引数には 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()SignatureResult を返します。toHex() は、ライターが /Contents フィールドに配置する 16 進文字列を生成します。生の DER バイト列は public な $cmsSignedData プロパティにあります。この関数は、具体的なクラスではなく契約に依存します。Core のテスト署名者でも Premium の HSM 署名者でも、この契約を満たしていれば同じように扱えます。

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

このサービスは 3 つの契約を注入します。暗号ポリシーは、署名操作の前に参照されます。SignatureResult は、public な $cmsSignedData プロパティで CMS バイト列を、$digestHex で SHA-256 ダイジェストを、toHex()/Contents 用の 16 進文字列を公開します。タイムスタンププロバイダーは CMS バイト列を受け取り、DER エンコードされたトークンを返します。catch ブロックはポリシー名をログに記録し、再スローします。失敗を握りつぶすことはありません。

  • バイト範囲のダイジェストは、署名値を除外しなければなりません。 Contents エントリーを含めたダイジェストは、決して検証に成功しない署名を生成します(ISO 32000-2 §12.8.1)。
  • SignerInterface::supportsLtv() は、状態ではなく能力を報告します。 署名者は長期検証をサポートしていても、タイムスタンプサービスが設定されていない場合は B-B 署名を生成することがあります。
  • DeferredSignerInterface::retrieveSignature() は、ジョブが完了するまで null を返します。チェックのたびにペイロードを転送しないように、まず isComplete() をポーリングしてください。結果が存在すれば、取得処理はべき等です。
  • LtvManagerInterface::addDocumentTimestamp() は、Document Security Store の書き込み後に実行しなければなりません。先に呼び出すと、無効な B-LTA 構造が生成されます。
  • CryptoPolicyInterface は、ポリシーが設定されていない場合、すべてのアルゴリズムに対して true を返します。規制された環境では、明示的なポリシーを設定してください。オープンなデフォルトに依存しないでください。
  • HsmSignerInterface::getCertificateChainDer() は、署名者証明書を除外します。署名者のリーフ証明書には getCertificateDer() を、中間証明書にはチェーンメソッドを使用してください。

署名のコストは、契約ではなく、暗号処理とネットワークのラウンドトリップによって決まります。ローカルのソフトウェア署名は 1 桁台のミリ秒です。HSM 署名では、デバイスとのラウンドトリップが加わります。タイムスタンプでは、Time-Stamping Authority へのネットワークのラウンドトリップが加わります。長期検証では、チェーン内の証明書ごとに 1 回の OCSP または CRL の取得が加わります。1500 ms の実時間という performance_budget は、ウォーム接続のリモート TSA を使用した、タイムスタンプ付き署名 1 件をカバーします。遅い失効エンドポイントに対する長期検証はこれを超過するため、リクエストパスの外で実行する必要があります。再現性プロファイルは structural であり、bitwise ではありません。タイムスタンプは署名時刻を埋め込むため、2 回の実行ではドキュメント構造は同一のまま、タイムスタンプのバイト列が異なります。

署名契約はエンジンの主要な暗号境界であるため、脅威モデルは明示的です。1 つ目の懸念は鍵の管理です。HsmSignerInterface は秘密鍵をハードウェア境界内に保持し、この契約が鍵素材を公開することはありません。2 つ目はアルゴリズムのダウングレードです。CryptoPolicyInterface は処理の前に弱いハッシュや短い鍵をブロックするため、エンジンをフォークすることなく FIPS 140-3 や eIDAS のプロファイルをデプロイで適用できます。3 つ目はタイムスタンプの信頼です。RFC 3161 トークンの信頼性は Time-Stamping Authority の信頼性と同等に限られるため、プロバイダー契約は注入可能であり、デプロイ側が独自の認証局を固定します。4 つ目は長期検証です。失効素材は署名時に取得され、Document Security Store に格納されるため、証明書の有効期限が切れても検証は維持されます。すべての署名者入力を信頼できないものとして扱ってください。バイト範囲はエンジンによって計算され、呼び出し元から受け取ることはありません。この契約は暗号署名を管理するため、このページには export_control_class: legal-review-required が設定されています。本文は引用衛生に従い、すべての規範的ソースを言い換えており、いずれも引用していません。

主張標準条項証跡
署名値は、署名辞書の Contents エントリーに、CMS SignedData または TimeStampToken として DER エンコード形式で格納ISO 32000-2§12.8.1
ダイジェストは、ByteRange 配列で定義されたバイト範囲に対して計算され、署名値を除外ISO 32000-2§12.8.1,
長期検証素材は、VRI、OCSP、CRL、証明書のエントリーとともに Document Security Store に保持ISO 32000-2§12.8.4.3,
PAdES の長期検証では、検証データを DSS および VRI 辞書に配置ETSI EN 319 142-2§6.3,
RFC 3161 タイムスタンプトークンは、TSA のリクエストとレスポンスを介してやり取りされ、messageImprint を通じて署名値のハッシュを関連付けRFC 3161§2.4.2, §2.4,
CMS SignedData シーケンスは、バージョン、ダイジェストアルゴリズム、カプセル化されたコンテンツ、署名者情報を保持RFC 5652§5.1

すべての条項は言い換えられています。NextPDF は規範的なテキストを複製しません。正式な文言については、公開されている標準を参照してください。

Core は署名契約を公開し、凍結します。HsmSignerInterfaceLtvManagerInterface、および遅延署名者を支える本番実装は、PKCS#11 ハードウェア統合や PAdES B-LT および B-LTA を含めて、Pro エディションと Enterprise エディションで提供されます。Core は class_exists() を使用して実行時にこれらを resolve(解決)し、契約にキャストします。そのため、オープンソースエンジンは商用依存関係を持たず、アップグレード時に API が変更されることはありません。