7

我有一些类已经使用DOM4J来读取 XML 文件并为数据提供 getter 方法。现在,我需要添加检查 XML 数字签名的可能性。

使用 org.w3c.dom 并遵循http://java.sun.com/developer/technicalArticles/xml/dig_signature_api/ 一切正常。

所以,我尝试使用 DOMWriter 将 org.dom4j.Document 转换为 org.w3c.dom.Document,但在此之后签名验证不起作用。我认为这是因为 DOMWiter 正在更改 XML 树(如 doc4.asXML() 似乎显示的那样)。

我试图找到一些东西来设置以保持文档的完整性,但是 DOMWriter 没有这样的方法。

下面是演示非对称转换的代码。

用于测试的文件是http://www.robertodiasduarte.com.br/files/nfe/131090007910044_v1.10-procNFe.xml

有人知道原因/解决方法吗?

谢谢(对不起我糟糕的英语)。

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;
        }
    }
}

例如,如果原始 XML 以:

<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() 返回:

<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">
...
4

1 回答 1

1

I had a closer look at this, and it turns out that DOM4J DOMWriter is doing something odd w.r.t. namespaces that obviously confuses the canonicalization process. I haven't pin pointed the exact reason, but I think it has to do with DOMWriter inserting extra xmlns attributes in the DOM elements. You can see the effect if you turn on logging for the XML digital signature API (as described in the article you refer to), the canonicalized <SignedInfo> element lacks namespace declaration in the DOM document produced by DOM4J.

However, instead of using DOMWriter, you can produce a DOM document by transformation, using a DOM4J DocumentSource and a 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);
    }
}

Using the resulting DOM document, the validation works.

于 2011-08-03T10:35:54.003 回答