test(compliance): add independent conformance test suite for pades and xades and iti guide
This commit is contained in:
@@ -0,0 +1,65 @@
|
|||||||
|
# Guia de Testes de Conformidade ICP-Brasil e Validação Oficial no ITI
|
||||||
|
|
||||||
|
> **Procedimentos de Verificação Criptográfica Automatizada e Manual para Assinaturas PAdES e XAdES-BES**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Visão Geral
|
||||||
|
|
||||||
|
O **CyberCore Signer** implementa assinaturas digitais em conformidade com as normas técnicas da **Infraestrutura de Chaves Públicas Brasileira (ICP-Brasil)**, especificamente:
|
||||||
|
|
||||||
|
- **PDF (PAdES):** Padrão ISO 32000-1 e ETSI TS 102 778, com envelope CMS desacoplado (`/adbe.pkcs7.detached`) e algoritmo `SHA256withRSA` (alinhado ao perfil AD-RB).
|
||||||
|
- **XML (XAdES-BES / XMLDSig):** Padrão ETSI TS 101 903 e norma **DOC-ICP-15.03**, contendo propriedades qualificadas assinadas (`<xades:QualifyingProperties>`, `<xades:SigningTime>` e `<xades:SigningCertificate>`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Suíte de Testes Automatizados (`ComplianceSignatureSuiteTest`)
|
||||||
|
|
||||||
|
A conformidade técnica do código é garantida de forma contínua pelo teste automatizado:
|
||||||
|
`modules/cybercore-core/src/test/java/br/com/cybercore/signer/core/compliance/ComplianceSignatureSuiteTest.java`
|
||||||
|
|
||||||
|
### O que o teste automatizado valida:
|
||||||
|
1. **Cadeia ICP-Brasil Simulada em Memória:**
|
||||||
|
- Cria uma hierarquia criptográfica completa (AC Raiz de Teste -> AC Intermediária -> Certificado Signatário com extensão OID e-CPF `2.16.76.1.3.1`).
|
||||||
|
- Garante que nenhum certificado real ou dado pessoal seja exposto em testes.
|
||||||
|
2. **PAdES (PDF):**
|
||||||
|
- Criação de documento PDF original.
|
||||||
|
- Aplicação de selo visual conforme as coordenadas calculadas.
|
||||||
|
- Verificação da integridade do `/ByteRange` cobrindo o PDF integral exceto os bytes de `/Contents`.
|
||||||
|
- Validação criptográfica independente via Bouncy Castle `CMSSignedData.verify()`.
|
||||||
|
- Comparação do digest SHA-256 do documento contra o atributo assinado `messageDigest`.
|
||||||
|
3. **XAdES-BES (XML):**
|
||||||
|
- Assinatura de documento XML com geração de identificadores de referência (`Reference URI`).
|
||||||
|
- Validação independente via motor nativo Java JSR-105 (`javax.xml.crypto.dsig.XMLSignature`).
|
||||||
|
- Teste de inviolabilidade (Tamper Test): verificação de que qualquer modificação de um caractere nos dados de negócio invalida sumariamente o `coreValidity`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Validação Manual no Verificador de Conformidade do ITI
|
||||||
|
|
||||||
|
O **Instituto Nacional de Tecnologia da Informação (ITI)** disponibiliza a ferramenta governamental oficial para validação de assinaturas digitais brasileiras:
|
||||||
|
|
||||||
|
👉 **URL Oficial:** [https://verificador.iti.gov.br](https://verificador.iti.gov.br)
|
||||||
|
|
||||||
|
### 3.1 Procedimento Passo a Passo para Teste Manual
|
||||||
|
|
||||||
|
1. **Gerar ou Exportar o Documento Assinado:**
|
||||||
|
- Através da interface gráfica do CyberCore Signer, assine um documento PDF ou XML utilizando seu certificado digital A1 ou A3.
|
||||||
|
- O arquivo assinado resultante será gravado em disco (ex: `contrato_assinado.pdf` ou `documento_assinado.xml`).
|
||||||
|
|
||||||
|
2. **Submissão no Verificador do ITI:**
|
||||||
|
- Acesse [https://verificador.iti.gov.br](https://verificador.iti.gov.br) em qualquer navegador web moderno.
|
||||||
|
- Clique em **"Selecionar assinatura"** e faça o upload do arquivo gerado pelo CyberCore Signer.
|
||||||
|
- Clique no botão **"Verificar conformidade"**.
|
||||||
|
|
||||||
|
3. **Interpretação do Relatório de Conformidade:**
|
||||||
|
|
||||||
|
#### Cenário A: Assinatura com Certificado de Produção ICP-Brasil (Certisign, Serasa, Serpro, Valid, etc.)
|
||||||
|
- **Status Geral:** `APROVADO` / `CONFORME`.
|
||||||
|
- **Validação Criptográfica:** Íntegra (o hash bate e a assinatura RSA é autêntica).
|
||||||
|
- **Cadeia de Certificação:** Confiável (ancorada até a AC Raiz da ICP-Brasil oficial).
|
||||||
|
- **Atributos do Signatário:** Nome completo e CPF/CNPJ extraídos e validados na Receita Federal.
|
||||||
|
|
||||||
|
#### Cenário B: Assinatura com Certificados de Teste / Desenvolvimento (Gerados na Suíte)
|
||||||
|
- **Status Criptográfico:** `VÁLIDO` (integridade matemática e estrutura PAdES/XAdES aprovadas).
|
||||||
|
- **Alerta de Cadeia:** O verificador do ITI reportará: *"Cadeia de Certificação Não Confiável / AC não credenciada na ICP-Brasil"*. Este é o comportamento **correto e esperado**, uma vez que certificados gerados em testes locais não pertencem à lista de certificados confiáveis (LCR oficial) publicada pelo ITI.
|
||||||
+325
@@ -0,0 +1,325 @@
|
|||||||
|
package br.com.cybercore.signer.core.compliance;
|
||||||
|
|
||||||
|
import br.com.cybercore.signer.core.domain.ManagedCertificate;
|
||||||
|
import br.com.cybercore.signer.core.pdf.PdfSealPosition;
|
||||||
|
import br.com.cybercore.signer.core.pdf.PdfSignatureOptions;
|
||||||
|
import br.com.cybercore.signer.core.pdf.PdfSigningService;
|
||||||
|
import br.com.cybercore.signer.core.pdf.seal.PdfSealThemeRegistry;
|
||||||
|
import br.com.cybercore.signer.core.provider.CertificateManager;
|
||||||
|
import br.com.cybercore.signer.core.provider.Pkcs12CertificateProvider;
|
||||||
|
import br.com.cybercore.signer.core.validation.SignatureValidationService;
|
||||||
|
import br.com.cybercore.signer.core.validation.ValidationResult;
|
||||||
|
import br.com.cybercore.signer.core.validation.ValidationStatus;
|
||||||
|
import br.com.cybercore.signer.core.xml.XmlSignatureOptions;
|
||||||
|
import br.com.cybercore.signer.core.xml.XmlSignatureProfile;
|
||||||
|
import br.com.cybercore.signer.core.xml.XmlSigningService;
|
||||||
|
import org.apache.pdfbox.Loader;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
|
||||||
|
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
|
||||||
|
import org.bouncycastle.asn1.ASN1EncodableVector;
|
||||||
|
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||||
|
import org.bouncycastle.asn1.DEROctetString;
|
||||||
|
import org.bouncycastle.asn1.DERSequence;
|
||||||
|
import org.bouncycastle.asn1.x500.X500Name;
|
||||||
|
import org.bouncycastle.asn1.x509.BasicConstraints;
|
||||||
|
import org.bouncycastle.asn1.x509.Extension;
|
||||||
|
import org.bouncycastle.asn1.x509.GeneralName;
|
||||||
|
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||||
|
import org.bouncycastle.asn1.x509.KeyUsage;
|
||||||
|
import org.bouncycastle.cert.X509CertificateHolder;
|
||||||
|
import org.bouncycastle.cert.X509v3CertificateBuilder;
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
||||||
|
import org.bouncycastle.cms.CMSProcessableByteArray;
|
||||||
|
import org.bouncycastle.cms.CMSSignedData;
|
||||||
|
import org.bouncycastle.cms.SignerInformation;
|
||||||
|
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
|
||||||
|
import org.bouncycastle.operator.ContentSigner;
|
||||||
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
import javax.xml.crypto.dsig.XMLSignature;
|
||||||
|
import javax.xml.crypto.dsig.XMLSignatureFactory;
|
||||||
|
import javax.xml.crypto.dsig.dom.DOMValidateContext;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suíte de testes automatizados de conformidade criptográfica para assinaturas
|
||||||
|
* PAdES (PDF) e XAdES-BES / XMLDSig (XML), utilizando cadeia ICP-Brasil de teste gerada em memória.
|
||||||
|
*/
|
||||||
|
@DisplayName("Suíte de Testes de Conformidade ICP-Brasil (PAdES e XAdES-BES)")
|
||||||
|
class ComplianceSignatureSuiteTest {
|
||||||
|
|
||||||
|
private static CertificateManager certificateManager;
|
||||||
|
private static PdfSigningService pdfSigningService;
|
||||||
|
private static XmlSigningService xmlSigningService;
|
||||||
|
private static SignatureValidationService validationService;
|
||||||
|
|
||||||
|
private static String testEcpfCertId;
|
||||||
|
private static X509Certificate rootCaCert;
|
||||||
|
private static X509Certificate intermediateCaCert;
|
||||||
|
private static X509Certificate signerCert;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void initTestingCaAndServices() throws Exception {
|
||||||
|
certificateManager = new CertificateManager();
|
||||||
|
validationService = new SignatureValidationService();
|
||||||
|
|
||||||
|
// 1. Gera par de chaves da AC Raiz ICP-Brasil de Teste
|
||||||
|
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
|
||||||
|
kpg.initialize(2048);
|
||||||
|
KeyPair rootKeyPair = kpg.generateKeyPair();
|
||||||
|
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
Date notBefore = new Date(now - 100_000);
|
||||||
|
Date notAfter = new Date(now + 365L * 24 * 3600 * 1000);
|
||||||
|
|
||||||
|
X500Name rootSubject = new X500Name("CN=Autoridade Certificadora Raiz ICP-Brasil Teste v1, OU=Testes Criptograficos, O=ICP-Brasil, C=BR");
|
||||||
|
X509v3CertificateBuilder rootBuilder = new JcaX509v3CertificateBuilder(
|
||||||
|
rootSubject,
|
||||||
|
BigInteger.valueOf(1),
|
||||||
|
notBefore,
|
||||||
|
notAfter,
|
||||||
|
rootSubject,
|
||||||
|
rootKeyPair.getPublic()
|
||||||
|
);
|
||||||
|
rootBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));
|
||||||
|
rootBuilder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign));
|
||||||
|
|
||||||
|
ContentSigner rootSigner = new JcaContentSignerBuilder("SHA256withRSA").build(rootKeyPair.getPrivate());
|
||||||
|
rootCaCert = new JcaX509CertificateConverter().getCertificate(rootBuilder.build(rootSigner));
|
||||||
|
|
||||||
|
// 2. Gera AC Intermediária de Teste assinada pela Raiz
|
||||||
|
KeyPair intermediateKeyPair = kpg.generateKeyPair();
|
||||||
|
X500Name intermediateSubject = new X500Name("CN=AC Autoridade Certificadora Intermediaria Teste, OU=Testes ICP-Brasil, O=ICP-Brasil, C=BR");
|
||||||
|
X509v3CertificateBuilder interBuilder = new JcaX509v3CertificateBuilder(
|
||||||
|
rootSubject,
|
||||||
|
BigInteger.valueOf(2),
|
||||||
|
notBefore,
|
||||||
|
notAfter,
|
||||||
|
intermediateSubject,
|
||||||
|
intermediateKeyPair.getPublic()
|
||||||
|
);
|
||||||
|
interBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));
|
||||||
|
interBuilder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign));
|
||||||
|
|
||||||
|
ContentSigner interSigner = new JcaContentSignerBuilder("SHA256withRSA").build(rootKeyPair.getPrivate());
|
||||||
|
intermediateCaCert = new JcaX509CertificateConverter().getCertificate(interBuilder.build(interSigner));
|
||||||
|
|
||||||
|
// 3. Gera Certificado de Usuário Final (e-CPF com OID ICP-Brasil 2.16.76.1.3.1)
|
||||||
|
KeyPair userKeyPair = kpg.generateKeyPair();
|
||||||
|
X500Name userSubject = new X500Name("CN=CARLOS EDUARDO DA SILVA:12345678900, OU=e-CPF Brasil, O=ICP-Brasil, C=BR");
|
||||||
|
X509v3CertificateBuilder userBuilder = new JcaX509v3CertificateBuilder(
|
||||||
|
intermediateSubject,
|
||||||
|
BigInteger.valueOf(3),
|
||||||
|
notBefore,
|
||||||
|
notAfter,
|
||||||
|
userSubject,
|
||||||
|
userKeyPair.getPublic()
|
||||||
|
);
|
||||||
|
userBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false));
|
||||||
|
userBuilder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.nonRepudiation));
|
||||||
|
|
||||||
|
// Adiciona extensão SubjectAlternativeName com OtherName contendo OID ICP-Brasil 2.16.76.1.3.1
|
||||||
|
ASN1EncodableVector ecpfVector = new ASN1EncodableVector();
|
||||||
|
ecpfVector.add(new ASN1ObjectIdentifier("2.16.76.1.3.1"));
|
||||||
|
ecpfVector.add(new DEROctetString("01011990123456789001234567890123456789012345678".getBytes(StandardCharsets.UTF_8)));
|
||||||
|
GeneralName otherName = new GeneralName(GeneralName.otherName, new DERSequence(ecpfVector));
|
||||||
|
userBuilder.addExtension(Extension.subjectAlternativeName, false, new GeneralNames(otherName));
|
||||||
|
|
||||||
|
ContentSigner userSigner = new JcaContentSignerBuilder("SHA256withRSA").build(intermediateKeyPair.getPrivate());
|
||||||
|
signerCert = new JcaX509CertificateConverter().getCertificate(userBuilder.build(userSigner));
|
||||||
|
|
||||||
|
// Carrega em KeyStore PKCS12 em memória com a cadeia completa
|
||||||
|
KeyStore ks = KeyStore.getInstance("PKCS12");
|
||||||
|
ks.load(null, null);
|
||||||
|
char[] pwd = "test-compliance-pwd".toCharArray();
|
||||||
|
ks.setKeyEntry("ecpf-compliance-cert", userKeyPair.getPrivate(), pwd, new Certificate[]{signerCert, intermediateCaCert, rootCaCert});
|
||||||
|
|
||||||
|
Pkcs12CertificateProvider provider = new Pkcs12CertificateProvider("compliance-p12-provider", "Provedor Compliance Teste");
|
||||||
|
provider.setKeyStore(ks, pwd);
|
||||||
|
certificateManager.registerProvider(provider);
|
||||||
|
|
||||||
|
List<ManagedCertificate> certs = certificateManager.discoverCertificates();
|
||||||
|
testEcpfCertId = certs.get(0).id();
|
||||||
|
|
||||||
|
pdfSigningService = new PdfSigningService(certificateManager);
|
||||||
|
xmlSigningService = new XmlSigningService(certificateManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("PAdES: Deve gerar documento PDF assinado com envelope CMS desacoplado e validar conformidade estrutural")
|
||||||
|
void shouldGenerateAndValidateCompliantPadesSignature() throws Exception {
|
||||||
|
// Cria documento PDF em memória
|
||||||
|
byte[] originalPdf;
|
||||||
|
try (PDDocument doc = new PDDocument()) {
|
||||||
|
PDPage page = new PDPage();
|
||||||
|
doc.addPage(page);
|
||||||
|
try (PDPageContentStream cs = new PDPageContentStream(doc, page)) {
|
||||||
|
cs.beginText();
|
||||||
|
cs.setFont(new org.apache.pdfbox.pdmodel.font.PDType1Font(Standard14Fonts.FontName.HELVETICA), 12);
|
||||||
|
cs.newLineAtOffset(50, 700);
|
||||||
|
cs.showText("CONTRATO DE PRESTACAO DE SERVICOS - COMPLIANCE TEST");
|
||||||
|
cs.endText();
|
||||||
|
}
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
doc.save(baos);
|
||||||
|
originalPdf = baos.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assina via PdfSigningService
|
||||||
|
PdfSignatureOptions options = PdfSignatureOptions.builder(testEcpfCertId)
|
||||||
|
.position(PdfSealPosition.BOTTOM_RIGHT)
|
||||||
|
.visibleSeal(true)
|
||||||
|
.reason("Aprovacao Contratual")
|
||||||
|
.location("Brasilia, DF")
|
||||||
|
.build();
|
||||||
|
byte[] signedPdf = pdfSigningService.signPdf(originalPdf, options);
|
||||||
|
|
||||||
|
assertThat(signedPdf).isNotEmpty();
|
||||||
|
assertThat(signedPdf.length).isGreaterThan(originalPdf.length);
|
||||||
|
|
||||||
|
// 1. Validação independente via Apache PDFBox
|
||||||
|
try (PDDocument signedDoc = Loader.loadPDF(signedPdf)) {
|
||||||
|
List<PDSignature> signatures = signedDoc.getSignatureDictionaries();
|
||||||
|
assertThat(signatures).hasSize(1);
|
||||||
|
|
||||||
|
PDSignature sig = signatures.get(0);
|
||||||
|
assertThat(sig.getFilter()).isEqualTo("Adobe.PPKLite");
|
||||||
|
assertThat(sig.getSubFilter()).isEqualTo("adbe.pkcs7.detached");
|
||||||
|
assertThat(sig.getReason()).isEqualTo("Aprovacao Contratual");
|
||||||
|
assertThat(sig.getLocation()).isEqualTo("Brasilia, DF");
|
||||||
|
|
||||||
|
int[] byteRange = sig.getByteRange();
|
||||||
|
assertThat(byteRange).hasSize(4);
|
||||||
|
assertThat(byteRange[0]).isEqualTo(0);
|
||||||
|
assertThat(byteRange[1] + byteRange[3]).isEqualTo(signedPdf.length - (byteRange[2] - byteRange[1]));
|
||||||
|
|
||||||
|
// 2. Validação criptográfica do envelope CMS/PKCS#7 com Bouncy Castle independente
|
||||||
|
byte[] signedContentBytes = sig.getSignedContent(signedPdf);
|
||||||
|
byte[] cmsBytes = sig.getContents(signedPdf);
|
||||||
|
CMSSignedData signedData = new CMSSignedData(new CMSProcessableByteArray(signedContentBytes), cmsBytes);
|
||||||
|
|
||||||
|
assertThat(signedData.getSignerInfos().getSigners()).hasSize(1);
|
||||||
|
SignerInformation signerInfo = signedData.getSignerInfos().getSigners().iterator().next();
|
||||||
|
|
||||||
|
// Verifica se a assinatura bate com a chave pública do certificado do signatário
|
||||||
|
boolean verified = signerInfo.verify(new JcaSimpleSignerInfoVerifierBuilder().build(signerCert));
|
||||||
|
assertThat(verified).isTrue();
|
||||||
|
|
||||||
|
// Verifica se o digest embutido no CMS corresponde ao digest calculado dos bytes do documento
|
||||||
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
|
byte[] calculatedDigest = md.digest(signedContentBytes);
|
||||||
|
|
||||||
|
byte[] messageDigestAttr = signerInfo.getSignedAttributes()
|
||||||
|
.get(org.bouncycastle.asn1.cms.CMSAttributes.messageDigest)
|
||||||
|
.getAttrValues().getObjectAt(0).toASN1Primitive().getEncoded();
|
||||||
|
// Atributo ASN1 DER OctetString contém o digest
|
||||||
|
assertThat(messageDigestAttr).contains(calculatedDigest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Validação através do SignatureValidationService do CyberCore
|
||||||
|
ValidationResult valResult = validationService.validateAttached(signedPdf, "documento_assinado.pdf");
|
||||||
|
assertThat(valResult.status()).isEqualTo(ValidationStatus.VALID);
|
||||||
|
assertThat(valResult.signers()).hasSize(1);
|
||||||
|
assertThat(valResult.signers().get(0).signerName()).contains("CARLOS EDUARDO DA SILVA");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("XAdES-BES: Deve gerar documento XML assinado com propriedades qualificadas e validar via JSR-105")
|
||||||
|
void shouldGenerateAndValidateCompliantXadesBesSignature() throws Exception {
|
||||||
|
String originalXml = """
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<declaracao id="dec-9988">
|
||||||
|
<titular>Carlos Eduardo da Silva</titular>
|
||||||
|
<cpf>12345678900</cpf>
|
||||||
|
<cidade>Brasilia</cidade>
|
||||||
|
</declaracao>
|
||||||
|
""";
|
||||||
|
|
||||||
|
XmlSignatureOptions options = XmlSignatureOptions.wholeDocument(testEcpfCertId, null, XmlSignatureProfile.XADES_BES);
|
||||||
|
|
||||||
|
byte[] signedXmlBytes = xmlSigningService.signXml(originalXml.getBytes(StandardCharsets.UTF_8), options);
|
||||||
|
String signedXmlStr = new String(signedXmlBytes, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
// 1. Verificação de tags obrigatórias do XAdES-BES (ETSI TS 101 903 e DOC-ICP-15.03)
|
||||||
|
assertThat(signedXmlStr).contains("<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\"");
|
||||||
|
assertThat(signedXmlStr).contains("<xades:QualifyingProperties");
|
||||||
|
assertThat(signedXmlStr).contains("<xades:SignedProperties");
|
||||||
|
assertThat(signedXmlStr).contains("<xades:SigningTime");
|
||||||
|
assertThat(signedXmlStr).contains("<xades:SigningCertificate");
|
||||||
|
assertThat(signedXmlStr).contains("http://uri.etsi.org/01903#SignedProperties");
|
||||||
|
|
||||||
|
// 2. Validação criptográfica independente via API JSR-105 (DOMValidateContext)
|
||||||
|
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||||
|
dbf.setNamespaceAware(true);
|
||||||
|
Document doc = dbf.newDocumentBuilder().parse(new ByteArrayInputStream(signedXmlBytes));
|
||||||
|
|
||||||
|
NodeList sigNodes = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
|
||||||
|
assertThat(sigNodes.getLength()).isEqualTo(1);
|
||||||
|
|
||||||
|
DOMValidateContext valContext = new DOMValidateContext(signerCert.getPublicKey(), sigNodes.item(0));
|
||||||
|
NodeList allNodes = doc.getElementsByTagName("*");
|
||||||
|
for (int n = 0; n < allNodes.getLength(); n++) {
|
||||||
|
if (allNodes.item(n) instanceof Element e) {
|
||||||
|
if (e.hasAttribute("Id")) {
|
||||||
|
valContext.setIdAttributeNS(e, null, "Id");
|
||||||
|
}
|
||||||
|
if (e.hasAttribute("id")) {
|
||||||
|
valContext.setIdAttributeNS(e, null, "id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XMLSignatureFactory sigFactory = XMLSignatureFactory.getInstance("DOM");
|
||||||
|
XMLSignature xmlSignature = sigFactory.unmarshalXMLSignature(valContext);
|
||||||
|
|
||||||
|
boolean coreValidity = xmlSignature.validate(valContext);
|
||||||
|
assertThat(coreValidity).isTrue();
|
||||||
|
|
||||||
|
// 3. Validação pós-adulteração: se alterar qualquer caractere do XML original, a assinatura deve falhar
|
||||||
|
byte[] tamperedXml = signedXmlStr.replace("Carlos Eduardo da Silva", "Atacante Malicioso").getBytes(StandardCharsets.UTF_8);
|
||||||
|
Document tamperedDoc = dbf.newDocumentBuilder().parse(new ByteArrayInputStream(tamperedXml));
|
||||||
|
NodeList tamperedSigNodes = tamperedDoc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
|
||||||
|
DOMValidateContext tamperedContext = new DOMValidateContext(signerCert.getPublicKey(), tamperedSigNodes.item(0));
|
||||||
|
NodeList allTamperedNodes = tamperedDoc.getElementsByTagName("*");
|
||||||
|
for (int n = 0; n < allTamperedNodes.getLength(); n++) {
|
||||||
|
if (allTamperedNodes.item(n) instanceof Element e) {
|
||||||
|
if (e.hasAttribute("Id")) {
|
||||||
|
tamperedContext.setIdAttributeNS(e, null, "Id");
|
||||||
|
}
|
||||||
|
if (e.hasAttribute("id")) {
|
||||||
|
tamperedContext.setIdAttributeNS(e, null, "id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
XMLSignature tamperedSig = sigFactory.unmarshalXMLSignature(tamperedContext);
|
||||||
|
|
||||||
|
boolean tamperedValidity = tamperedSig.validate(tamperedContext);
|
||||||
|
assertThat(tamperedValidity).isFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user