Pular para o conteúdo
getnextpdf.com

Enterprise edição

Assinatura com HSM — Referência Profunda

Esta página é a referência detalhada da superfície de assinatura com HSM do NextPDF Enterprise. Ela cobre três tipos públicos. NextPDF\Enterprise\Security\Signature\Hsm\Pkcs11Signer assina por meio de um token PKCS#11 via extensão ext-pkcs11. NextPDF\Enterprise\Security\Signature\Hsm\OpenSslCliSigner assina por meio do binário openssl em um subprocesso, para chaves apoiadas por provider ou engine que o ext-openssl do PHP não consegue carregar. NextPDF\Enterprise\Security\Signature\Hsm\Provider\HsmSignerProviderAdapter expõe qualquer uma das implementações concretas como um SignerProviderInterface unificado. Em todos os caminhos, a chave privada permanece dentro da fronteira do token; o NextPDF entrega os bytes a serem assinados e recebe a assinatura. O caminho pós-quântico (signPqs) é um preview: está desativado por padrão, não carrega nenhuma alegação de conformidade e não tem caminho de verificação suportado nos validadores de PDF atuais. O NextPDF não detém nenhuma certificação e não concede nenhuma; suporte não é igual a conformidade, e conformidade não é igual a certificação.

Esta capacidade acompanha o NextPDF Enterprise (nextpdf/enterprise) e é ativada com um envelope de licença de nível Enterprise. Uma implantação sem essa habilitação não carrega as classes da capacidade. Compare edições e obtenha uma licença.

Todos os três tipos residem em NextPDF\Enterprise\Security\Signature\Hsm; o adaptador fica em seu sub-namespace Provider. Ambos os signers implementam o contrato Core NextPDF\Contracts\HsmSignerInterface.

SímboloParâmetrosComportamento padrãoRetornaLança ou falha comNotas
Pkcs11Signer::__construct()string $libraryPath, int $slotId, string $pin, string $certLabel, ?string $keyLabel = null, array $chainDer = [], bool $enablePostQuantum = false, ?FipsSignatureEnforcer $fipsEnforcer = nullAbre a biblioteca do fornecedor, faz login no slot e carrega o certificado e os metadados de algoritmo da chave a partir do tokenHsmOperationException quando ext-pkcs11 está ausente ou o acesso ao token falhaUm handle de módulo é cacheado por caminho de biblioteca por processo; PIN e labels são #[SensitiveParameter]
Pkcs11Signer::sign()string $data, string $algorithm = 'sha256WithRSAEncryption'Assina no token; a saída ECDSA bruta é convertida para DER ECDSA-Sig-Valuestring bytes brutos da assinaturaHsmOperationException (chave não encontrada, falha do token); InvalidArgumentException (algoritmo não mapeado); exceções do gate FIPS antes de assinar quando um enforcer está conectadoConjunto de algoritmos fechado; veja Contrato de comportamento
Pkcs11Signer::signPqs()string $data, Pkcs11PqsAlgorithm $algorithm, string $context = '', bool $randomized = trueRecusado a menos que $enablePostQuantum tenha sido definido; despacha o mecanismo PQ PKCS#11 provisóriostring bytes brutos da assinaturaHsmOperationException (desativado, falha do token, divergência no comprimento da assinatura); InvalidArgumentException (contexto acima de 255 bytes)Preview; nenhuma alegação de conformidade; os identificadores de mecanismo são provisórios
Pkcs11Signer::isPostQuantumEnabled()NenhumReporta a flag de opt-in do construtorboolNenhum
Pkcs11Signer::getCertificateDer()NenhumRetorna o certificado do signer lido do tokenstring (DER)NenhumCarregado uma vez na construção
Pkcs11Signer::getCertificateChainDer()NenhumRetorna os intermediários fornecidos ao construtorarray<string> (DER)NenhumExclui o certificado do signer
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 = nullVerifica proc_open, sonda o binário e a versão, resolve o backend e carrega os certificadosHsmOperationException (proc_open desativado, arquivo de módulo/config/certificado ausente, falha do binário, sem backend); InvalidArgumentException (pin-value dentro de $keyUri)OpenSslCliBackend::Auto prefere o provider OpenSSL 3.x, depois o engine
OpenSslCliSigner::sign()string $data, string $algorithm = 'sha256WithRSAEncryption'Executa openssl dgst em um subprocesso; por padrão, o PIN transita por um arquivo pin-source efêmero 0600string bytes brutos da assinaturaHsmOperationException (timeout, PIN rejeitado, chave não encontrada, falha ao carregar o módulo, saída vazia, falha do arquivo de pin); InvalidArgumentException (algoritmo não mapeado); exceções do gate FIPS antes de assinarO subprocesso é encerrado após $timeoutSeconds; o stderr é redigido antes de chegar às mensagens
Superfície de acessadores de OpenSslCliSignerNenhumResultados somente-leitura da construçãostring / array<string> / OpenSslCliBackendNenhumgetCertificateDer, getCertificateChainDer, getPublicKeyAlgorithm, getCertificatePem, getResolvedBackend, getOpensslVersion
HsmSignerProviderAdapter::__construct()HsmSignerInterface $hsm, string $providerId, SignatureAlgorithm $algorithm = SignatureAlgorithm::Pkcs1v15Encapsula uma implementação concreta de HSM como um SignerProviderInterfaceNenhumConvenções de id de provider: pkcs11-{module-id}, openssl-cli
HsmSignerProviderAdapter::providerId()NenhumRetorna o id fornecido ao construtornon-empty-stringNenhum
HsmSignerProviderAdapter::supportsAlgorithm()SignatureAlgorithm $algoMapeia o enum para um nome no estilo OpenSSL, depois faz a interseção com o conjunto permitido do backendboolNenhumRecusa algoritmos apenas de digest; ids openssl-engine não anunciam nada
HsmSignerProviderAdapter::sign()string $data, ?string $keyVersion = nullDespacha por meio do signer encapsulado com o algoritmo configuradonon-empty-stringKeyManagementException ($keyVersion não nulo); SignatureFailedException (algoritmo não mapeável, falha do driver, assinatura vazia)Contrato SPI fail-closed; todo erro de driver aflora tipado
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
  • Custódia da chave. A chave privada nunca sai da fronteira do token. O Pkcs11Signer delega a operação ao token; o OpenSslCliSigner passa uma referência de chave — uma URI PKCS#11 — ao subprocesso openssl. Nenhum dos signers consegue exportar a chave.
  • Sessão e login. O Pkcs11Signer cacheia um handle de módulo PKCS#11 por caminho de biblioteca por processo, porque a interface do token deve ser inicializada exatamente uma vez por processo. Cada operação abre uma sessão e faz login com o PIN; o login autentica o usuário antes de qualquer uso da chave privada (PKCS#11 v3.1 §5.6.8). Quando o slot reporta um login existente, o signer faz logout e login novamente, de modo que tokens que exigem um PIN novo por operação recebam um.
  • Conjunto de algoritmos (fechado). Ambos os signers aceitam exatamente: sha256WithRSAEncryption, sha384WithRSAEncryption, sha512WithRSAEncryption; RSASSA-PSS, RSASSA-PSS-SHA256, RSASSA-PSS-SHA384, RSASSA-PSS-SHA512; ecdsa-with-SHA256, ecdsa-with-SHA384, ecdsa-with-SHA512. O Pkcs11Signer aceita adicionalmente ecdsa-raw. Qualquer outro identificador levanta InvalidArgumentException — nenhum algoritmo substituto é jamais assinado.
  • Vínculo de salt do PSS. Para toda variante PSS, o comprimento do salt é igual ao comprimento do digest — 32, 48 ou 64 bytes — e os parâmetros de hash e MGF correspondem ao digest escolhido. Isto segue a estrutura de parâmetros do mecanismo PSS, na qual o comprimento do salt é tipicamente o comprimento do hash da mensagem (PKCS#11 v3.1 §6.1.9). Ambos os signers aplicam o mesmo pareamento, portanto uma configuração válida em um backend é válida no outro.
  • Conversão ECDSA. Um token retorna uma assinatura ECDSA como a concatenação bruta, com zero-padding, de r e s (PKCS#11 v3.1 §6.3.1). O Pkcs11Signer::sign() converte essa saída para a forma ECDSA-Sig-Value codificada em DER que os validadores de PDF e o OpenSSL esperam. O chamador nunca manipula a forma bruta.
  • Entrega do PIN (caminho CLI). No padrão seguro, o PIN é gravado em um arquivo efêmero criado exclusivamente com permissões apenas do dono, referenciado por meio do atributo pin-source da URI PKCS#11, e removido após o subprocesso encerrar. Nesse modo, o PIN não é colocado na linha de comando e não é exportado para o ambiente do subprocesso. Com $legacyPinDelivery = true, o PIN é embutido como pin-value na URI, o que é observável na linha de comando do processo; esse modo é opt-in apenas.
  • Disciplina do subprocesso. O OpenSslCliSigner gera o binário com um array de argumentos — sem interpolação de shell —, impõe $timeoutSeconds, encerra o subprocesso na expiração e classifica o stderr em erros tipados. Segredos são redigidos do stderr antes de serem citados em uma mensagem de exceção.
  • Semântica do adaptador. Um token HSM não tem conceito de versão de chave gerenciada; a chave no token é a versão. O HsmSignerProviderAdapter::sign() portanto rejeita qualquer $keyVersion não nulo com KeyManagementException em vez de ignorá-lo. O supportsAlgorithm() faz a interseção do mapeamento do enum com o conjunto aceito pelo backend encapsulado, de modo que o adaptador nunca anuncia um mecanismo que o backend rejeitaria no momento da assinatura. Uma assinatura vazia vinda do driver levanta SignatureFailedException.
  • Preview pós-quântico. O signPqs() é protegido pela flag de construtor $enablePostQuantum e se recusa a executar de outra forma. A string de contexto é limitada a 255 bytes, correspondendo ao limite de contexto do ML-DSA (FIPS 204). A assinatura retornada deve corresponder ao comprimento exato em bytes do conjunto de parâmetros Pkcs11PqsAlgorithm selecionado, ou a chamada falha. Os identificadores de mecanismo seguem uma extensão PQ PKCS#11 provisória e não são finais. Os perfis PAdES não reconhecem suítes pós-quânticas, a maioria dos validadores de PDF rejeita tais assinaturas e o NextPDF não fornece nenhum caminho de verificação para elas. Nenhuma conformidade é alegada.
  • Construir Pkcs11Signer sem ext-pkcs11 levanta HsmOperationException imediatamente; a extensão não é empacotada com as distribuições PHP padrão.
  • Um label de certificado ou de chave privada que não corresponde a nenhum objeto no token levanta HsmOperationException nomeando a classe do objeto ausente. O label da chave pode legitimamente diferir do label do certificado em alguns tokens.
  • Logins malsucedidos repetidos podem travar o PIN no token; o token impõe essa política, não o NextPDF. Tokens cujas chaves exigem autenticação a cada uso recebem um login novo por meio do caminho de logout e nova tentativa (PKCS#11 v3.1, semântica always-authenticate).
  • O OpenSslCliSigner recusa uma $keyUri que já contém pin-value na construção, fail-closed, porque essa entrega contornaria o caminho seguro do PIN.
  • No Windows, o modo seguro de arquivo de pin falha fechado com HsmOperationException: os bits de permissão de arquivo não conseguem restringir concessões de leitura de ACL lá, então o signer se recusa a deixar um PIN em texto claro na ACL do diretório temporário. A entrega legada de PIN é a alternativa documentada e opt-in para hosts Windows confiáveis.
  • A autodetecção de backend requer OpenSSL 3.x para o caminho do provider; o LibreSSL nunca resolve para o provider. Quando nem uma sondagem de provider nem de engine tem sucesso, a construção falha com HsmOperationException em vez de adiar a falha para o momento da assinatura.
  • Um subprocesso que excede $timeoutSeconds é encerrado e reportado como timeout; um subprocesso que sai limpo com saída vazia é reportado como falha de assinatura vazia. Nenhuma das condições pode produzir um documento parcialmente assinado.
  • Uma assinatura pós-quântica cujo comprimento em bytes não corresponde ao conjunto de parâmetros selecionado é rejeitada antes que possa chegar à codificação CMS.
  • O HsmSignerProviderAdapter com o id de provider aposentado openssl-engine não anuncia nenhum algoritmo, então uma configuração obsoleta falha na seleção do provider em vez de no momento da assinatura.

Ambos os signers aceitam um FipsSignatureEnforcer opcional. Quando um está conectado, o modo FIPS está ativo para aquele signer: sign() rejeita um algoritmo de assinatura não permitido ou uma chave abaixo do piso antes que qualquer assinatura no token ou no subprocesso ocorra. Os pisos seguem a tabela de geração de assinaturas — módulos RSA abaixo de 2048 bits e ordens ECDSA abaixo de 224 bits são proibidos (NIST SP 800-131A Rev.2 §3 Table 2). Sem um enforcer, o comportamento é inalterado. O gate cobre apenas o caminho clássico sign(); o signPqs() é governado por sua própria flag de preview. Estas são alegações de capacidade sobre o código NextPDF: a validação FIPS 140-3 se vincula a um módulo criptográfico por meio do CMVP, que nesta implantação é o HSM ou provider do operador — o NextPDF não é um módulo validado, não detém nenhuma certificação e não concede nenhuma.

AlegaçãoPadrãoCláusula
O login autentica o usuário no token antes das operações com a chave privada; um PIN incorreto nega o acesso.PKCS#11 v3.1§5.6.8
Chaves always-authenticate precisam de um login novo por uso; nova autenticação malsucedida repetida pode travar o PIN.PKCS#11 v3.1CKA_ALWAYS_AUTHENTICATE re-authentication
Uma assinatura ECDSA de token é a concatenação bruta r‖s; o signer a converte para DER visando interoperabilidade com PDF.PKCS#11 v3.1§6.3.1
Os parâmetros PSS vinculam hash, MGF e comprimento do salt; os signers definem o salt igual ao comprimento do digest.PKCS#11 v3.1§6.1.9
O gate FIPS nega a geração de assinatura com RSA abaixo de 2048 bits ou ordem ECDSA abaixo de 224 bits.NIST SP 800-131A Rev.2§3 Table 2
A string de contexto pós-quântica é limitada a 255 bytes.FIPS 204HashML-DSA context handling
A validação FIPS 140-3 se vincula a módulos criptográficos por meio do CMVP.FIPS 140-3CMVP program scope

Todas as cláusulas são parafraseadas; nenhum texto normativo é reproduzido. O NextPDF não faz nenhuma alegação de certificação. Os signers alinham seu comportamento com as cláusulas citadas como uma capacidade. Se uma assinatura produzida verifica é a decisão do verificador contra suas âncoras de confiança; a segurança da chave depende do token, do HSM e do operador — não do NextPDF isoladamente.

  • O mecanismo de entrega do PIN segue a convenção pin-source da URI PKCS#11 (RFC 7512); esse RFC está fora do corpus citado, então o comportamento acima é fundamentado no código-fonte do produto, não em uma citação de spec.

  • Confirme que o runtime carrega ext-pkcs11 antes de construir o Pkcs11Signer; a construção falha rápido quando a extensão está ausente. O signer CLI precisa de proc_open habilitado e de um binário openssl com um provider ou engine PKCS#11 instalado.

  • O PIN, o label do certificado e o label da chave são #[SensitiveParameter], então são excluídos dos stack traces. Forneça o PIN a partir de um gerenciador de segredos; nunca o grave em código-fonte, em configuração commitada ao controle de versão, ou em logs.

  • A construção é o passo caro em ambos os signers: o caminho PKCS#11 faz login e lê o certificado, e o caminho CLI sonda o binário e o backend. Construa uma vez e reutilize a instância; o cache de módulo por biblioteca torna a construção repetida contra a mesma biblioteca segura.

  • Encapsule um signer em HsmSignerProviderAdapter quando o chamador trabalha por meio de SignerProviderInterface. Passe o id de provider canônico para a classe encapsulada — pkcs11-{module-id} ou openssl-cli — de modo que as verificações de capacidade usem o conjunto permitido do backend correto.

  • Antes de habilitar o preview pós-quântico, verifique os identificadores de mecanismo do firmware do token contra os valores provisórios que o NextPDF registra; uma divergência falha no momento da assinatura. Não habilite o preview para saída PAdES de produção.

  • getResolvedBackend() e getOpensslVersion() existem para registro de evidências; persista-os junto com as evidências de assinatura quando seu programa de conformidade exigir reprodutibilidade.

Esta página documenta apenas o comportamento observável externamente e a superfície de API pública suportada. Caminhos de namespace internos, classes auxiliares, tabelas de mecanismos, nomes de arquivos de runbook e prefixos de tickets estão fora do escopo.