XML签名在向文档(Java)明确添加元素时未验证



我使用第三方提供的给定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创建,但问题是我找不到与根元素相同命名空间分配前缀(仅在文档中仅用于其(的方法。

问题:

实际上是从有效载荷摘要中省略的名称空间 使用createElementNSappendChild添加元素时计算元素?

是否可以通过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>

更新 - 完整的XML签名过程

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

我如何找到API签名的实际XML字符串,我尝试在启用Javax.xml.crypto.dsig.cachereference后尝试阅读参考输入流,但在签名时这不起作用,仅在验证时起作用?<<<<<</p>

幸运的是,默认实现(假设正在使用(提供了一些调试功能。这是使用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本身的错误。解决方法有些混乱,但可以完成工作。实际上,这确实改变了摘要。我尝试了通过提供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 A仅针对单个元素的同一根名称空间提供前缀?

简短的答案是,没有一个微不足道的方法可以做到这一点。可以在此处找到长答案:https://stackoverflow.com/a/42301479/630136

编辑:

附加说明。请注意将签名文档输出到文件的变压器。如果设置为缩进输出,则结果文件实际上将具有不同的消化。有空格可以忽略(例如在<empty /><empty/>中(,但是在元素中,它通常被视为文本节点和一部分规范化版本,该版本是在其上计算出的摘要。

最新更新