Modelo de Assinador Digital - PHP
Aqui você encontra um programa de modelo, escrito em PHP
para servir de referência na implementação do assinador digital. Este exemplo, utiliza um subconjunto do padrão XMLDSig
para assinatura de documentos XML utilizando a biblioteca OpenSSL.
O assinador digital é necessário para assinar o documento XML
Termo de Autorização. O certificado digital aqui disponibilizado, será utilizado para testes deste programa, sendo possível simular a assinatura como autor do pedido de dados, para ser submetido a um destinatário.
Componente
Serpro.Componentes.AssinadorDigital.php
Realiza a assinatura digital de documento XML utilizando o padrão W3C XMLDSig
.
Linguagem de programação
PHP
Versão | Autor | Código-fonte |
---|---|---|
1.0.0 | SERPRO | Serpro.Componentes.AssinadorDigital.php.zip |
Certificado Digital
Como demonstração, está sendo disponibilizado este certificado digital que só funciona em ambiente de desenvolvimento e homologação:
Atenção
Este certificado digital e-CNPJ disponibilizado é compatível somente com o ambiente de demonstração. Não possui valor em ambientes produtivos.
- Certificado de homologação eCNPJ-SERPRO-33683111000107.zip
- Cadeias de certificação - homologação: https://repositoriohom.serpro.gov.br/cadeias/serproacf5h1.p7b
- LCR: https://repositoriohom.serpro.gov.br/lcr/serproacf5h1.crl
Dependências
- PHP 8.0+
- Extensão Openssl
- Um certificado digital do tipo ICPBrasil válido
Código Serpro.Componentes.AssinadorDigital.php
<?php
date_default_timezone_set('America/Sao_Paulo');
// DADOS DO DESTINATÁRIO
$destinatario_numero = 'aqui_vai_o_CNPJ_do_contratante';
$destinatario_nome = 'aqui_vai_o_razão_social_contratante';
$destinatario_tipo = 'PJ';
$destinatario_papel = 'contratante';
// DADOS DO ASSINANTE*/
$assinante_numero = 'aqui_vai_o_numero_CPF_ou_CNPJ_de_quem_vai_assinar';
$assinante_nome = 'aqui_vai_o_nome_exato_CPF_ou_CNPJ_de_quem_vai_assinar';
$assinante_tipo = 'PF_ou_PJ';
$assinante_papel = 'autor pedido de dados';
// Certificado digital
$arquivoCertificado = 'arquivo.pfx';
$senhaCertificado = 'sEnha';
// DATAS
$data_assinatura = date("Ymd");
$data_vigencia_termo = '+30 days';
function montarTermoAutorizacao()
{
$stringRoot = <<<XML
<?xml version='1.0' encoding='UTF-8'?><termoDeAutorizacao></termoDeAutorizacao>
XML;
$xmlNaoAssinado = new SimpleXMLElement($stringRoot );
$dados = $xmlNaoAssinado->addChild('dados');
$sistema = $dados->addChild('sistema');
$sistema->addAttribute('id', 'API Integra Contador');
$termo = $dados->addChild('termo');
$termo->addAttribute('texto', 'Autorizo a empresa CONTRATANTE, identificada neste termo de autorização como DESTINATÁRIO, a executar as requisições dos serviços web disponibilizados pela API INTEGRA CONTADOR, onde terei o papel de AUTOR PEDIDO DE DADOS no corpo da mensagem enviada na requisição do serviço web. Esse termo de autorização está assinado digitalmente com o certificado digital do PROCURADOR ou OUTORGADO DO CONTRIBUINTE responsável, identificado como AUTOR DO PEDIDO DE DADOS.');
$avisoLegal = $dados->addChild('avisoLegal');
$avisoLegal->addAttribute('texto', 'O acesso a estas informações foi autorizado pelo próprio PROCURADOR ou OUTORGADO DO CONTRIBUINTE, responsável pela informação, via assinatura digital. É dever do destinatário da autorização e consumidor deste acesso observar a adoção de base legal para o tratamento dos dados recebidos conforme artigos 7º ou 11º da LGPD (Lei n.º 13.709, de 14 de agosto de 2018), aos direitos do titular dos dados (art. 9º, 17 e 18, da LGPD) e aos princípios que norteiam todos os tratamentos de dados no Brasil (art. 6º, da LGPD).');
$finalidade = $dados->addChild('finalidade ');
$finalidade->addAttribute('texto', 'A finalidade única e exclusiva desse TERMO DE AUTORIZAÇÃO, é garantir que o CONTRATANTE apresente a API INTEGRA CONTADOR esse consentimento do PROCURADOR ou OUTORGADO DO CONTRIBUINTE assinado digitalmente, para que possa realizar as requisições dos serviços web da API INTEGRA CONTADOR em nome do AUTOR PEDIDO DE DADOS (PROCURADOR ou OUTORGADO DO CONTRIBUINTE).');
$dataAssinatura = $dados->addChild('dataAssinatura');
$dataAssinatura->addAttribute('data', $GLOBALS['data_assinatura']);
$vigencia = $dados->addChild('vigencia');
$vigencia->addAttribute('data', date('Ymd', $GLOBALS['data_vigencia_termo'],strtotime(date('Ymd')))));
$destinatario = $dados->addChild('destinatario');
$destinatario->addAttribute('numero', $GLOBALS['destinatario_numero']);
$destinatario->addAttribute('nome', $GLOBALS['destinatario_nome']);
$destinatario->addAttribute('tipo', $GLOBALS['destinatario_tipo']);
$destinatario->addAttribute('papel', $GLOBALS['destinatario_papel']);
$assinadoPor = $dados->addChild('assinadoPor');
$assinadoPor->addAttribute('numero', $GLOBALS['assinante_numero']);
$assinadoPor->addAttribute('nome', $GLOBALS['assinante_nome']);
$assinadoPor->addAttribute('tipo', $GLOBALS['assinante_tipo']);
$assinadoPor->addAttribute('papel', $GLOBALS['assinante_papel']);
return $xmlNaoAssinado;
}
function carregarCertificados()
{
$arquivo = $GLOBALS['arquivoCertificado'];
$certs = $GLOBALS['certificados'];
$senha = $GLOBALS['senhaCertificado'];
$conteudo = file_get_contents($arquivo);
openssl_pkcs12_read($conteudo, $certs, $senha);
return $certs;
}
function obterPrivateKey($certificados)
{
return $certificados["pkey"];
}
function obterCertificado($certificados)
{
return $certificados["cert"];
}
function obterx509Certificate($certificados)
{
// <X509Certificate>
$x509Certificate = obterCertificado($certificados);
$x509Certificate = str_replace('-----END CERTIFICATE-----', ' ', $x509Certificate);
$x509Certificate = str_replace('-----BEGIN CERTIFICATE-----', ' ', $x509Certificate);
return preg_replace("/\r|\n/", "", trim($x509Certificate));
}
function assinar($xmlNaoAssinado, $certificados)
{
// Leitura do XML que contém o documento Termo de Autorização ainda não assinado
$xml = new DOMDocument();
// Preservando os espaços em branco
$xml->preserveWhiteSpace = true;
$xml->formatOutput = false;
$xml->loadXML($xmlNaoAssinado->asXML());
// Canonizar o conteúdo, exclusivo e sem comentários
if (!$xml->documentElement) {
throw new UnexpectedValueException('Indefindo o elemento documentElement');
}
$canonicalData = $xml->documentElement->C14N(true, false);
// Calcular o digest
$digestValue = openssl_digest($canonicalData, "sha256", true);
if ($digestValue === false) {
throw new UnexpectedValueException('Invalid digest value');
}
// Codificar para base64 (encode)
$digestValue = base64_encode($digestValue);
// Adiciona os elementos que vai compor a tag Signature com a assinatura digital
$signatureElement = $xml->createElement('Signature');
$signatureElement->setAttribute('xmlns', 'http://www.w3.org/2000/09/xmldsig#');
$xml->documentElement->appendChild($signatureElement);
$signedInfoElement = $xml->createElement('SignedInfo');
$signatureElement->appendChild($signedInfoElement);
$canonicalizationMethodElement = $xml->createElement('CanonicalizationMethod');
$canonicalizationMethodElement->setAttribute('Algorithm', 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315');
$signedInfoElement->appendChild($canonicalizationMethodElement);
$signatureMethodElement = $xml->createElement('SignatureMethod');
$signatureMethodElement->setAttribute('Algorithm', 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256');
$signedInfoElement->appendChild($signatureMethodElement);
$referenceElement = $xml->createElement('Reference');
$referenceElement->setAttribute('URI', '');
$signedInfoElement->appendChild($referenceElement);
$transformsElement = $xml->createElement('Transforms');
$referenceElement->appendChild($transformsElement);
$transformElement = $xml->createElement('Transform');
$transformElement->setAttribute('Algorithm', 'http://www.w3.org/2000/09/xmldsig#enveloped-signature');
$transformsElement->appendChild($transformElement);
$transformElement = $xml->createElement('Transform');
$transformElement->setAttribute('Algorithm', 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315');
$transformsElement->appendChild($transformElement);
$digestMethodElement = $xml->createElement('DigestMethod');
$digestMethodElement->setAttribute('Algorithm', 'http://www.w3.org/2001/04/xmlenc#sha256');
$referenceElement->appendChild($digestMethodElement);
$digestValueElement = $xml->createElement('DigestValue', $digestValue);
$referenceElement->appendChild($digestValueElement);
$signatureValueElement = $xml->createElement('SignatureValue', '');
$signatureElement->appendChild($signatureValueElement);
$keyInfoElement = $xml->createElement('KeyInfo');
$signatureElement->appendChild($keyInfoElement);
$X509Data = $xml->createElement('X509Data');
$keyInfoElement->appendChild($X509Data);
$X509Certificate = $xml->createElement('X509Certificate', obterx509Certificate($certificados));
$X509Data->appendChild($X509Certificate);
$c14nSignedInfo = $signedInfoElement->C14N(true, false);
$status = openssl_sign($c14nSignedInfo, $signatureValue, obterPrivateKey($certificados), OPENSSL_ALGO_SHA256);
if (!$status) {
throw new XmlSignerException('Falha no cálculo da assinatura.');
}
$xpath = new DOMXpath($xml);
$signatureValueElement = $xpath->query('//SignatureValue', $signatureElement)->item(0);
$signatureValueElement->nodeValue = base64_encode($signatureValue);
return $xml->saveXML();
}
$certificados = array();
$xmlNaoAssinado = montarTermoAutorizacao();
$certificados = carregarCertificados();
$resultado = assinar($xmlNaoAssinado, $certificados);
echo '<textarea>'.$resultado.'</textarea>';
echo '<textarea>'.base64_encode($resultado).'</textarea>';
/* Output:
Textarea1 xml assinado:
<?xml version="1.0" encoding="UTF-8"?>
<termoDeAutorizacao><dados><sistema id="API Integra Contador"/>...
Textarea2 xml assinado em base64:
PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHRlcm1vRdd...
*/
Créditos e Licença
Esse componente foi criado pelas equipes do SERPRO.
E foi utilizado licença MIT baseado no código open source no projeto do Github XMLDSIG for PHP.