diff --git a/docs/xml-xmldsig.md b/docs/xml-xmldsig.md index 2b3c005..940e38a 100644 --- a/docs/xml-xmldsig.md +++ b/docs/xml-xmldsig.md @@ -1,16 +1,27 @@ -# Assinaturas XML (XMLDSig e ICP-Brasil) +# Assinaturas XML no Padrão XAdES e ICP-Brasil -Este documento descreve a especificação e o funcionamento da assinatura digital de arquivos XML no padrão **W3C XMLDSig (XML Digital Signatures)** e **ICP-Brasil** no CyberCore Signer. +Este documento descreve a especificação, conformidade e o funcionamento da assinatura digital de arquivos XML no padrão avançado **XAdES (XML Advanced Electronic Signatures - ETSI TS 101 903 e ICP-Brasil DOC-ICP-15.03)** e **W3C XMLDSig** no CyberCore Signer. --- ## 1. Visão Geral -O CyberCore Signer fornece suporte nativo a assinaturas digitais XML utilizando a engine padronizada e segura da plataforma Java (`javax.xml.crypto.dsig`). +O CyberCore Signer fornece suporte nativo a assinaturas digitais XML utilizando a engine padronizada e segura da plataforma Java (`javax.xml.crypto.dsig`), garantindo conformidade com o padrão **XAdES-BES** adotado pelo ITI (Instituto Nacional de Tecnologia da Informação) na infraestrutura ICP-Brasil (perfil AD-RB). -### 1.1 Casos de Uso Suportados +### 1.1 Perfis Suportados -- **Documento Inteiro Envelopado (`URI=""`):** Contratos, despachos, declarações e relatórios em formato XML. +* **`XAdES-BES` (Padrão Recomendado / ICP-Brasil):** + - Conforme especificação técnica **ETSI TS 101 903** e norma **DOC-ICP-15.03**. + - Encapsula o elemento `` contendo `` e ``. + - Inclui propriedade qualificada de data/hora da assinatura (``) em formato UTC ISO-8601. + - Inclui vínculo criptográfico irrefutável do certificado do signatário (``) contendo o digest SHA-256 do certificado DER e referências do emissor/serial (``). + - Protege as propriedades qualificadas através de uma `` em `` com `Type="http://uri.etsi.org/01903#SignedProperties"`. +* **`XMLDSig` Clássico (W3C Enveloped):** + - Mantido para compatibilidade com layouts estritos de notas fiscais eletrônicas legadas (ex: NF-e, CT-e, MDF-e SEFAZ). + +### 1.2 Casos de Uso Suportados + +- **Documento Inteiro Envelopado (`URI=""`):** Contratos, despachos, declarações, laudos e relatórios em XML. - **Assinatura de Elementos Específicos por ID:** - **NF-e (Nota Fiscal Eletrônica):** Assinatura da tag `` - **CT-e (Conhecimento de Transporte Eletrônico):** Assinatura da tag `` @@ -19,19 +30,69 @@ O CyberCore Signer fornece suporte nativo a assinaturas digitais XML utilizando --- -## 2. Padrões Criptográficos +## 2. Estrutura Criptográfica do XAdES-BES -- **Canonicalização (C14N):** `http://www.w3.org/TR/2001/REC-xml-c14n-20010315` (C14N inclusiva sem comentários). -- **Transformações:** - - `http://www.w3.org/2000/09/xmldsig#enveloped-signature` (remove a assinatura do cálculo do digest). - - C14N padrão. -- **Algoritmo de Digest:** SHA-256 (`http://www.w3.org/2001/04/xmlenc#sha256`). -- **Algoritmo de Assinatura:** RSA-SHA256 (`http://www.w3.org/2001/04/xmldsig-more#rsa-sha256`). -- **KeyInfo:** Informações do certificado X.509 embutidas em ``. +```xml + + + + + + + + + + + + + [Digest do nó em Base64] + + + + + + + + + [Digest das SignedProperties em Base64] + + + + [Assinatura RSA-SHA256 em Base64] + + + + [Certificado DER do signatário em Base64] + + + + + + + + 2026-10-11T14:05:00Z + + + + + [SHA-256 do Certificado] + + + [Emissor X.500] + [Número Serial] + + + + + + + + +``` --- -## 3. Segurança e Mitigação contra XXE +## 3. Segurança e Mitigação contra Ataques XXE O parser XML é configurado com blindagem estrita contra ataques **XML External Entity (XXE)** e expansões recursivas de entidades: @@ -47,10 +108,11 @@ dbf.setExpandEntityReferences(false); --- -## 4. Validação Pós-Assinatura +## 4. Validação Pós-Assinatura e Auditoria -O serviço `XmlSigningService.validateXmlSignature(byte[] xmlBytes)` permite auditar a assinatura imediatamente após sua emissão, verificando: +O serviço `XmlSigningService.validateXmlSignature(byte[] xmlBytes)` e o `SignatureValidationService` realizam: -1. A integridade criptográfica da assinatura (`SignedInfo`). -2. A integridade dos digests de todas as referências apontadas (`Reference.validate()`). -3. O certificado X.509 público extraído da chave. +1. **Validação criptográfica de SignedInfo:** Confere se o `SignatureValue` foi emitido pela chave pública correspondente. +2. **Validação de integridade dos dados de negócio:** Confere o digest do conteúdo ou elemento assinado. +3. **Validação de integridade do XAdES:** Confere o digest das propriedades qualificadas (`SignedProperties`), rejeitando qualquer adulteração no horário de assinatura (`SigningTime`) ou no certificado declarado. +4. **Inspeção de signatário e datação:** Extração de nome, documento (CPF/CNPJ), autoridade certificadora emissora e datação auditável. diff --git a/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/validation/SignatureValidationService.java b/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/validation/SignatureValidationService.java index f4fe8f6..882bdf7 100644 --- a/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/validation/SignatureValidationService.java +++ b/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/validation/SignatureValidationService.java @@ -15,6 +15,7 @@ import org.bouncycastle.util.Store; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; +import org.w3c.dom.Element; import org.w3c.dom.NodeList; import javax.xml.crypto.dsig.XMLSignature; @@ -221,32 +222,59 @@ public final class SignatureValidationService { } /** - * Validação de assinaturas embutidas em documentos XML (XMLDSig). + * Validação de assinaturas embutidas em documentos XML (XAdES ou XMLDSig). */ private ValidationResult validateXml(byte[] xmlBytes, String fileName) { try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); Document doc = dbf.newDocumentBuilder().parse(new ByteArrayInputStream(xmlBytes)); + // Registro de atributos Id no DOM + registerAllIdAttributes(doc.getDocumentElement()); + NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); + if (nl.getLength() == 0) { + nl = doc.getElementsByTagName("Signature"); + } if (nl.getLength() == 0) { return new ValidationResult( ValidationStatus.INVALID, ValidationType.XML_DSIG, - "Nenhum elemento XMLDSig encontrado no documento.", + "Nenhum elemento XML encontrado no documento.", List.of(), null, fileName ); } + // Identificação de padrão XAdES + NodeList qpList = doc.getElementsByTagNameNS("http://uri.etsi.org/01903/v1.3.2#", "QualifyingProperties"); + if (qpList.getLength() == 0) { + qpList = doc.getElementsByTagName("QualifyingProperties"); + } + boolean isXades = qpList.getLength() > 0; + ValidationType valType = isXades ? ValidationType.XADES_XML : ValidationType.XML_DSIG; + XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM"); List signers = new ArrayList<>(); boolean allValid = true; for (int i = 0; i < nl.getLength(); i++) { - DOMValidateContext valContext = new DOMValidateContext(new KeyValueKeySelector(), nl.item(i)); + Element sigElement = (Element) nl.item(i); + DOMValidateContext valContext = new DOMValidateContext(new KeyValueKeySelector(), sigElement); + + // Registra nós com atributo Id no valContext + NodeList allNodes = doc.getElementsByTagName("*"); + for (int n = 0; n < allNodes.getLength(); n++) { + if (allNodes.item(n) instanceof Element e && e.hasAttribute("Id")) { + valContext.setIdAttributeNS(e, null, "Id"); + } + } + XMLSignature signature = fac.unmarshalXMLSignature(valContext); boolean coreValidity = signature.validate(valContext); @@ -254,22 +282,66 @@ public final class SignatureValidationService { allValid = false; } + // Extração de metadados do signatário a partir do KeyInfo + String signerName = "Signatário XML #" + (i + 1); + String identifier = ""; + String issuer = isXades ? "ICP-Brasil / XAdES" : "Assinatura Enveloped XML"; + String certSubject = "XML Signature Element"; + + if (signature.getKeyInfo() != null) { + for (Object kiContent : signature.getKeyInfo().getContent()) { + if (kiContent instanceof javax.xml.crypto.dsig.keyinfo.X509Data x509Data) { + for (Object o : x509Data.getContent()) { + if (o instanceof X509Certificate cert) { + certSubject = cert.getSubjectX500Principal().getName(); + signerName = X509CertificateUtils.extractCommonName(certSubject); + String cpf = X509CertificateUtils.extractCpf(cert, signerName); + if (cpf != null) { + identifier = "CPF: " + cpf; + } else { + String cnpj = X509CertificateUtils.extractCnpj(cert, signerName); + if (cnpj != null) { + identifier = "CNPJ: " + cnpj; + } + } + issuer = X509CertificateUtils.extractCommonName(cert.getIssuerX500Principal().getName()); + break; + } + } + } + } + } + + // Data de assinatura (SigningTime se XAdES, senão data atual) + String dateStr = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(new Date()); + if (isXades) { + NodeList stList = doc.getElementsByTagNameNS("http://uri.etsi.org/01903/v1.3.2#", "SigningTime"); + if (stList.getLength() > 0) { + dateStr = stList.item(0).getTextContent(); + } + } + signers.add(new ValidationSignerInfo( - "Signatário XML #" + (i + 1), - "", - "Assinatura Enveloped XML", - new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(new Date()), + signerName, + identifier, + issuer, + dateStr, coreValidity, coreValidity, signature.getSignedInfo().getSignatureMethod().getAlgorithm(), - "XMLDSig Element", - "Validade estrutural e criptográfica: " + (coreValidity ? "OK" : "FALHA") + certSubject, + (isXades ? "Padrão XAdES-BES (ETSI TS 101 903 / ICP-Brasil)" : "Padrão XMLDSig W3C") + + " • " + (coreValidity ? "Assinatura íntegra" : "Integridade violada") )); } ValidationStatus status = allValid ? ValidationStatus.VALID : ValidationStatus.INVALID; - String msg = allValid ? "Documento XML assinado e válido conforme padrão W3C XML-DSig." : "Assinatura XML inválida ou com integridade violada."; - return new ValidationResult(status, ValidationType.XML_DSIG, msg, signers, null, fileName); + String msg = allValid + ? (isXades + ? "Documento XML assinado e íntegro no padrão avançado XAdES-BES (ICP-Brasil)." + : "Documento XML assinado e válido conforme padrão W3C XMLDSig.") + : "Assinatura XML inválida ou com integridade violada."; + return new ValidationResult(status, valType, msg, signers, null, fileName); } catch (Exception e) { logger.error("Erro ao validar XML: {}", e.getMessage(), e); @@ -284,6 +356,23 @@ public final class SignatureValidationService { } } + private static void registerAllIdAttributes(Element root) { + if (root == null) return; + if (root.hasAttribute("Id")) { + root.setIdAttribute("Id", true); + } else if (root.hasAttribute("id")) { + root.setIdAttribute("id", true); + } else if (root.hasAttribute("ID")) { + root.setIdAttribute("ID", true); + } + NodeList children = root.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + if (children.item(i) instanceof Element child) { + registerAllIdAttributes(child); + } + } + } + /** * Processa e valida criptograficamente os signatários de um pacote CMSSignedData. */ diff --git a/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/validation/ValidationType.java b/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/validation/ValidationType.java index 8be237c..728c821 100644 --- a/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/validation/ValidationType.java +++ b/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/validation/ValidationType.java @@ -7,6 +7,7 @@ public enum ValidationType { DETACHED_CMS("Assinatura Destacada (CAdES / PKCS#7)", false), ATTACHED_CMS("Assinatura Incorporada (CMS / P7M)", true), PADES_PDF("Assinatura Incorporada em PDF (PAdES)", true), + XADES_XML("Assinatura Incorporada em XML (XAdES / ICP-Brasil)", true), XML_DSIG("Assinatura Incorporada em XML (XML-DSig)", true), UNKNOWN("Formato Desconhecido", false); diff --git a/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/xml/XmlSignatureOptions.java b/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/xml/XmlSignatureOptions.java index 75424ef..117748e 100644 --- a/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/xml/XmlSignatureOptions.java +++ b/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/xml/XmlSignatureOptions.java @@ -3,30 +3,43 @@ package br.com.cybercore.signer.core.xml; import java.util.Objects; /** - * Parâmetros de configuração para a operação de assinatura digital XMLDSig. + * Parâmetros de configuração para a operação de assinatura digital XML (XAdES ou XMLDSig). */ public record XmlSignatureOptions( String certificateId, char[] pinOrPassword, String targetElementTag, String targetElementId, - String signatureAlgorithm + String signatureAlgorithm, + XmlSignatureProfile profile ) { public static final String DEFAULT_ALGORITHM = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"; + public static final XmlSignatureProfile DEFAULT_PROFILE = XmlSignatureProfile.XADES_BES; public XmlSignatureOptions { Objects.requireNonNull(certificateId, "ID do certificado não pode ser nulo."); if (signatureAlgorithm == null || signatureAlgorithm.isBlank()) { signatureAlgorithm = DEFAULT_ALGORITHM; } + if (profile == null) { + profile = DEFAULT_PROFILE; + } } public static XmlSignatureOptions wholeDocument(String certificateId, char[] pinOrPassword) { - return new XmlSignatureOptions(certificateId, pinOrPassword, null, null, DEFAULT_ALGORITHM); + return new XmlSignatureOptions(certificateId, pinOrPassword, null, null, DEFAULT_ALGORITHM, DEFAULT_PROFILE); + } + + public static XmlSignatureOptions wholeDocument(String certificateId, char[] pinOrPassword, XmlSignatureProfile profile) { + return new XmlSignatureOptions(certificateId, pinOrPassword, null, null, DEFAULT_ALGORITHM, profile); } public static XmlSignatureOptions forElement(String certificateId, char[] pinOrPassword, String tag, String id) { - return new XmlSignatureOptions(certificateId, pinOrPassword, tag, id, DEFAULT_ALGORITHM); + return new XmlSignatureOptions(certificateId, pinOrPassword, tag, id, DEFAULT_ALGORITHM, DEFAULT_PROFILE); + } + + public static XmlSignatureOptions forElement(String certificateId, char[] pinOrPassword, String tag, String id, XmlSignatureProfile profile) { + return new XmlSignatureOptions(certificateId, pinOrPassword, tag, id, DEFAULT_ALGORITHM, profile); } } diff --git a/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/xml/XmlSignatureProfile.java b/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/xml/XmlSignatureProfile.java new file mode 100644 index 0000000..3f6b047 --- /dev/null +++ b/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/xml/XmlSignatureProfile.java @@ -0,0 +1,37 @@ +package br.com.cybercore.signer.core.xml; + +/** + * Perfis de assinatura digital para documentos XML suportados pelo CyberCore Signer. + */ +public enum XmlSignatureProfile { + + /** + * Padrão XAdES-BES (XML Advanced Electronic Signatures - Basic Electronic Signature). + * Em conformidade com a especificação ETSI TS 101 903 e padrão ICP-Brasil DOC-ICP-15.03 (AD-RB). + * Inclui propriedades qualificadas assinadas: data/hora de assinatura (SigningTime) + * e referência criptográfica do certificado do signatário (SigningCertificate). + */ + XADES_BES("XAdES-BES (Padrão ICP-Brasil / ETSI)", "Assinatura avançada com data/hora e certificado qualificado vinculados"), + + /** + * Padrão W3C XMLDSig convencional (Enveloped Signature sem propriedades qualificadas XAdES). + * Mantido para compatibilidade com layouts fiscais estritos (ex: NF-e, CT-e, MDF-e SEFAZ legados). + */ + XMLDSIG("XMLDSig Clássico (W3C)", "Assinatura enveloped básica sem propriedades qualificadas"); + + private final String displayName; + private final String description; + + XmlSignatureProfile(String displayName, String description) { + this.displayName = displayName; + this.description = description; + } + + public String getDisplayName() { + return displayName; + } + + public String getDescription() { + return description; + } +} diff --git a/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/xml/XmlSigningService.java b/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/xml/XmlSigningService.java index 9699515..9836b3e 100644 --- a/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/xml/XmlSigningService.java +++ b/modules/cybercore-core/src/main/java/br/com/cybercore/signer/core/xml/XmlSigningService.java @@ -17,12 +17,14 @@ import javax.xml.crypto.KeySelector; import javax.xml.crypto.KeySelectorException; import javax.xml.crypto.KeySelectorResult; import javax.xml.crypto.XMLCryptoContext; +import javax.xml.crypto.XMLStructure; +import javax.xml.crypto.dom.DOMStructure; import javax.xml.crypto.dsig.CanonicalizationMethod; import javax.xml.crypto.dsig.DigestMethod; import javax.xml.crypto.dsig.Reference; -import javax.xml.crypto.dsig.SignatureMethod; import javax.xml.crypto.dsig.SignedInfo; import javax.xml.crypto.dsig.Transform; +import javax.xml.crypto.dsig.XMLObject; import javax.xml.crypto.dsig.XMLSignature; import javax.xml.crypto.dsig.XMLSignatureFactory; import javax.xml.crypto.dsig.dom.DOMSignContext; @@ -43,25 +45,41 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.security.Key; +import java.security.MessageDigest; import java.security.PublicKey; import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.UUID; /** - * Serviço de alto nível para Assinatura Digital de Documentos XML segundo o padrão W3C XMLDSig e ICP-Brasil. - * Suporta assinatura enveloped de documento completo e assinatura de elementos por ID (NF-e, SPED, etc.). + * Serviço de alto nível para Assinatura Digital de Documentos XML segundo os padrões: + * - XAdES-BES (ETSI TS 101 903 / ICP-Brasil DOC-ICP-15.03 - Padrão Avançado) + * - W3C XMLDSig convencional (Enveloped / NF-e / SPED legado) + * * Utiliza o suporte de fábrica nativo e seguro da plataforma Java (javax.xml.crypto.dsig). */ public class XmlSigningService { private static final Logger logger = LoggerFactory.getLogger(XmlSigningService.class); + public static final String XADES_132_NS = "http://uri.etsi.org/01903/v1.3.2#"; + public static final String XADES_SIGNED_PROPERTIES_TYPE = "http://uri.etsi.org/01903#SignedProperties"; + public static final String XMLDSIG_NS = XMLSignature.XMLNS; + + private static final DateTimeFormatter ISO_INSTANT_FORMATTER = + DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss'Z'").withZone(ZoneOffset.UTC); + private final CertificateManager certificateManager; public XmlSigningService(CertificateManager certificateManager) { @@ -85,7 +103,7 @@ public class XmlSigningService { try (FileOutputStream fos = new FileOutputStream(targetXml)) { fos.write(signedBytes); } - logger.info("XML assinado com sucesso e salvo em: {}", targetXml.getAbsolutePath()); + logger.info("XML assinado com sucesso ({}) e salvo em: {}", options.profile().getDisplayName(), targetXml.getAbsolutePath()); } /** @@ -117,7 +135,8 @@ public class XmlSigningService { } SigningKeyMaterial keyMaterial = keyMaterialOpt.get(); - logger.info("Iniciando assinatura XMLDSig com certificado '{}' [tipo={}]", cert.commonName(), cert.type()); + logger.info("Iniciando assinatura XML [perfil={}] com certificado '{}' [tipo={}]", + options.profile(), cert.commonName(), cert.type()); // 2. Parse Seguro do XML (Prevenção rigorosa de XXE) DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); @@ -134,13 +153,13 @@ public class XmlSigningService { // 3. Configuração do XMLSignatureFactory XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM"); - // Transformações padrão: Enveloped + C14N - List transforms = new ArrayList<>(); - transforms.add(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)); - transforms.add(fac.newTransform(CanonicalizationMethod.INCLUSIVE, (TransformParameterSpec) null)); + // Transformações padrão para os dados de negócio: Enveloped + C14N + List dataTransforms = new ArrayList<>(); + dataTransforms.add(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)); + dataTransforms.add(fac.newTransform(CanonicalizationMethod.INCLUSIVE, (TransformParameterSpec) null)); - // Determinação da Referência e Ponto de Inserção - Reference ref; + // Determinação da Referência de Dados e Ponto de Inserção da Assinatura + Reference dataRef; Node insertionParent; if (options.targetElementId() != null && !options.targetElementId().isBlank()) { @@ -150,7 +169,7 @@ public class XmlSigningService { throw new IllegalArgumentException("Elemento XML com ID '" + elemId + "' não encontrado no documento."); } targetElement.setIdAttribute("Id", true); - ref = fac.newReference("#" + elemId, fac.newDigestMethod(DigestMethod.SHA256, null), transforms, null, null); + dataRef = fac.newReference("#" + elemId, fac.newDigestMethod(DigestMethod.SHA256, null), dataTransforms, null, null); insertionParent = (targetElement.getParentNode() != null) ? targetElement.getParentNode() : doc.getDocumentElement(); } else if (options.targetElementTag() != null && !options.targetElementTag().isBlank()) { NodeList list = doc.getElementsByTagName(options.targetElementTag()); @@ -160,42 +179,87 @@ public class XmlSigningService { if (list.getLength() > 0 && list.item(0) instanceof Element elem && elem.hasAttribute("Id")) { String idVal = elem.getAttribute("Id"); elem.setIdAttribute("Id", true); - ref = fac.newReference("#" + idVal, fac.newDigestMethod(DigestMethod.SHA256, null), transforms, null, null); + dataRef = fac.newReference("#" + idVal, fac.newDigestMethod(DigestMethod.SHA256, null), dataTransforms, null, null); insertionParent = (elem.getParentNode() != null) ? elem.getParentNode() : doc.getDocumentElement(); } else { - ref = fac.newReference("", fac.newDigestMethod(DigestMethod.SHA256, null), transforms, null, null); + dataRef = fac.newReference("", fac.newDigestMethod(DigestMethod.SHA256, null), dataTransforms, null, null); insertionParent = doc.getDocumentElement(); } } else { // Assinatura Enveloped do documento completo - ref = fac.newReference("", fac.newDigestMethod(DigestMethod.SHA256, null), transforms, null, null); + dataRef = fac.newReference("", fac.newDigestMethod(DigestMethod.SHA256, null), dataTransforms, null, null); insertionParent = doc.getDocumentElement(); } - // 4. Montagem do SignedInfo + List references = new ArrayList<>(); + references.add(dataRef); + + List xmlObjects = new ArrayList<>(); + String signatureUniqueId = "sig-" + UUID.randomUUID(); + Element signedPropertiesElem = null; + + // 4. Se for perfil XAdES-BES, constrói as QualifyingProperties e a Reference correspondente + if (options.profile() == XmlSignatureProfile.XADES_BES) { + String signedPropsId = "signed-props-" + UUID.randomUUID(); + signedPropertiesElem = buildXadesSignedProperties(doc, signedPropsId, keyMaterial.signingCertificate(), Instant.now()); + + Element qualifyingProperties = doc.createElementNS(XADES_132_NS, "xades:QualifyingProperties"); + qualifyingProperties.setAttribute("Target", "#" + signatureUniqueId); + qualifyingProperties.appendChild(signedPropertiesElem); + + XMLStructure xadesStructure = new DOMStructure(qualifyingProperties); + XMLObject xadesObject = fac.newXMLObject(Collections.singletonList(xadesStructure), null, null, null); + xmlObjects.add(xadesObject); + + // Adiciona Referência Criptográfica cobrindo as SignedProperties + List xadesTransforms = Collections.singletonList( + fac.newTransform(CanonicalizationMethod.INCLUSIVE, (TransformParameterSpec) null) + ); + Reference xadesRef = fac.newReference( + "#" + signedPropsId, + fac.newDigestMethod(DigestMethod.SHA256, null), + xadesTransforms, + XADES_SIGNED_PROPERTIES_TYPE, + null + ); + references.add(xadesRef); + } + + // 5. Montagem do SignedInfo SignedInfo si = fac.newSignedInfo( fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null), fac.newSignatureMethod(options.signatureAlgorithm(), null), - Collections.singletonList(ref) + references ); - // 5. Inclusão da Cadeia de Certificados no KeyInfo + // 6. Inclusão da Cadeia de Certificados no KeyInfo KeyInfoFactory kif = fac.getKeyInfoFactory(); List x509Content = new ArrayList<>(); x509Content.add(keyMaterial.signingCertificate()); X509Data x509Data = kif.newX509Data(x509Content); KeyInfo ki = kif.newKeyInfo(Collections.singletonList(x509Data)); - // 6. Execução da Assinatura DOM + // 7. Execução da Assinatura DOM DOMSignContext dsc = new DOMSignContext(keyMaterial.privateKey(), insertionParent); if (keyMaterial.jcaProvider() != null) { dsc.setProperty("org.jcp.xml.dsig.internal.dom.SignatureProvider", keyMaterial.jcaProvider()); } - XMLSignature signature = fac.newXMLSignature(si, ki); + if (signedPropertiesElem != null) { + dsc.setIdAttributeNS(signedPropertiesElem, null, "Id"); + } + + XMLSignature signature = fac.newXMLSignature( + si, + ki, + xmlObjects, + signatureUniqueId, + null + ); + signature.sign(dsc); - // 7. Serialização do XML Assinado + // 8. Serialização do XML Assinado TransformerFactory tf = TransformerFactory.newInstance(); Transformer trans = tf.newTransformer(); trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); @@ -205,12 +269,13 @@ public class XmlSigningService { ByteArrayOutputStream baos = new ByteArrayOutputStream(); trans.transform(new DOMSource(doc), new StreamResult(baos)); - logger.info("Assinatura XMLDSig gerada com sucesso. Tamanho: {} bytes", baos.size()); + logger.info("Assinatura XML ({}) gerada com sucesso. Tamanho final: {} bytes", + options.profile(), baos.size()); return baos.toByteArray(); } /** - * Valida a integridade criptográfica de um documento XML assinado com XMLDSig. + * Valida a integridade criptográfica de um documento XML assinado com XAdES ou XMLDSig. */ public boolean validateXmlSignature(byte[] signedXmlBytes) throws Exception { Objects.requireNonNull(signedXmlBytes, "Bytes do XML assinado não podem ser nulos."); @@ -218,24 +283,17 @@ public class XmlSigningService { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); Document doc = dbf.newDocumentBuilder().parse(new ByteArrayInputStream(signedXmlBytes)); - // Registra nós com atributo Id/id/ID para que o ResourceResolver do XMLDSig localize a referência - NodeList allElements = doc.getElementsByTagName("*"); - for (int i = 0; i < allElements.getLength(); i++) { - if (allElements.item(i) instanceof Element e) { - if (e.hasAttribute("Id")) { - e.setIdAttribute("Id", true); - } else if (e.hasAttribute("id")) { - e.setIdAttribute("id", true); - } else if (e.hasAttribute("ID")) { - e.setIdAttribute("ID", true); - } - } - } + // Registra nós com atributo Id/id/ID para que o ResourceResolver localize as referências + registerIdAttributes(doc.getDocumentElement()); - NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); + NodeList nl = doc.getElementsByTagNameNS(XMLDSIG_NS, "Signature"); if (nl.getLength() == 0) { nl = doc.getElementsByTagName("Signature"); } @@ -248,8 +306,168 @@ public class XmlSigningService { XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM"); DOMValidateContext valContext = new DOMValidateContext(new X509KeySelector(), sigElement); + // Registra explicitamente elementos com atributo Id no valContext + NodeList allNodes = doc.getElementsByTagName("*"); + for (int i = 0; i < allNodes.getLength(); i++) { + if (allNodes.item(i) instanceof Element e) { + if (e.hasAttribute("Id")) { + valContext.setIdAttributeNS(e, null, "Id"); + } + } + } + XMLSignature signature = fac.unmarshalXMLSignature(valContext); - return signature.validate(valContext); + boolean coreValidity = signature.validate(valContext); + if (!coreValidity) { + boolean sv = signature.getSignatureValue().validate(valContext); + logger.warn("Validação XML falhou. SignatureValue válido: {}", sv); + int refIdx = 0; + for (Object o : signature.getSignedInfo().getReferences()) { + if (o instanceof Reference r) { + boolean rValid = r.validate(valContext); + byte[] calcDigest = r.getCalculatedDigestValue(); + byte[] storedDigest = r.getDigestValue(); + String calcB64 = calcDigest != null ? Base64.getEncoder().encodeToString(calcDigest) : "null"; + String storedB64 = storedDigest != null ? Base64.getEncoder().encodeToString(storedDigest) : "null"; + logger.warn("Reference[{}] URI='{}' válida: {} | calcDigest={} | storedDigest={}", + refIdx++, r.getURI(), rValid, calcB64, storedB64); + } + } + } + return coreValidity; + } + + /** + * Inspeciona e extrai informações detalhadas da assinatura XML (XAdES ou XMLDSig). + */ + public XmlSignatureDetails inspectXmlSignature(byte[] signedXmlBytes) throws Exception { + Objects.requireNonNull(signedXmlBytes, "Bytes do XML assinado não podem ser nulos."); + + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + + Document doc = dbf.newDocumentBuilder().parse(new ByteArrayInputStream(signedXmlBytes)); + registerIdAttributes(doc.getDocumentElement()); + + NodeList nl = doc.getElementsByTagNameNS(XMLDSIG_NS, "Signature"); + if (nl.getLength() == 0) { + nl = doc.getElementsByTagName("Signature"); + } + if (nl.getLength() == 0) { + return null; + } + + Element sigElem = (Element) nl.item(0); + String sigId = sigElem.getAttribute("Id"); + + // Verifica presença de XAdES + NodeList qpNodes = doc.getElementsByTagNameNS(XADES_132_NS, "QualifyingProperties"); + boolean isXades = qpNodes.getLength() > 0; + + String signingTime = null; + if (isXades) { + NodeList timeNodes = doc.getElementsByTagNameNS(XADES_132_NS, "SigningTime"); + if (timeNodes.getLength() > 0) { + signingTime = timeNodes.item(0).getTextContent(); + } + } + + // Validação criptográfica + boolean isValid = false; + try { + isValid = validateXmlSignature(signedXmlBytes); + } catch (Exception ex) { + logger.warn("Validação da assinatura XML falhou durante inspeção: {}", ex.getMessage()); + } + + String sigMethod = ""; + NodeList smNodes = sigElem.getElementsByTagNameNS(XMLDSIG_NS, "SignatureMethod"); + if (smNodes.getLength() > 0 && smNodes.item(0) instanceof Element smElem) { + sigMethod = smElem.getAttribute("Algorithm"); + } + + return new XmlSignatureDetails( + isValid, + isXades ? XmlSignatureProfile.XADES_BES : XmlSignatureProfile.XMLDSIG, + sigMethod, + signingTime, + sigId + ); + } + + /** + * Constrói a estrutura DOM das propriedades assinadas do XAdES (SignedProperties). + */ + private Element buildXadesSignedProperties(Document doc, String signedPropsId, X509Certificate cert, Instant signInstant) throws Exception { + Element signedProperties = doc.createElementNS(XADES_132_NS, "xades:SignedProperties"); + signedProperties.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xades", XADES_132_NS); + signedProperties.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:ds", XMLDSIG_NS); + signedProperties.setAttribute("Id", signedPropsId); + signedProperties.setIdAttribute("Id", true); + + Element signedSignatureProperties = doc.createElementNS(XADES_132_NS, "xades:SignedSignatureProperties"); + + // 1. Data e Hora UTC da Assinatura + Element signingTime = doc.createElementNS(XADES_132_NS, "xades:SigningTime"); + signingTime.setTextContent(ISO_INSTANT_FORMATTER.format(signInstant)); + signedSignatureProperties.appendChild(signingTime); + + // 2. Vínculo Criptográfico do Certificado do Signatário (SigningCertificate) + Element signingCertificate = doc.createElementNS(XADES_132_NS, "xades:SigningCertificate"); + Element certElem = doc.createElementNS(XADES_132_NS, "xades:Cert"); + + // Digest SHA-256 do Certificado DER + Element certDigest = doc.createElementNS(XADES_132_NS, "xades:CertDigest"); + Element digestMethod = doc.createElementNS(XMLDSIG_NS, "ds:DigestMethod"); + digestMethod.setAttribute("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256"); + + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] certHash = md.digest(cert.getEncoded()); + String certHashB64 = Base64.getEncoder().encodeToString(certHash); + + Element digestValue = doc.createElementNS(XMLDSIG_NS, "ds:DigestValue"); + digestValue.setTextContent(certHashB64); + + certDigest.appendChild(digestMethod); + certDigest.appendChild(digestValue); + certElem.appendChild(certDigest); + + // Dados do Emissor e Serial do Certificado + Element issuerSerial = doc.createElementNS(XADES_132_NS, "xades:IssuerSerial"); + Element x509IssuerName = doc.createElementNS(XMLDSIG_NS, "ds:X509IssuerName"); + x509IssuerName.setTextContent(cert.getIssuerX500Principal().getName()); + + Element x509SerialNumber = doc.createElementNS(XMLDSIG_NS, "ds:X509SerialNumber"); + x509SerialNumber.setTextContent(cert.getSerialNumber().toString()); + + issuerSerial.appendChild(x509IssuerName); + issuerSerial.appendChild(x509SerialNumber); + certElem.appendChild(issuerSerial); + + signingCertificate.appendChild(certElem); + signedSignatureProperties.appendChild(signingCertificate); + + signedProperties.appendChild(signedSignatureProperties); + return signedProperties; + } + + private static void registerIdAttributes(Element root) { + if (root == null) return; + if (root.hasAttribute("Id")) { + root.setIdAttribute("Id", true); + } else if (root.hasAttribute("id")) { + root.setIdAttribute("id", true); + } else if (root.hasAttribute("ID")) { + root.setIdAttribute("ID", true); + } + + NodeList children = root.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + if (children.item(i) instanceof Element child) { + registerIdAttributes(child); + } + } } private static Element findElementById(Document doc, String id, String hintTag) { @@ -264,6 +482,17 @@ public class XmlSigningService { return doc.getElementById(id); } + /** + * DTO de detalhes de uma assinatura XML. + */ + public record XmlSignatureDetails( + boolean valid, + XmlSignatureProfile profile, + String algorithm, + String signingTime, + String signatureId + ) {} + /** * Seletor de chave pública baseado no certificado X.509 embutido no KeyInfo. */ diff --git a/modules/cybercore-core/src/test/java/br/com/cybercore/signer/core/xml/XmlSigningServiceTest.java b/modules/cybercore-core/src/test/java/br/com/cybercore/signer/core/xml/XmlSigningServiceTest.java index 254028b..bd4eac0 100644 --- a/modules/cybercore-core/src/test/java/br/com/cybercore/signer/core/xml/XmlSigningServiceTest.java +++ b/modules/cybercore-core/src/test/java/br/com/cybercore/signer/core/xml/XmlSigningServiceTest.java @@ -72,8 +72,8 @@ class XmlSigningServiceTest { } @Test - @DisplayName("Deve assinar documento XML completo com XMLDSig Enveloped e validar com sucesso") - void shouldSignWholeXmlDocumentAndValidate() throws Exception { + @DisplayName("Deve assinar documento XML no padrão XAdES-BES por padrão e validar com sucesso") + void shouldSignWholeXmlDocumentWithXadesBesByDefaultAndValidate() throws Exception { String xml = """ @@ -86,19 +86,38 @@ class XmlSigningServiceTest { byte[] signedXmlBytes = xmlSigningService.signXml(xml.getBytes(StandardCharsets.UTF_8), options); String signedXmlStr = new String(signedXmlBytes, StandardCharsets.UTF_8); + + // Verifica tags essenciais do XMLDSig assertThat(signedXmlStr).contains(" @@ -117,21 +136,48 @@ class XmlSigningServiceTest { testCertId, null, "infNFe", - "NFe35261012345678000195550010000000011000000010" + "NFe35261012345678000195550010000000011000000010", + XmlSignatureProfile.XADES_BES ); byte[] signedNfe = xmlSigningService.signXml(nfeXml.getBytes(StandardCharsets.UTF_8), options); String signedStr = new String(signedNfe, StandardCharsets.UTF_8); assertThat(signedStr).contains("URI=\"#NFe35261012345678000195550010000000011000000010\""); + assertThat(signedStr).contains("QualifyingProperties"); + assertThat(signedStr).contains("SignedProperties"); boolean isValid = xmlSigningService.validateXmlSignature(signedNfe); assertThat(isValid).isTrue(); } @Test - @DisplayName("Deve detectar adulteração em XML assinado e reprovar na validação") - void shouldDetectTamperingInSignedXml() throws Exception { + @DisplayName("Deve assinar documento XML no perfil XMLDSig clássico quando explicitamente solicitado") + void shouldSignWithClassicXmlDsigWhenConfigured() throws Exception { + String xml = """ + + + Valor de teste + + """; + + XmlSignatureOptions options = XmlSignatureOptions.wholeDocument(testCertId, null, XmlSignatureProfile.XMLDSIG); + byte[] signedXmlBytes = xmlSigningService.signXml(xml.getBytes(StandardCharsets.UTF_8), options); + + String signedXmlStr = new String(signedXmlBytes, StandardCharsets.UTF_8); + assertThat(signedXmlStr).contains(" @@ -142,11 +188,36 @@ class XmlSigningServiceTest { XmlSignatureOptions options = XmlSignatureOptions.wholeDocument(testCertId, null); byte[] signedXml = xmlSigningService.signXml(xml.getBytes(StandardCharsets.UTF_8), options); - // Adulteração intencional do conteúdo original + // Adulteração no payload de negócio String tampered = new String(signedXml, StandardCharsets.UTF_8) .replace("R$ 100,00", "R$ 999.999,00"); boolean isValid = xmlSigningService.validateXmlSignature(tampered.getBytes(StandardCharsets.UTF_8)); assertThat(isValid).isFalse(); } + + @Test + @DisplayName("Deve detectar adulteração nas propriedades qualificadas do XAdES (SigningTime)") + void shouldDetectTamperingInXadesSignedProperties() throws Exception { + String xml = """ + + + Assinado conforme regras XAdES + + """; + + XmlSignatureOptions options = XmlSignatureOptions.wholeDocument(testCertId, null); + byte[] signedXml = xmlSigningService.signXml(xml.getBytes(StandardCharsets.UTF_8), options); + + String signedStr = new String(signedXml, StandardCharsets.UTF_8); + + // Adulteração fraudulenta da data da assinatura no nó XAdES + String tampered = signedStr.replaceFirst( + "[^<]+", + "1999-01-01T00:00:00Z" + ); + + boolean isValid = xmlSigningService.validateXmlSignature(tampered.getBytes(StandardCharsets.UTF_8)); + assertThat(isValid).isFalse(); + } } diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/BatchSigningDialog.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/BatchSigningDialog.java index e23e640..1346f65 100644 --- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/BatchSigningDialog.java +++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/BatchSigningDialog.java @@ -386,7 +386,7 @@ public final class BatchSigningDialog { Path dest = targetDir.resolve(outPdfName); pdfService.signPdfFile(item.getFile(), dest.toFile(), options); } else if (lowerName.endsWith(".xml")) { - // Assinatura XMLDSig de documento XML + // Assinatura XAdES-BES de documento XML (ETSI TS 101 903 / ICP-Brasil) br.com.cybercore.signer.core.xml.XmlSigningService xmlService = new br.com.cybercore.signer.core.xml.XmlSigningService(certManager); br.com.cybercore.signer.core.xml.XmlSignatureOptions options = diff --git a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/XmlSignDialog.java b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/XmlSignDialog.java index bb9d1c2..a9656ad 100644 --- a/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/XmlSignDialog.java +++ b/modules/cybercore-ui/src/main/java/br/com/cybercore/signer/ui/view/XmlSignDialog.java @@ -5,6 +5,7 @@ import br.com.cybercore.signer.core.domain.ManagedCertificate; import br.com.cybercore.signer.core.provider.CertificateManager; import br.com.cybercore.signer.core.security.PinSessionManager; 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 br.com.cybercore.signer.ui.components.SvgIcons; import br.com.cybercore.signer.ui.theme.ThemeManager; @@ -25,6 +26,7 @@ import javafx.scene.control.PasswordField; import javafx.scene.control.ProgressIndicator; import javafx.scene.control.RadioButton; import javafx.scene.control.ScrollPane; +import javafx.scene.control.Separator; import javafx.scene.control.TextField; import javafx.scene.control.ToggleGroup; import javafx.scene.layout.ColumnConstraints; @@ -68,7 +70,9 @@ public final class XmlSignDialog { private PasswordField pinField; private Label lblPinNotice; - // Controles de Modo de Assinatura + // Controles de Padrão e Modo de Assinatura + private RadioButton rbXadesBes; + private RadioButton rbXmlDsig; private RadioButton rbWholeDocument; private RadioButton rbSpecificElement; private TextField txtElementTag; @@ -94,10 +98,10 @@ public final class XmlSignDialog { if (owner != null) { this.stage.initOwner(owner); } - this.stage.setTitle("CyberCore Signer — Assinatura Digital de XML (XMLDSig)"); + this.stage.setTitle("CyberCore Signer — Assinatura Digital de XML (XAdES / ICP-Brasil)"); this.stage.setResizable(true); this.stage.setMinWidth(720); - this.stage.setMinHeight(580); + this.stage.setMinHeight(600); initLayout(); } @@ -111,9 +115,9 @@ public final class XmlSignDialog { HBox header = new HBox(14); header.setAlignment(Pos.CENTER_LEFT); VBox titleBox = new VBox(4); - Label lblTitle = new Label("ASSINATURA DE DOCUMENTO XML (XMLDSIG)"); + Label lblTitle = new Label("ASSINATURA DE DOCUMENTO XML (XADES / ICP-BRASIL)"); lblTitle.getStyleClass().add("dialog-header"); - Label lblSub = new Label("Assine digitalmente documentos XML no padrão W3C XMLDSig e ICP-Brasil (NF-e, CT-e, MDF-e, eSocial, etc.)."); + Label lblSub = new Label("Assine digitalmente documentos XML no padrão avançado XAdES (ETSI TS 101 903 / ICP-Brasil DOC-ICP-15.03) ou XMLDSig."); lblSub.getStyleClass().add("dialog-subtitle"); titleBox.getChildren().addAll(lblTitle, lblSub); header.getChildren().addAll(SvgIcons.xmlIcon(), titleBox); @@ -198,7 +202,21 @@ public final class XmlSignDialog { // 3. Seção: Modo de Assinatura XMLDSig VBox xmlModeSection = new VBox(12); xmlModeSection.getStyleClass().add("dialog-box"); - Label lblModeTitle = new Label("MODO DE ASSINATURA XMLDSIG"); + Label lblProfileTitle = new Label("PADRÃO DE ASSINATURA XML"); + lblProfileTitle.getStyleClass().add("dialog-field-title"); + + ToggleGroup profileGroup = new ToggleGroup(); + rbXadesBes = new RadioButton("XAdES-BES (ETSI TS 101 903 / ICP-Brasil DOC-ICP-15.03 - Padrão Avançado)"); + rbXadesBes.setToggleGroup(profileGroup); + rbXadesBes.setSelected(true); + + rbXmlDsig = new RadioButton("XMLDSig Clássico W3C (Enveloped legado / NF-e SEFAZ)"); + rbXmlDsig.setToggleGroup(profileGroup); + + VBox profileBox = new VBox(8); + profileBox.getChildren().addAll(rbXadesBes, rbXmlDsig); + + Label lblModeTitle = new Label("ESCOPO DE ASSINATURA"); lblModeTitle.getStyleClass().add("dialog-field-title"); ToggleGroup modeGroup = new ToggleGroup(); @@ -247,7 +265,16 @@ public final class XmlSignDialog { chkValidateAfter = new CheckBox("Validar conformidade e integridade criptográfica após gravação"); chkValidateAfter.setSelected(true); - xmlModeSection.getChildren().addAll(lblModeTitle, rbWholeDocument, rbSpecificElement, elementConfigBox, chkValidateAfter); + xmlModeSection.getChildren().addAll( + lblProfileTitle, + profileBox, + new Separator(), + lblModeTitle, + rbWholeDocument, + rbSpecificElement, + elementConfigBox, + chkValidateAfter + ); formContent.getChildren().addAll(fileSection, certSection, xmlModeSection); scrollPane.setContent(formContent); @@ -389,6 +416,10 @@ public final class XmlSignDialog { pin = pinField.getText().toCharArray(); } + XmlSignatureProfile profile = rbXadesBes.isSelected() + ? XmlSignatureProfile.XADES_BES + : XmlSignatureProfile.XMLDSIG; + XmlSignatureOptions options; if (rbSpecificElement.isSelected()) { String tag = txtElementTag.getText().trim(); @@ -396,14 +427,14 @@ public final class XmlSignDialog { if (tag.isBlank()) { tag = "infNFe"; } - options = XmlSignatureOptions.forElement(cert.id(), pin, tag, id.isBlank() ? null : id); + options = XmlSignatureOptions.forElement(cert.id(), pin, tag, id.isBlank() ? null : id, profile); } else { - options = XmlSignatureOptions.wholeDocument(cert.id(), pin); + options = XmlSignatureOptions.wholeDocument(cert.id(), pin, profile); } setFormDisabled(true); progressIndicator.setVisible(true); - lblStatus.setText("Assinando documento XML (XMLDSig)..."); + lblStatus.setText("Assinando documento XML (" + profile.getDisplayName() + ")..."); lblStatus.setStyle("-fx-text-fill: -fx-accent;"); boolean validate = chkValidateAfter.isSelected();