当在文档中显式添加一个元素时,XML签名无法验证(JAVA)?

14

我正在使用由第三方提供的给定XSD的JAXB创建以下XML文档。第三方要求对文档进行签名,并添加一个额外的元素来保存签名。使用JDK 1.7。

以下是编组的代码示例:

JAXBContext jaxbContext = JAXBContext.newInstance(DataPDU.class);
DataPDU myDataPDU = new DataPDU();
myDataPDU.setRevision("2.0.6");

// marshall the file
Marshaller marshaller = jaxbContext.createMarshaller();

DOMResult domResult = new DOMResult();
marshaller.marshal(myDataPDU, domResult);

// get the document list
Document document = (Document) domResult.getNode();

然后我创建了以下元素(LAU),并使用HMAC-SHA256算法和JSR105 JAVA API对文档进行签名(我不会包含整个签名代码以减少冗长,我使用XMLSignature类的标准行为,然后使用XML转换器将文档从DOMSource转换为文件输出流):

Element LAUElement = document.createElementNS("urn:swift:saa:xsd:saa.2.0", "Saa:LAU");
Element rootElement = document.getDocumentElement();
rootElement.appendChild(LAUElement);

// sign the document
XMLSignatureUtil.sign(document, secret, LAUElement, "ds");
// create the output file
TransformerUtil.transformDocumentToFile(document, "resultingFile.xml");

XML签名本身没有问题,但在验证时,计算得到的摘要值与实际摘要值不同。
我发现,在创建LAU元素时更改命名空间值时,摘要值从未更改,好像文档正在被签名并忽略LAU元素的命名空间。这可能是失败的原因。整个文档中的任何其他更改或直接更改LAU元素的前缀都会影响有效载荷的计算摘要。
如果我直接将签名附加到根元素而不是创建LAU元素,则验证可以正常工作。
LAU元素存在于XSD中,并且可以使用JAXB创建,但问题是我无法找到一种方法来为与根元素相同命名空间的单个元素分配一个前缀(仅用于文档)。
问题:
命名空间是否实际上在使用createElementNS和appendChild向文档添加元素时被省略在了有效载荷摘要计算中?
是否有一种通过JAXB为单个元素提供与根命名空间相同前缀的方法?
如何找到API签名的实际XML字符串?我尝试启用javax.xml.crypto.dsig.cacheReference后读取参考输入流,但这在签名时不起作用,只有在验证时才起作用?
以下是XML样例:
<DataPDU xmlns="urn:swift:saa:xsd:saa.2.0">
  <Revision>2.0.6</Revision>
  <Saa:LAU xmlns:Saa="urn:swift:saa:xsd:saa.2.0"> Signature lies here </Saa:LAU>
</DataPDU>

Update - Full XML signature process

JAXBContext jaxbContext = JAXBContext.newInstance(DataPDU.class);
DataPDU myDataPDU = new DataPDU();
myDataPDU.setRevision("2.0.6");

// marshall the file
Marshaller marshaller = jaxbContext.createMarshaller();

DOMResult domResult = new DOMResult();
marshaller.marshal(myDataPDU, domResult);

// get the document list
Document document = (Document) domResult.getNode();

// signing process
XMLSignatureFactory factory = XMLSignatureFactory.getInstance("DOM");

SignatureMethod signatureMethod =
    factory.newSignatureMethod("http://www.w3.org/2001/04/xmldsig-more#hmac-sha256", null);

CanonicalizationMethod canonicalizationMethod =
    factory.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE, (XMLStructure) null);

List<Transform> transforms = new ArrayList<Transform>();
transforms.add(factory.newTransform(Transform.ENVELOPED, (XMLStructure) null));
transforms.add(factory.newTransform("http://www.w3.org/2001/10/xml-exc-c14n#", (XMLStructure) null));

DigestMethod digestMethod = factory.newDigestMethod("http://www.w3.org/2001/04/xmlenc#sha256", null);

Reference reference = factory.newReference("", digestMethod, transforms, null, null);

SignedInfo signedInfo =
    factory.newSignedInfo(canonicalizationMethod, signatureMethod, Collections.singletonList(reference));

String secretKey = "Abcd1234abcd1234Abcd1234abcd1234";
SecretKeySpec secret_key = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");

Element LAUElement = document.createElementNS("urn:swift:saa:xsd:saa.2.0", "Saa:LAU");
Element rootElement = document.getDocumentElement();
rootElement.appendChild(LAUElement);

DOMSignContext domSignContext = new DOMSignContext(secret_key, LAUElement);
domSignContext.setDefaultNamespacePrefix("ds");

XMLSignature signature = factory.newXMLSignature(signedInfo, null);
signature.sign(domSignContext);


我认为我们真的需要看到签名代码(甚至可能是验证代码),因为在研究XML-Dsig API时,我不清楚您如何计算整个DOM上的签名,然后将其附加到“LAU”元素中。我相信我可以回答您的问题,但首先需要验证您如何建立签名,以了解为什么它一开始就无法工作。 - G_H
@G_H,我已经添加了完整的XML签名片段,这里明确添加了LAU标记并为其分配了命名空间。由于LAU是在计算签名之前文档中的元素,因此它在计算签名时被包含在内。然后,在DOMSignContext中,我将LAU标记本身分配为父节点,因此在计算后,签名将作为子元素添加到其中。 - KAD
这段代码在摘要值中也包含了LAU标签。我该如何计算不包括LAU标签的摘要值,然后再添加LAU标签呢? - Uğur Yeşilyurt
1个回答

2
我该如何找到API签署的实际XML字符串?我尝试启用javax.xml.crypto.dsig.cacheReference并读取参考输入流,但在签署时它没有效果,仅在验证时有效。幸运的是,默认实现(假设正在使用)提供了一些调试功能。这里有一个示例设置,可以使用FINEST粒度记录到控制台,尽管FINE似乎已经足够了。
handlers= java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level = FINER
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
org.jcp.xml.dsig.internal.level = FINER
com.sun.org.apache.xml.internal.security.level = FINER

您可以将此保存为logging.properties文件,并通过命令行选项-Djava.util.logging.config.file提供其路径,尽管这也可能是以编程方式完成的。
这样做实际上会显示规范化的XML被签名,这回答了以下问题:
“在使用createElementNS和appendChild将元素添加到文档时,负载摘要计算是否实际上省略了命名空间?”
确实如此。运行代码后,我在日志中看到了签名的规范化XML。
<DataPDU xmlns="urn:swift:saa:xsd:saa.2.0"><Revision>2.0.6</Revision><Saa:LAU></Saa:LAU></DataPDU>

请注意,Saa前缀存在,但命名空间声明不存在。然而,在转换后,带有前缀的声明会出现在结果中,因此信息仍然存在。这就解释了为什么更改命名空间URI本身不会导致不同的摘要,但更改前缀会导致不同的摘要。不知道为什么会发生这种情况。感觉不应该这样,但规范中关于规范化版本包含什么的规则非常令人困惑,要么是我漏掉了什么,要么是实现中的一个错误。解决此问题的方法是在创建LAUElement之后添加以下行:
 LAUElement.setAttribute("xmlns:Saa", "urn:swift:saa:xsd:saa.2.0");

我认为可能是DOMResult使用的文档构建器没有设置为命名空间感知,但我尝试过了,没有任何不同:

DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
domFactory.setNamespaceAware(true);
DocumentBuilder docBuilder = domFactory.newDocumentBuilder();
Document doc = docBuilder.newDocument();
DOMResult domResult = new DOMResult(doc);
marshaller.marshal(myDataPDU, domResult);

可能是DOMResult本身的一个bug。解决方法有些混乱,但它确实起到了作用。这确实改变了摘要。我尝试过提供LAUElement和文档根作为DOMSignContext的输入,结果在两种情况下都得到了相同的签名(摘要和签名值),尽管在后一种情况下,签名是添加到根而不是元素中。
这也告诉我们,即使您提供了元素并使用了排除规范化方法,整个文档也会用作签名的输入。它只改变了签名的位置。URI解析会找到祖先元素直到根元素和其下面的所有内容。这让我有点惊讶。
通过上述操作,我成功地使用以下代码验证了签名:
XMLSignatureFactory factory = XMLSignatureFactory.getInstance("DOM");

NodeList sigList = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
if (sigList.getLength() == 0) {
    throw new Exception("Cannot find Signature element");
}
String secretKey = "Abcd1234abcd1234Abcd1234abcd1234";
SecretKeySpec secret_key = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
DOMValidateContext valContext = new DOMValidateContext(secret_key, sigList.item(0));
XMLSignature signature = factory.unmarshalXMLSignature(valContext);

System.out.println("Core validity: " + signature.validate(valContext));

确实,问题出在当 上的命名空间声明被删除后,生成的摘要与验证时生成的摘要不同。

是否有一种方法可以通过JAXB为相同根命名空间提供单个元素的前缀?

简而言之,没有简单的方法来做到这一点。长的答案可以在这里找到:https://stackoverflow.com/a/42301479/630136 编辑:
另外注意,输出签名文档到文件的转换器要小心。如果设置缩进输出,结果文件实际上将拥有不同的摘要。有些空格可能会被忽略(例如<empty /><empty/>),但是在元素内部中,它通常被视为文本节点并包含在规范化版本中,从而计算摘要。

好的,感谢您的答复。日志记录部分帮助我跟踪了问题,就像您所做的那样。与您一样,我也在想,为什么它会从有效载荷中删除。我不认为问题出在DOMResult上,因为标签是在编组之后添加的,也是在从DOMResult获取文档节点之后。关于DOMSignContext的另一件事,第二个构造函数参数指定在签名后将保存签名的节点,而不是有效载荷内的内容。实际上,Reference指定了这个部分,并将空字符串传递给URI... - KAD
继续。将空字符串传递给URI属性将告诉签名API对整个文档进行签名,您可以指定特定节点、外部URL或使用Xpointer来签署有效负载的特定元素。感谢这个解决方法,我会在此期间使用它,希望有人能提供详细的解释为什么会发生这种情况。+1 - KAD

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接