assinarXml method

Future<String> assinarXml(
  1. String conteudoXml
)

Assina o XML digitalmente seguindo EXATAMENTE o padrão PHP do SERPRO

Processo baseado na implementação PHP de referência:

  1. Carrega certificado P12/PFX
  2. Canoniza o documento (C14N, sem comentários) - sobre o documentElement SEM Signature
  3. Calcula digest SHA-256 do documento canonizado
  4. Constrói estrutura Signature com SignedInfo contendo o DigestValue
  5. Canoniza APENAS o SignedInfo
  6. Assina o SignedInfo canonizado com RSA-SHA256
  7. 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');
  }
}