0

我需要用 java 1.6 签署一个特定的 XML 标签。我的 XML 是这样的:

<RecepcionarLoteRps>
<EnviarLoteRpsEnvio xmlns="http://isscuritiba.curitiba.pr.gov.br/iss/nfse.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://isscuritiba.curitiba.pr.gov.br/iss/nfse.xsd">
    <LoteRps>
        <ListaRps>
            <Rps>           
                <InfRps id="1">
                    . . .
                </InfRps>                       
            </Rps>                  
        </ListaRps>         
</EnviarLoteRpsEnvio>

我对其进行签名的 java 代码(不包括密钥库部分)是:

    try {
    String ArqAssinar = "file.xml";
    String Charset = "UTF-8";

    /* URI and ID */
    String idRef = "1";
    String uriRef = "#" + idRef;

    XMLSignatureFactory XmlSignFac = XMLSignatureFactory.getInstance("DOM");

    DigestMethod DigMet = XmlSignFac.newDigestMethod(DigestMethod.SHA1, null);

    Transform TransfRef1 = XmlSignFac.newTransform(CanonicalizationMethod.INCLUSIVE, (TransformParameterSpec) null);
    Transform TransfRef2 = XmlSignFac.newTransform(SignatureMethod.RSA_SHA1, (C14NMethodParameterSpec) null);
    List<Transform> Transfs = new ArrayList<Transform>();
    Transfs.add(TransfRef1);
    Transfs.add(TransfRef2);

    /* Reference - where I use URI and ID */
    Reference Ref = XmlSignFac.newReference(uriRef, DigMet, Transfs, null, idRef);

    CanonicalizationMethod CannMet = XmlSignFac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null);
    SignatureMethod SignMet = XmlSignFac.newSignatureMethod(SignatureMethod.RSA_SHA1, null);

    /* SignedInfo - where I use Reference */
    SignedInfo SignInfo = XmlSignFac.newSignedInfo(CannMet, SignMet, Collections.singletonList(Ref));
    KeyInfoFactory keyInfFac = XmlSignFac.getKeyInfoFactory();

    List<X509Certificate> X509Content = new ArrayList<X509Certificate>();
    X509Content.add(Certif);

    X509Data X509dados = keyInfFac.newX509Data(X509Content);

    KeyInfo KeyInf = keyInfFac.newKeyInfo(Collections.singletonList(X509dados));

    /* Process file for input */
    DocumentBuilderFactory DocBuilderFactory = DocumentBuilderFactory.newInstance();
    DocBuilderFactory.setNamespaceAware(true);
    DocumentBuilder DocBuilder = DocBuilderFactory.newDocumentBuilder();
    InputStream Input = new FileInputStream(arq);
    Reader Leitor = new InputStreamReader(Input, Charset);
    InputSource Origem = new InputSource(Leitor);

    Document Doc = DocBuilder.parse(Origem);

    /* Search for tag in document using ID */
    XPathFactory factory = XPathFactory.newInstance();
    XPath xpathPesquisa = factory.newXPath();
    XPathExpression expr = xpathPesquisa.compile(String.format("//*[@id='%s']", idRef));
    NodeList nodes = (NodeList) expr.evaluate(docParaAssinar, XPathConstants.NODESET);
    Node Tag = null;
    DOMSignContext DocSignCont = null;
    XMLSignature Signature = null;

    if (nodes.getLength() != 0) {
        tagComId = nodes.item(0);
        Tag = tagComId.getParentNode();

        DocSignCont = new DOMSignContext(PrivPass, Tag);

        /* Do the signature */
        Signature = this.XmlSignFac.newXMLSignature(SignInfo, KeyInf);
        Signature.sign(DocSignCont);        

        /* Updates the file */      
        OutputStream Saida = new FileOutputStream(arqAtualizar);
        Writer Escritor = new OutputStreamWriter(Saida, Charset);
        StreamResult Resultado = new StreamResult(Escritor);
        TransformerFactory TransformFac = TransformerFactory.newInstance();
        Transformer Transf = TransformFac.newTransformer();
        Transf.transform(new DOMSource(docAssinado), Resultado);
    } else {
        . . .
    }
} catch (Exception E) {
    . . .
}

当我在 Eclipse 中运行此代码时,我在 DOMXMLSignature.sign() 方法中收到 StackOverflow 错误。有一个对 DOMXMLSignature.digestReference() 方法的调用,并且该函数无限期地调用自身。

如果 URI 为 "" 且 ID 为空,即当我需要对整个 XML 进行签名时,此代码有效。

为了签署特定的 XML 标签,我需要做些什么不同的事情?

我对这段代码有一些问题:

public final class DOMXMLSignature extends DOMStructure
implements XMLSignature {


@Override
public void sign(XMLSignContext signContext)
    throws MarshalException, XMLSignatureException
{
    if (signContext == null) {
        throw new NullPointerException("signContext cannot be null");
    }
    DOMSignContext context = (DOMSignContext)signContext;
    marshal(context.getParent(), context.getNextSibling(),
            DOMUtils.getSignaturePrefix(context), context);

    // generate references and signature value
    List<Reference> allReferences = new ArrayList<>();

    // traverse the Signature and register all objects with IDs that
    // may contain References
    signatureIdMap = new HashMap<>();
    signatureIdMap.put(id, this);
    signatureIdMap.put(si.getId(), si);
    @SuppressWarnings("unchecked")
    List<Reference> refs = si.getReferences();
    for (Reference ref : refs) {
        signatureIdMap.put(ref.getId(), ref);
    }
    for (XMLObject obj : objects) {
        signatureIdMap.put(obj.getId(), obj);
        @SuppressWarnings("unchecked")
        List<XMLStructure> content = obj.getContent();
        for (XMLStructure xs : content) {
            if (xs instanceof Manifest) {
                Manifest man = (Manifest)xs;
                signatureIdMap.put(man.getId(), man);
                @SuppressWarnings("unchecked")
                List<Reference> manRefs = man.getReferences();
                for (Reference ref : manRefs) {
                    allReferences.add(ref);
                    signatureIdMap.put(ref.getId(), ref);
                }
            }
        }
    }
    // always add SignedInfo references after Manifest references so
    // that Manifest reference are digested first
    allReferences.addAll(refs);

    // generate/digest each reference
    for (Reference ref : allReferences) {
        digestReference((DOMReference)ref, signContext);
    }

    // do final sweep to digest any references that were skipped or missed
    for (Reference ref : allReferences) {
        if (((DOMReference)ref).isDigested()) {
            continue;
        }
        ((DOMReference)ref).digest(signContext);
    }

    Key signingKey = null;
    try {
        KeySelectorResult keySelectorResult = signContext.getKeySelector().select(ki,
                                                  KeySelector.Purpose.SIGN,
                                                  si.getSignatureMethod(),
                                                  signContext);
        signingKey = keySelectorResult.getKey();
        if (signingKey == null) {
            throw new XMLSignatureException("the keySelector did not " +
                                            "find a signing key");
        }
        ksr = keySelectorResult;
    } catch (KeySelectorException kse) {
        throw new XMLSignatureException("cannot find signing key", kse);
    }

    // calculate signature value
    try {
        byte[] val = ((AbstractDOMSignatureMethod)
            si.getSignatureMethod()).sign(signingKey, si, signContext);
        ((DOMSignatureValue)sv).setValue(val);
    } catch (InvalidKeyException ike) {
        throw new XMLSignatureException(ike);
    }

    this.localSigElem = sigElem;
}

private void digestReference(DOMReference ref, XMLSignContext signContext)
    throws XMLSignatureException
{
    if (ref.isDigested()) {
        return;
    }
    // check dependencies
    String uri = ref.getURI();
    if (Utils.sameDocumentURI(uri)) {
        String parsedId = Utils.parseIdFromSameDocumentURI(uri);
        if (parsedId != null && signatureIdMap.containsKey(parsedId)) {
            XMLStructure xs = signatureIdMap.get(parsedId);
            if (xs instanceof DOMReference) {
                digestReference((DOMReference)xs, signContext);
            } else if (xs instanceof Manifest) {
                Manifest man = (Manifest)xs;
                List<Reference> manRefs = DOMManifest.getManifestReferences(man);
                for (int i = 0, size = manRefs.size(); i < size; i++) {
                    digestReference((DOMReference)manRefs.get(i),
                                    signContext);
                }
            }
        }
        // if uri="" and there are XPath Transforms, there may be
        // reference dependencies in the XPath Transform - so be on
        // the safe side, and skip and do at end in the final sweep
        if (uri.length() == 0) {
            List<Transform> transforms = ref.getTransforms();
            for (Transform transform : transforms) {
                String transformAlg = transform.getAlgorithm();
                if (transformAlg.equals(Transform.XPATH) ||
                    transformAlg.equals(Transform.XPATH2)) {
                    return;
                }
            }
        }
    }
    ref.digest(signContext);
}

}

public final class Utils {

/**
 * Returns the ID from a same-document URI (ex: "#id")
 */
public static String parseIdFromSameDocumentURI(String uri) {
    if (uri.length() == 0) {
        return null;
    }
    String id = uri.substring(1);
    if (id != null && id.startsWith("xpointer(id(")) {
        int i1 = id.indexOf('\'');
        int i2 = id.indexOf('\'', i1+1);
        id = id.substring(i1+1, i2);
    }
    return id;
}

/**
 * Returns true if uri is a same-document URI, false otherwise.
 */
public static boolean sameDocumentURI(String uri) {
    return uri != null && (uri.length() == 0 || uri.charAt(0) == '#');
}

}

这只是 DOMXMLSignature 类和 Util 类的一部分,只是与案例有关的方法。

我上面的代码类 DOMXMLSignature.sign() 方法。这个方法做marshall,获取引用,获取引用id,调用DOMXMLSignature.digestReference()方法。

DOMXMLSignature.digestReference() 方法验证引用是否已经被消化 - 它不是。所以他得到了 URI,验证这是否是同文档 URI - 它是 - ,确认文档中的 id 是 URI 中的 id。然后,问题是:通过 signatureIdMap.get(parsedId) 获得的实例总是 DOMReference 类型,因此该方法无限期地调用自己。

这发生在 Java 1.6 中,在本机类中没有任何变化。如何解决此问题并签署特定的 XML 标记?在调用 DOMXMLSignature.sign() 方法之前是否必须计算摘要值?

4

1 回答 1

0

哇。

首先,您的代码不是可重现的示例:

  • tagComId docParaAssinar docAssinado未声明,后两者从未设置。我为第一个添加了类型Node,并将后两个替换为Doc. 仅供参考:使用首字母大写作为本地人(也包括字段或方法)的名称是违反传统风格的,因此感觉很奇怪,但确实有效,所以我没有费心去改变这些。

  • SignatureMethod.RSA_SHA1不是有效的Transform,并且尝试将其用作引发异常的人。在引用的转换列表中也没有意义,因为签名不是单独应用于一个引用,而是应用于包含可能多个引用和其他数据的 SignedInfo。我删除了它。

  • 此外,为了更方便的测试,我用固定输入和控制台输出替换了文件 I/O(看起来不错)。

通过这些更改,我确实重现了 stackoverflow。这是因为您将 #1 指定为 URI 并将 1 指定为引用的 id,因此要解析引用,它必须转到被引用的元素并解析它,并且被引用的元素本身就是(相同的)引用,所以它必须去标识的引用元素并解析它,所以它必须等等。你不能使引用的 id 与你试图引用的元素相同,不应该使它与任何其他元素相同,你不需要引用 Reference 本身,因此最好不要为其设置任何id。我将id更改newReference为null。

通过这些更改,我可以签署“”(整个文档)而不是“#1”。我发现是因为#x 不引用(一个元素)名为“id”的属性,它引用(一个)在模式或 DTD中定义为 id 的属性 - 而你<InfRps id="1">的没有被定义为ID。请参阅
在 java Java XML DOM 中解析 XML 时没有工作 ID 属性
:id 属性有什么特别之处?
XML id 属性与 Java 的 getElementById 一起使用?

您引用的架构仅将此属性定义为字符串而不是 id,并且无论如何都不会使用,因为默认情况下 DOM 解析器不使用架构。我没有摆弄编辑使用模式,而是编写了一个(非常!)最小的 DTD 来使这个属性成为一个 id,下面的代码可以让我签署所需的元素(使用我创建的虚拟密钥和证书):

    static void SO69652045XMLSign (String[] args) throws Exception {
        //++ debug data
        String input = "<!DOCTYPE RecepcionarLoteRps [ <!ATTLIST InfRps id ID #REQUIRED> ] >"+ 
                "<RecepcionarLoteRps><EnviarLoteRpsEnvio xmlns=\"http://isscuritiba.curitiba.pr.gov.br/iss/nfse.xsd\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://isscuritiba.curitiba.pr.gov.br/iss/nfse.xsd\">"+
                "<LoteRps><ListaRps><Rps><InfRps id=\"1\">xxx</InfRps></Rps></ListaRps> </LoteRps> </EnviarLoteRpsEnvio></RecepcionarLoteRps>";
        KeyStore keystore = KeyStore.getInstance("PKCS12");
        try(InputStream is = new FileInputStream("69652045.p12")){ keystore.load(is,"sekrit".toCharArray()); }
        PrivateKey PrivPass = (PrivateKey) keystore.getKey("mykey","sekrit".toCharArray());
        X509Certificate Certif = (X509Certificate) keystore.getCertificateChain("mykey")[0];
        //--String ArqAssinar = "file.xml";
        //--String Charset = "UTF-8";

        /* URI and ID */
        String idRef = "1";
        String uriRef = "#" + idRef;

        XMLSignatureFactory XmlSignFac = XMLSignatureFactory.getInstance("DOM");

        DigestMethod DigMet = XmlSignFac.newDigestMethod(DigestMethod.SHA1, null);

        Transform TransfRef1 = XmlSignFac.newTransform(CanonicalizationMethod.INCLUSIVE, (TransformParameterSpec) null);
        //--Transform TransfRef2 = XmlSignFac.newTransform(SignatureMethod.RSA_SHA1, (C14NMethodParameterSpec) null);
        List<Transform> Transfs = new ArrayList<Transform>();
        Transfs.add(TransfRef1);
        //--Transfs.add(TransfRef2);

        /* Reference - where I use URI and ID */
        Reference Ref = XmlSignFac.newReference(uriRef, DigMet, Transfs, null, /*idRef*/null);

        CanonicalizationMethod CannMet = XmlSignFac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null);
        SignatureMethod SignMet = XmlSignFac.newSignatureMethod(SignatureMethod.RSA_SHA1, null);

        /* SignedInfo - where I use Reference */
        SignedInfo SignInfo = XmlSignFac.newSignedInfo(CannMet, SignMet, Collections.singletonList(Ref));
        KeyInfoFactory keyInfFac = XmlSignFac.getKeyInfoFactory();

        List<X509Certificate> X509Content = new ArrayList<X509Certificate>();
        X509Content.add(Certif);

        X509Data X509dados = keyInfFac.newX509Data(X509Content);

        KeyInfo KeyInf = keyInfFac.newKeyInfo(Collections.singletonList(X509dados));

        /* Process file for input */
        DocumentBuilderFactory DocBuilderFactory = DocumentBuilderFactory.newInstance();
        DocBuilderFactory.setNamespaceAware(true);
        DocumentBuilder DocBuilder = DocBuilderFactory.newDocumentBuilder();
        //--InputStream Input = new FileInputStream(arq);
        //--Reader Leitor = new InputStreamReader(Input, Charset);
        //--InputSource Origem = new InputSource(Leitor);
        InputSource Origem = new InputSource (new StringReader (input)); //++
        Document Doc = DocBuilder.parse(Origem);

        /* Search for tag in document using ID */
        XPathFactory factory = XPathFactory.newInstance();
        XPath xpathPesquisa = factory.newXPath();
        XPathExpression expr = xpathPesquisa.compile(String.format("//*[@id='%s']", idRef));
        NodeList nodes = (NodeList) expr.evaluate(/*docParaAssinar*/Doc, XPathConstants.NODESET);
        Node Tag = null;
        DOMSignContext DocSignCont = null;
        XMLSignature Signature = null;

        if (nodes.getLength() != 0) {
            Node tagComId = nodes.item(0); //**
            Tag = tagComId.getParentNode();

            DocSignCont = new DOMSignContext(PrivPass, Tag);

            /* Do the signature */
            Signature = /*this.*/XmlSignFac.newXMLSignature(SignInfo, KeyInf);
            Signature.sign(DocSignCont);        

            /* Updates the file */      
            //--OutputStream Saida = new FileOutputStream(arqAtualizar);
            //--Writer Escritor = new OutputStreamWriter(Saida, Charset);
            Writer Escritor = new OutputStreamWriter(System.out,StandardCharsets.UTF_8); //++
            StreamResult Resultado = new StreamResult(Escritor);
            TransformerFactory TransformFac = TransformerFactory.newInstance();
            Transformer Transf = TransformFac.newTransformer();
            Transf.transform(new DOMSource(/*docAssinado*/Doc), Resultado);
        } else {
            throw new Exception ("id not found");
        }
    }

为清晰起见,对其进行了轻微编辑:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<RecepcionarLoteRps><EnviarLoteRpsEnvio xmlns="http://isscuritiba.curitiba.pr.gov.br/iss/nfse.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://isscuritiba.curitiba.pr.gov.br/iss/nfse.xsd">
 <LoteRps><ListaRps><Rps>
  <InfRps id="1">xxx</InfRps>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
   <SignedInfo>
    <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
    <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
    <Reference URI="#1">
     <Transforms><Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/></Transforms>
     <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
     <DigestValue>fRXn427d6rObJ0udybG5aY5E6n4=</DigestValue>
     </Reference></SignedInfo>
   <SignatureValue>...</SignatureValue>
   <KeyInfo><X509Data><X509Certificate>...</X509Certificate></X509Data></KeyInfo>
  </Signature>
 </Rps></ListaRps> </LoteRps> </EnviarLoteRpsEnvio></RecepcionarLoteRps>

我不能说这种定义 XML 的形式或其他形式在您的环境或应用程序中是否可接受。

最后,如果您不知道,SHA1 在 2017 年因碰撞而被破坏,大多数主管安全机构不再允许签名使用它(直接或间接)。(对于SSL/TLS 证书,包括此类网站,自 2014-2015 年以来,SHA1被禁止。)但这对 SO 来说是题外话。

于 2021-10-27T11:45:20.633 回答