Aller au contenu
getnextpdf.com

Enterprise édition

Signature HSM — Référence détaillée

Cette page est la référence détaillée de la surface de signature HSM de NextPDF Enterprise. Elle couvre trois types publics. NextPDF\Enterprise\Security\Signature\Hsm\Pkcs11Signer signe via un jeton PKCS#11 à travers l’extension ext-pkcs11. NextPDF\Enterprise\Security\Signature\Hsm\OpenSslCliSigner signe via le binaire openssl dans un sous-processus, pour des clés adossées à un fournisseur ou à un moteur que le PHP ext-openssl ne peut pas charger. NextPDF\Enterprise\Security\Signature\Hsm\Provider\HsmSignerProviderAdapter expose l’un ou l’autre concret comme un SignerProviderInterface unifié. Dans chaque voie, la clé privée reste à l’intérieur de la frontière du jeton ; NextPDF remet les octets à signer et reçoit la signature. La voie post-quantique (signPqs) est un aperçu : elle est désactivée par défaut, ne porte aucune revendication de conformité et n’a aucune voie de vérification prise en charge dans les validateurs PDF actuels. NextPDF ne détient aucune certification et n’en accorde aucune ; la prise en charge n’équivaut pas à la conformité, et la conformité n’équivaut pas à la certification.

Cette capacité est livrée dans NextPDF Enterprise (nextpdf/enterprise) et s’active avec une enveloppe de licence de niveau Enterprise. Un déploiement sans ce droit ne charge pas les classes de la capacité. Comparer les éditions et obtenir une licence.

Les trois types vivent dans NextPDF\Enterprise\Security\Signature\Hsm ; l’adaptateur se trouve dans son sous-espace de noms Provider. Les deux signataires implémentent le contrat Core NextPDF\Contracts\HsmSignerInterface.

SymboleParamètresComportement par défautRenvoieLève ou échoue avecNotes
Pkcs11Signer::__construct()string $libraryPath, int $slotId, string $pin, string $certLabel, ?string $keyLabel = null, array $chainDer = [], bool $enablePostQuantum = false, ?FipsSignatureEnforcer $fipsEnforcer = nullOuvre la bibliothèque fournisseur, se connecte au slot et charge le certificat et les métadonnées d’algorithme de clé depuis le jetonHsmOperationException lorsque ext-pkcs11 est absent ou que l’accès au jeton échoueUn handle de module est mis en cache par chemin de bibliothèque par processus ; le PIN et les labels sont #[SensitiveParameter]
Pkcs11Signer::sign()string $data, string $algorithm = 'sha256WithRSAEncryption'Signe sur le jeton ; la sortie ECDSA brute est convertie en DER ECDSA-Sig-Valuestring octets de signature brutsHsmOperationException (clé introuvable, défaillance du jeton) ; InvalidArgumentException (algorithme non mappé) ; exceptions du garde-fou FIPS avant la signature lorsqu’un enforcer est câbléEnsemble d’algorithmes fermé ; voir Contrat de comportement
Pkcs11Signer::signPqs()string $data, Pkcs11PqsAlgorithm $algorithm, string $context = '', bool $randomized = trueRefusé sauf si $enablePostQuantum a été défini ; dispatche le mécanisme PQ PKCS#11 provisoirestring octets de signature brutsHsmOperationException (désactivé, défaillance du jeton, incohérence de longueur de signature) ; InvalidArgumentException (contexte au-delà de 255 octets)Aperçu ; aucune revendication de conformité ; les identifiants de mécanisme sont provisoires
Pkcs11Signer::isPostQuantumEnabled()AucunSignale le drapeau d’activation du constructeurboolAucun
Pkcs11Signer::getCertificateDer()AucunRenvoie le certificat du signataire lu depuis le jetonstring (DER)AucunChargé une fois à la construction
Pkcs11Signer::getCertificateChainDer()AucunRenvoie les intermédiaires fournis au constructeurarray<string> (DER)AucunExclut le certificat du signataire
OpenSslCliSigner::__construct()string $keyUri, string $certPath, string $pin, array $extraCertPaths = [], OpenSslCliBackend $backend = OpenSslCliBackend::Auto, string $opensslBinary = 'openssl', int $timeoutSeconds = 30, ?string $modulePath = null, ?string $configPath = null, bool $legacyPinDelivery = false, ?FipsSignatureEnforcer $fipsEnforcer = nullVérifie proc_open, sonde le binaire et sa version, résout le backend et charge les certificatsHsmOperationException (proc_open désactivé, fichier de module/config/certificat manquant, défaillance du binaire, aucun backend) ; InvalidArgumentException (pin-value dans $keyUri)OpenSslCliBackend::Auto préfère le fournisseur OpenSSL 3.x, puis le moteur
OpenSslCliSigner::sign()string $data, string $algorithm = 'sha256WithRSAEncryption'Exécute openssl dgst dans un sous-processus ; le PIN transite par un fichier pin-source éphémère 0600 par défautstring octets de signature brutsHsmOperationException (timeout, PIN rejeté, clé introuvable, échec de chargement du module, sortie vide, échec du fichier pin) ; InvalidArgumentException (algorithme non mappé) ; exceptions du garde-fou FIPS avant la signatureLe sous-processus est tué après $timeoutSeconds ; stderr est expurgé avant d’atteindre les messages
Surface d’accesseurs OpenSslCliSignerAucunRésultats de construction en lecture seulestring / array<string> / OpenSslCliBackendAucungetCertificateDer, getCertificateChainDer, getPublicKeyAlgorithm, getCertificatePem, getResolvedBackend, getOpensslVersion
HsmSignerProviderAdapter::__construct()HsmSignerInterface $hsm, string $providerId, SignatureAlgorithm $algorithm = SignatureAlgorithm::Pkcs1v15Enveloppe un concret HSM comme un SignerProviderInterfaceAucunConventions d’id de fournisseur : pkcs11-{module-id}, openssl-cli
HsmSignerProviderAdapter::providerId()AucunRenvoie l’id fourni au constructeurnon-empty-stringAucun
HsmSignerProviderAdapter::supportsAlgorithm()SignatureAlgorithm $algoMappe l’enum vers un nom de style OpenSSL, puis intersecte l’ensemble autorisé du backendboolAucunDécline les algorithmes digest-only ; les id openssl-engine n’annoncent rien
HsmSignerProviderAdapter::sign()string $data, ?string $keyVersion = nullDispatche à travers le signataire enveloppé avec l’algorithme configurénon-empty-stringKeyManagementException ($keyVersion non nul) ; SignatureFailedException (algorithme non mappable, défaillance du pilote, signature vide)Contrat SPI fail-closed ; chaque erreur de pilote remonte typée
public function __construct(private readonly string $libraryPath, private readonly int $slotId, #[SensitiveParameter] private readonly string $pin, #[SensitiveParameter] private readonly string $certLabel, #[SensitiveParameter] private readonly ?string $keyLabel = null, array $chainDer = [], private readonly bool $enablePostQuantum = false, ?FipsSignatureEnforcer $fipsEnforcer = null)
public function sign(string $data, string $algorithm = 'sha256WithRSAEncryption'): string
public function signPqs(string $data, Pkcs11PqsAlgorithm $algorithm, string $context = '', bool $randomized = true): string
public function isPostQuantumEnabled(): bool
public function getCertificateDer(): string
public function getCertificateChainDer(): array
public function __construct(private string $keyUri, string $certPath, #[SensitiveParameter] private string $pin, array $extraCertPaths = [], private OpenSslCliBackend $backend = OpenSslCliBackend::Auto, private string $opensslBinary = 'openssl', private int $timeoutSeconds = 30, private ?string $modulePath = null, private ?string $configPath = null, private bool $legacyPinDelivery = false, private ?FipsSignatureEnforcer $fipsEnforcer = null)
public function sign(string $data, string $algorithm = 'sha256WithRSAEncryption'): string
public function getCertificateDer(): string
public function getCertificateChainDer(): array
public function getPublicKeyAlgorithm(): string
public function getCertificatePem(): string
public function getResolvedBackend(): OpenSslCliBackend
public function getOpensslVersion(): string
public function __construct(private HsmSignerInterface $hsm, private string $providerId, private SignatureAlgorithm $algorithm = SignatureAlgorithm::Pkcs1v15)
public function providerId(): string
public function supportsAlgorithm(SignatureAlgorithm $algo): bool
public function sign(string $data, ?string $keyVersion = null): string
  • Garde des clés. La clé privée ne quitte jamais la frontière du jeton. Pkcs11Signer délègue l’opération au jeton ; OpenSslCliSigner passe une référence de clé — une URI PKCS#11 — au sous-processus openssl. Aucun des deux signataires ne peut exporter la clé.
  • Session et connexion. Pkcs11Signer met en cache un handle de module PKCS#11 par chemin de bibliothèque par processus, car l’interface du jeton doit être initialisée exactement une fois par processus. Chaque opération ouvre une session et se connecte avec le PIN ; la connexion authentifie l’utilisateur avant toute utilisation de clé privée (PKCS#11 v3.1 §5.6.8). Lorsque le slot signale une connexion existante, le signataire se déconnecte puis se reconnecte, afin que les jetons exigeant un PIN frais par opération en reçoivent un.
  • Ensemble d’algorithmes (fermé). Les deux signataires acceptent exactement : sha256WithRSAEncryption, sha384WithRSAEncryption, sha512WithRSAEncryption ; RSASSA-PSS, RSASSA-PSS-SHA256, RSASSA-PSS-SHA384, RSASSA-PSS-SHA512 ; ecdsa-with-SHA256, ecdsa-with-SHA384, ecdsa-with-SHA512. Pkcs11Signer accepte en plus ecdsa-raw. Tout autre identifiant lève InvalidArgumentException — aucun algorithme de substitution n’est jamais signé.
  • Liaison du sel PSS. Pour chaque variante PSS, la longueur du sel est égale à la longueur du condensat — 32, 48 ou 64 octets — et les paramètres de hachage et de MGF correspondent au condensat choisi. Cela suit la structure des paramètres de mécanisme PSS, où la longueur du sel est typiquement la longueur du hachage du message (PKCS#11 v3.1 §6.1.9). Les deux signataires appliquent le même appariement, de sorte qu’une configuration valide sur un backend est valide sur l’autre.
  • Conversion ECDSA. Un jeton renvoie une signature ECDSA comme la concaténation brute, zéro-remplie, de r et s (PKCS#11 v3.1 §6.3.1). Pkcs11Signer::sign() convertit cette sortie vers la forme ECDSA-Sig-Value encodée en DER que les validateurs PDF et OpenSSL attendent. L’appelant ne manipule jamais la forme brute.
  • Livraison du PIN (voie CLI). Dans le comportement par défaut sécurisé, le PIN est écrit dans un fichier éphémère créé exclusivement avec des permissions réservées au propriétaire, référencé via l’attribut pin-source de l’URI PKCS#11, et supprimé après la fin du sous-processus. Dans ce mode, le PIN n’est pas placé dans la ligne de commande ni exporté dans l’environnement du sous-processus. Avec $legacyPinDelivery = true, le PIN est intégré comme pin-value dans l’URI, ce qui est observable dans la ligne de commande du processus ; ce mode est opt-in uniquement.
  • Discipline du sous-processus. OpenSslCliSigner lance le binaire avec un tableau d’arguments — sans interpolation par le shell — applique $timeoutSeconds, tue le sous-processus à l’expiration et classe stderr en erreurs typées. Les secrets sont expurgés de stderr avant qu’il ne soit cité dans un message d’exception.
  • Sémantique de l’adaptateur. Un jeton HSM n’a aucun concept de version de clé gérée ; la clé sur le jeton est la version. HsmSignerProviderAdapter::sign() rejette donc tout $keyVersion non nul avec KeyManagementException au lieu de l’ignorer. supportsAlgorithm() intersecte le mappage de l’enum avec l’ensemble accepté du backend enveloppé, de sorte que l’adaptateur n’annonce jamais un mécanisme que le backend rejetterait au moment de la signature. Une signature vide provenant du pilote lève SignatureFailedException.
  • Aperçu post-quantique. signPqs() est protégé par le drapeau de constructeur $enablePostQuantum et refuse de s’exécuter autrement. La chaîne de contexte est limitée à 255 octets, correspondant à la borne de contexte ML-DSA (FIPS 204). La signature renvoyée doit correspondre à la longueur exacte en octets du jeu de paramètres Pkcs11PqsAlgorithm sélectionné, sinon l’appel échoue. Les identifiants de mécanisme suivent une extension PQ PKCS#11 provisoire et ne sont pas définitifs. Les profils PAdES ne reconnaissent pas les suites post-quantiques, la plupart des validateurs PDF rejettent de telles signatures, et NextPDF ne fournit aucune voie de vérification pour elles. Aucune conformité n’est revendiquée.
  • Construire Pkcs11Signer sans ext-pkcs11 lève HsmOperationException immédiatement ; l’extension n’est pas fournie avec les distributions PHP standard.
  • Un label de certificat ou de clé privée qui ne correspond à aucun objet sur le jeton lève HsmOperationException en nommant la classe d’objet manquante. Le label de clé peut légitimement différer du label de certificat sur certains jetons.
  • Des connexions échouées répétées peuvent verrouiller le PIN au niveau du jeton ; c’est le jeton qui applique cette politique, pas NextPDF. Les jetons dont les clés exigent une authentification à chaque utilisation reçoivent une connexion fraîche via la voie déconnexion-puis-réessai (PKCS#11 v3.1, sémantique always-authenticate).
  • OpenSslCliSigner refuse à la construction un $keyUri qui contient déjà pin-value, en mode fail-closed, car cette livraison contournerait la voie de PIN sécurisée.
  • Sous Windows, le mode de fichier pin sécurisé échoue de manière fail-closed avec HsmOperationException : les bits de permission de fichier ne peuvent pas y restreindre les octrois de lecture ACL, de sorte que le signataire refuse de laisser un PIN en clair à la merci des ACL du répertoire temporaire. La livraison de PIN legacy est l’alternative documentée, opt-in, pour les hôtes Windows de confiance.
  • La détection automatique du backend requiert OpenSSL 3.x pour la voie du fournisseur ; LibreSSL ne résout jamais vers le fournisseur. Lorsque ni une sonde de fournisseur ni une sonde de moteur ne réussit, la construction échoue avec HsmOperationException plutôt que de reporter l’échec au moment de la signature.
  • Un sous-processus qui dépasse $timeoutSeconds est terminé et signalé comme un timeout ; un sous-processus qui se termine proprement avec une sortie vide est signalé comme un échec de signature vide. Aucune de ces conditions ne peut produire un document partiellement signé.
  • Une signature post-quantique dont la longueur en octets ne correspond pas au jeu de paramètres sélectionné est rejetée avant qu’elle ne puisse atteindre l’encodage CMS.
  • HsmSignerProviderAdapter avec l’id de fournisseur retiré openssl-engine n’annonce aucun algorithme, de sorte qu’une configuration périmée échoue à la sélection du fournisseur plutôt qu’au moment de la signature.

Les deux signataires acceptent un FipsSignatureEnforcer optionnel. Lorsqu’il est câblé, le mode FIPS est actif pour ce signataire : sign() rejette un algorithme de signature non autorisé ou une clé sous le seuil avant toute signature par le jeton ou le sous-processus. Les seuils suivent la table de génération de signature — les modules RSA sous 2048 bits et les ordres ECDSA sous 224 bits sont interdits (NIST SP 800-131A Rev.2 §3 Table 2). Sans enforcer, le comportement est inchangé. Le garde-fou couvre uniquement la voie classique sign() ; signPqs() est régi par son propre drapeau d’aperçu. Ce sont des revendications de capacité concernant le code NextPDF : la validation FIPS 140-3 s’attache à un module cryptographique via le CMVP, qui dans ce déploiement est le HSM ou le fournisseur de l’opérateur — NextPDF n’est pas un module validé, ne détient aucune certification et n’en accorde aucune.

RevendicationStandardClause
La connexion authentifie l’utilisateur auprès du jeton avant les opérations de clé privée ; un mauvais PIN refuse l’accès.PKCS#11 v3.1§5.6.8
Les clés always-authenticate exigent une connexion fraîche à chaque utilisation ; une ré-authentification échouée à répétition peut verrouiller le PIN.PKCS#11 v3.1CKA_ALWAYS_AUTHENTICATE re-authentication
Une signature ECDSA de jeton est la concaténation brute r‖s ; le signataire la convertit en DER pour l’interopérabilité PDF.PKCS#11 v3.1§6.3.1
Les paramètres PSS lient le hachage, le MGF et la longueur du sel ; les signataires fixent le sel égal à la longueur du condensat.PKCS#11 v3.1§6.1.9
Le garde-fou FIPS refuse la génération de signature avec RSA sous 2048 bits ou un ordre ECDSA sous 224 bits.NIST SP 800-131A Rev.2§3 Table 2
La chaîne de contexte post-quantique est limitée à 255 octets.FIPS 204HashML-DSA context handling
La validation FIPS 140-3 s’attache aux modules cryptographiques via le CMVP.FIPS 140-3CMVP program scope

Toutes les clauses sont paraphrasées ; aucun texte normatif n’est reproduit. NextPDF ne formule aucune revendication de certification. Les signataires alignent leur comportement sur les clauses citées à titre de capacité. Qu’une signature produite se vérifie est la décision du vérificateur au regard de ses ancres de confiance ; la sécurité de la clé dépend du jeton, du HSM et de l’opérateur — pas de NextPDF seul.

  • Le mécanisme de livraison du PIN suit la convention pin-source de l’URI PKCS#11 (RFC 7512) ; cette RFC est hors du corpus cité, de sorte que le comportement ci-dessus est fondé sur la source du produit, pas sur une citation de spécification.

  • Confirme que l’environnement d’exécution charge ext-pkcs11 avant de construire Pkcs11Signer ; la construction échoue rapidement lorsque l’extension est absente. Le signataire CLI a besoin de proc_open activé et d’un binaire openssl avec un fournisseur ou un moteur PKCS#11 installé.

  • Le PIN, le label de certificat et le label de clé sont #[SensitiveParameter], de sorte qu’ils sont exclus des traces de pile. Fournis le PIN depuis un gestionnaire de secrets ; ne l’écris jamais dans le code source, dans une configuration commise au contrôle de version, ni dans les logs.

  • La construction est l’étape coûteuse sur les deux signataires : la voie PKCS#11 se connecte et lit le certificat, et la voie CLI sonde le binaire et le backend. Construis une fois et réutilise l’instance ; le cache de module par bibliothèque rend sûre une construction répétée contre la même bibliothèque.

  • Enveloppe un signataire dans HsmSignerProviderAdapter lorsque l’appelant travaille à travers SignerProviderInterface. Passe l’id de fournisseur canonique pour la classe enveloppée — pkcs11-{module-id} ou openssl-cli — afin que les vérifications de capacité utilisent le bon ensemble autorisé du backend.

  • Avant d’activer l’aperçu post-quantique, vérifie les identifiants de mécanisme du firmware du jeton par rapport aux valeurs provisoires que NextPDF enregistre ; une incohérence échoue au moment de la signature. N’active pas l’aperçu pour une sortie PAdES de production.

  • getResolvedBackend() et getOpensslVersion() existent pour l’enregistrement de preuves ; persiste-les avec les preuves de signature lorsque ton programme de conformité exige la reproductibilité.

Cette page documente uniquement le comportement observable de l’extérieur et la surface d’API publique prise en charge. Les chemins d’espaces de noms internes, les classes utilitaires, les tables de mécanismes, les noms de fichiers de runbook et les préfixes de tickets sont hors périmètre.