assinarXml method
- String conteudoXml
Assina o XML digitalmente seguindo EXATAMENTE o padrão PHP do SERPRO
Processo baseado na implementação PHP de referência:
- Carrega certificado P12/PFX
- Canoniza o documento (C14N, sem comentários) - sobre o documentElement SEM Signature
- Calcula digest SHA-256 do documento canonizado
- Constrói estrutura Signature com SignedInfo contendo o DigestValue
- Canoniza APENAS o SignedInfo
- Assina o SignedInfo canonizado com RSA-SHA256
- Insere SignatureValue e retorna XML completo
Implementation
Future<String> assinarXml(String conteudoXml) async {
try {
// 1. Carregar certificado se ainda não carregado
if (_chavePrivada == null || _certificadoX509Base64 == null) {
await carregarCertificado();
}
// 2. Parsear XML (documento SEM Signature ainda)
final documento = XmlDocument.parse(conteudoXml);
// 3. Canonizar o documentElement (C14N sem comentários)
// PHP: $canonicalData = $xml->documentElement->C14N(true, false);
final dadosCanonicalizados = _canonizarElemento(documento.rootElement);
// 4. Calcular digest SHA-256 do documento canonizado
// PHP: $digestValue = openssl_digest($canonicalData, "sha256", true);
// PHP: $digestValue = base64_encode($digestValue);
final valorDigest = _calcularDigest(dadosCanonicalizados);
// 5. Construir estrutura Signature com SignedInfo
final signatureXml = _construirSignatureXmlString(valorDigest);
// 6. Adicionar Signature ao documento para poder canonizar o SignedInfo
final docComSignature = XmlDocument.parse(
conteudoXml.replaceAll(
'</termoDeAutorizacao>',
'$signatureXml</termoDeAutorizacao>',
),
);
// 7. Extrair e canonizar APENAS o SignedInfo
// PHP: $c14nSignedInfo = $signedInfoElement->C14N(true, false);
final signedInfoElement = docComSignature
.findAllElements('SignedInfo')
.first;
final signedInfoCanonico = _canonizarElemento(
signedInfoElement,
namespacePai: 'http://www.w3.org/2000/09/xmldsig#',
);
// 8. Assinar o SignedInfo canonizado com RSA-SHA256
// PHP: openssl_sign($c14nSignedInfo, $signatureValue, $privateKey, OPENSSL_ALGO_SHA256);
final valorAssinatura = _assinarComRsaSha256(signedInfoCanonico);
// 9. Substituir o SignatureValue vazio pelo valor da assinatura
final xmlFinal = docComSignature
.toXmlString(pretty: false, indent: '')
.replaceFirst(
'<SignatureValue></SignatureValue>',
'<SignatureValue>$valorAssinatura</SignatureValue>',
);
return xmlFinal;
} catch (e) {
if (e is ExcecaoAutenticaProcurador) rethrow;
throw ExcecaoAssinaturaXml('Erro ao assinar XML: $e');
}
}