2011-07-29 9 views
7

Tengo algunas clases que ya usan DOM4J para leer archivos XML y proporcionar métodos getter a los datos. Ahora, necesito agregar la posibilidad de verificar XML digital firmas.Problema con la conversión de org.dom4j.Document a org.w3c.dom.Document y XML Signature

Usando org.w3c.dom y siguiendo http://java.sun.com/developer/technicalArticles/xml/dig_signature_api/ todo funciona correctamente.

Por lo tanto, trato de usar DOMWriter para convertir de org.dom4j.Document a org.w3c.dom.Document, pero después de esto la validación de la firma no funciona. Creo que sucede porque DOMWiter está cambiando el árbol XML (como parece mostrar doc4.asXML()).

Intento encontrar algo para establecer a fin de mantener la integridad del documento, pero DOMWriter no tiene tales métodos.

A continuación se muestra el código que demuestra la conversión asimétrica.

El archivo utilizado para las pruebas es http://www.robertodiasduarte.com.br/files/nfe/131090007910044_v1.10-procNFe.xml

¿Alguien sabe razones/soluciones a este?

Gracias (y lo siento, mi pobre inglés).

package testevalidanfe; 

import java.io.File; 
import java.io.FileWriter; 
import java.io.PrintWriter; 
import javax.swing.JOptionPane; 
import javax.xml.crypto.dsig.XMLSignature; 
import javax.xml.crypto.dsig.XMLSignatureFactory; 
import javax.xml.crypto.dsig.dom.DOMValidateContext; 
import javax.xml.parsers.DocumentBuilder; 
import javax.xml.parsers.DocumentBuilderFactory; 
import javax.xml.transform.Transformer; 
import javax.xml.transform.TransformerFactory; 
import javax.xml.transform.dom.DOMSource; 
import javax.xml.transform.stream.StreamResult; 
import org.dom4j.io.XMLWriter; 
import org.w3c.dom.Document; 
import org.w3c.dom.Node; 

public class Testevalidanfe { 

    public static void main(String[] args) throws Exception { 
     DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 
     dbf.setNamespaceAware(true); 
     DocumentBuilder db = dbf.newDocumentBuilder(); 
     Document d = db.parse("exemplo-nfe.xml"); 

     Node no = d.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature").item(0); 

     DOMValidateContext valContext = new DOMValidateContext(new X509KeySelector(), no); 
     XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM"); 
     XMLSignature signature = fac.unmarshalXMLSignature(valContext); 

     JOptionPane.showMessageDialog(null, "Validation using org.w3c.dom: " + signature.validate(valContext)); 
     org.dom4j.io.DOMReader domreader = new org.dom4j.io.DOMReader(); 
     org.dom4j.Document doc4 = domreader.read(d); 
     org.dom4j.io.DOMWriter domwriter = new org.dom4j.io.DOMWriter(); 
     d = domwriter.write(doc4); 

     String after = doc4.asXML(); 

     PrintWriter writer = new PrintWriter(new File("after-convertion.xml")); 
     writer.print(after); 
     writer.close(); 

     no = d.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature").item(0); 

     valContext = new DOMValidateContext(new X509KeySelector(), no); 
     fac = XMLSignatureFactory.getInstance("DOM"); 
     signature = fac.unmarshalXMLSignature(valContext); 

     JOptionPane.showMessageDialog(null, "Validation after convert: " + signature.validate(valContext)); 
    } 
} 

package testevalidanfe; 

import java.security.Key; 
import java.security.PublicKey; 
import java.security.cert.X509Certificate; 
import java.util.Iterator; 
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.SignatureMethod; 
import javax.xml.crypto.dsig.keyinfo.KeyInfo; 
import javax.xml.crypto.dsig.keyinfo.X509Data; 

public class X509KeySelector extends KeySelector { 
    public KeySelectorResult select(KeyInfo keyInfo, 
           KeySelector.Purpose purpose, 
           AlgorithmMethod method, 
           XMLCryptoContext context) 
    throws KeySelectorException { 
     Iterator ki = keyInfo.getContent().iterator(); 
     while (ki.hasNext()) { 
      XMLStructure info = (XMLStructure) ki.next(); 
      if (!(info instanceof X509Data)) 
       continue; 
      X509Data x509Data = (X509Data) info; 
      Iterator xi = x509Data.getContent().iterator(); 
      while (xi.hasNext()) { 
       Object o = xi.next(); 
       if (!(o instanceof X509Certificate)) 
        continue; 
       final PublicKey key = ((X509Certificate)o).getPublicKey(); 
       if (algEquals(method.getAlgorithm(), key.getAlgorithm())) { 
        return new KeySelectorResult() { 
         public Key getKey() { return key; } 
        }; 
       } 
      } 
     } 
     throw new KeySelectorException("No key found!"); 
    } 

    static boolean algEquals(String algURI, String algName) { 
     if ((algName.equalsIgnoreCase("DSA") && 
      algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) || 
      (algName.equalsIgnoreCase("RSA") && 
      algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1))) { 
      return true; 
     } else { 
      return false; 
     } 
    } 
} 

, por ejemplo, si el XML original comienza con:

<nfeProc versao="1.10" xmlns="http://www.portalfiscal.inf.br/nfe"> 
<NFe xmlns="http://www.portalfiscal.inf.br/nfe"> 
<infNFe Id="NFe31090807301671000131550010001000216008030809" versao="1.10" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
... 

doc4.asXML() Retorno Este:

<nfeProc xmlns="http://www.portalfiscal.inf.br/nfe" versao="1.10"> 
<NFe> 
<infNFe xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Id="NFe31090807301671000131550010001000216008030809" versao="1.10"> 
... 
+1

+1 para una pregunta bien escrita – andyb

+0

Buena pregunta. ¿Cómo cambia el árbol XML la salida de DOMWriter? ¿Que es diferente? –

+0

¡Gracias! Ver la pregunta editada. –

Respuesta

1

Tenía una mirada más cercana a esta, y resulta que DOM4J DOMWriter está haciendo algo extraño wrt espacios de nombres que obviamente confunden el proceso de canonización. No he señalado el motivo exacto, pero creo que tiene que ver con DOMWriter insertar atributos extra xmlns en los elementos DOM. Puede ver el efecto si activa el registro para la API de firma digital XML (como se describe en el artículo al que se refiere), elcanonicalizado del elemento SignedInfo> carece de declaración de espacio de nombres en el documento DOM producido por DOM4J.

Sin embargo, en lugar de utilizar DOMWriter, puede producir un documento DOM por transformación, utilizando un DOMSJ DocumentSource y un DOMResult.

/** 
* Create a DOM document from a DOM4J document 
*/ 
static Document copy(org.dom4j.Document orig) { 
    try { 
     TransformerFactory tf = TransformerFactory.newInstance(); 
     Transformer t = tf.newTransformer(); 
     DOMResult result = new DOMResult(); 
     t.transform(new DocumentSource(orig), result); 
     return (Document) result.getNode(); 
    } catch (Exception e) { 
     throw new RuntimeException(e); 
    } 
} 

Utilizando el documento DOM resultante, la validación funciona.

+0

¡Funciona, gracias! Tomo una (pequeña) mirada a la fuente dom4j (última versión estable, 1.6.1) pero no puedo entender lo que está sucediendo. –

+0

+1 buena investigación y respuesta – andyb

+0

No funciona para mí. Solo puedo confiar en el analizador W3C, pero estoy buscando una solución. – kbec

Cuestiones relacionadas