Created
January 30, 2025 02:40
-
-
Save robsonkades/d79ba905cab68f9c41de8e6d01d9063e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.w3c.dom.Document; | |
import org.w3c.dom.Element; | |
import org.w3c.dom.Node; | |
import org.w3c.dom.NodeList; | |
import javax.xml.XMLConstants; | |
import javax.xml.crypto.AlgorithmMethod; | |
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.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.XMLSignature; | |
import javax.xml.crypto.dsig.XMLSignatureFactory; | |
import javax.xml.crypto.dsig.dom.DOMSignContext; | |
import javax.xml.crypto.dsig.dom.DOMValidateContext; | |
import javax.xml.crypto.dsig.keyinfo.KeyInfo; | |
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory; | |
import javax.xml.crypto.dsig.keyinfo.X509Data; | |
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec; | |
import javax.xml.crypto.dsig.spec.TransformParameterSpec; | |
import javax.xml.parsers.DocumentBuilder; | |
import javax.xml.parsers.DocumentBuilderFactory; | |
import javax.xml.parsers.ParserConfigurationException; | |
import javax.xml.transform.OutputKeys; | |
import javax.xml.transform.Source; | |
import javax.xml.transform.Transformer; | |
import javax.xml.transform.TransformerException; | |
import javax.xml.transform.TransformerFactory; | |
import javax.xml.transform.dom.DOMSource; | |
import javax.xml.transform.stream.StreamResult; | |
import javax.xml.transform.stream.StreamSource; | |
import java.io.ByteArrayInputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.StringWriter; | |
import java.nio.charset.StandardCharsets; | |
import java.security.KeyStore; | |
import java.security.PrivateKey; | |
import java.security.PublicKey; | |
import java.security.cert.X509Certificate; | |
import java.util.Arrays; | |
import java.util.Collections; | |
public class XMLSignatureUtil { | |
private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = createDocumentBuilderFactory(); | |
private static final KeySelector KEY_SELECTOR = new KeyValueKeySelector(); | |
private static final XMLSignatureFactory XML_SIGNATURE_FACTORY = XMLSignatureFactory.getInstance("DOM", new org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI()); | |
public static Document createDocument(InputStream inputStream) throws Exception { | |
try (inputStream) { | |
DocumentBuilder documentBuilder = createDocumentBuilder(); | |
return documentBuilder.parse(inputStream); | |
} | |
} | |
public static boolean isValid(String xml) throws Exception { | |
try (InputStream sanitizedStream = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { | |
return isValid(sanitizedStream); | |
} | |
} | |
public static boolean isValid(InputStream inputStream) throws Exception { | |
try (InputStream sanitizedStream = sanitizeAndConvertToStream(inputStream)) { | |
Document document = createDocument(sanitizedStream); | |
NodeList rootElement = document.getElementsByTagName("NFe"); | |
if (rootElement.getLength() == 0) { | |
throw new Exception("Elemento NFe não encontrado."); | |
} | |
Element infNfeElement = findFirstElementChild(rootElement.item(0)); | |
if (infNfeElement == null) { | |
throw new Exception("Elemento filho de NFe não encontrado."); | |
} | |
infNfeElement.setIdAttribute("Id", true); | |
NodeList signatureElement = document.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); | |
if (signatureElement.getLength() == 0) { | |
throw new Exception("Elemento de assinatura não encontrado."); | |
} | |
NodeList keyInfoList = document.getElementsByTagNameNS(XMLSignature.XMLNS, "KeyInfo"); | |
if (keyInfoList.getLength() == 0) { | |
throw new Exception("Elemento KeyInfo não encontrado."); | |
} | |
DOMValidateContext domValidateContext = new DOMValidateContext(KEY_SELECTOR, signatureElement.item(0)); | |
XMLSignature signature = XML_SIGNATURE_FACTORY.unmarshalXMLSignature(domValidateContext); | |
return signature.validate(domValidateContext); | |
} | |
} | |
public static String signed(String xml, String password, byte[] certificate) throws Exception { | |
try (InputStream sanitizedStream = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { | |
return signed(sanitizedStream, password, certificate); | |
} | |
} | |
public static String signed(InputStream inputStream, String password, byte[] pfxPath) throws Exception { | |
Document document = createDocument(inputStream); | |
KeyStore keyStore = KeyStore.getInstance("PKCS12"); | |
try (var keyStream = new ByteArrayInputStream(pfxPath)) { | |
keyStore.load(keyStream, password.toCharArray()); | |
} | |
String alias = keyStore.aliases().nextElement(); | |
PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, password.toCharArray()); | |
X509Certificate certificate = (X509Certificate) keyStore.getCertificate(alias); | |
NodeList infNFeElements = document.getElementsByTagNameNS("http://www.portalfiscal.inf.br/nfe", "infNFe"); | |
String id; | |
if (infNFeElements.getLength() > 0) { | |
Element infNFeElement = (Element) infNFeElements.item(0); | |
infNFeElement.setIdAttribute("Id", true); | |
id = infNFeElement.getAttribute("Id"); | |
} else { | |
throw new Exception("Elemento infNFe não encontrado no documento XML."); | |
} | |
// Referência com as transformações exigidas | |
Reference ref = XML_SIGNATURE_FACTORY.newReference( | |
"#" + id, | |
XML_SIGNATURE_FACTORY.newDigestMethod(DigestMethod.SHA1, null), | |
Arrays.asList( | |
XML_SIGNATURE_FACTORY.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null), | |
XML_SIGNATURE_FACTORY.newTransform(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null) | |
), | |
null, | |
null | |
); | |
// Informação assinada | |
SignedInfo signedInfo = XML_SIGNATURE_FACTORY.newSignedInfo( | |
XML_SIGNATURE_FACTORY.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null), | |
XML_SIGNATURE_FACTORY.newSignatureMethod(SignatureMethod.RSA_SHA256, null), | |
Collections.singletonList(ref) | |
); | |
// Adicionar informações da chave | |
KeyInfoFactory keyInfoFactory = XML_SIGNATURE_FACTORY.getKeyInfoFactory(); | |
X509Data x509Data = keyInfoFactory.newX509Data(Collections.singletonList(certificate)); | |
KeyInfo keyInfo = keyInfoFactory.newKeyInfo(Collections.singletonList(x509Data)); | |
DOMSignContext domSignContext = new DOMSignContext(privateKey, document.getDocumentElement()); | |
XMLSignature xmlSignature = XML_SIGNATURE_FACTORY.newXMLSignature(signedInfo, keyInfo); | |
xmlSignature.sign(domSignContext); | |
TransformerFactory tf = TransformerFactory.newInstance(); | |
Transformer transformer = tf.newTransformer(); | |
StringWriter writer = new StringWriter(); | |
transformer.transform(new DOMSource(document), new StreamResult(writer)); | |
return writer.toString(); | |
} | |
private static synchronized DocumentBuilder createDocumentBuilder() throws ParserConfigurationException { | |
return DOCUMENT_BUILDER_FACTORY.newDocumentBuilder(); | |
} | |
private static DocumentBuilderFactory createDocumentBuilderFactory() { | |
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | |
factory.setNamespaceAware(true); | |
try { | |
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); | |
factory.setFeature("http://xml.org/sax/features/external-general-entities", false); | |
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); | |
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); | |
} catch (ParserConfigurationException e) { | |
throw new IllegalStateException("Erro ao configurar DocumentBuilderFactory", e); | |
} | |
return factory; | |
} | |
private static InputStream sanitizeAndConvertToStream(InputStream inputStream) throws TransformerException, IOException { | |
try (StringWriter writer = new StringWriter()) { | |
TransformerFactory transformerFactory = TransformerFactory.newInstance(); | |
transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); | |
Transformer transformer = transformerFactory.newTransformer(); | |
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); | |
transformer.setOutputProperty(OutputKeys.INDENT, "no"); | |
transformer.setOutputProperty(OutputKeys.METHOD, "xml"); | |
Source source = new StreamSource(inputStream); | |
transformer.transform(source, new StreamResult(writer)); | |
String sanitizedXml = writer.toString() | |
.replaceAll("\\s+", " ") | |
.replaceAll("\\s*>\\s*", ">") | |
.replaceAll("\\s*<\\s*", "<") | |
.trim(); | |
return new ByteArrayInputStream(sanitizedXml.getBytes(StandardCharsets.UTF_8)); | |
} | |
} | |
private static Element findFirstElementChild(Node parent) { | |
if (parent == null) return null; | |
NodeList children = parent.getChildNodes(); | |
for (int i = 0; i < children.getLength(); i++) { | |
Node child = children.item(i); | |
if (child.getNodeType() == Node.ELEMENT_NODE) { | |
return (Element) child; | |
} | |
} | |
return null; | |
} | |
// Classe interna permanece inalterada, exceto para o uso de exceções específicas. | |
private static class KeyValueKeySelector extends KeySelector { | |
public KeySelectorResult select(KeyInfo keyInfo, KeySelector.Purpose purpose, AlgorithmMethod method, XMLCryptoContext context) throws KeySelectorException { | |
if (keyInfo == null) { | |
throw new KeySelectorException("Nenhum KeyInfo encontrado!"); | |
} | |
for (XMLStructure info : keyInfo.getContent()) { | |
if (info instanceof X509Data) { | |
try { | |
for (Object data : ((X509Data) info).getContent()) { | |
if (data instanceof X509Certificate) { | |
PublicKey pk = ((X509Certificate) data).getPublicKey(); | |
return new SimpleKeySelectorResult(pk); | |
} | |
} | |
} catch (Exception e) { | |
throw new KeySelectorException(e); | |
} | |
} | |
} | |
throw new KeySelectorException("Nenhum KeyValue encontrado!"); | |
} | |
} | |
private record SimpleKeySelectorResult(PublicKey pk) implements KeySelectorResult { | |
public PublicKey getKey() { | |
return pk; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment