首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >当向文档(JAVA)显式添加元素时,XML签名不验证?

当向文档(JAVA)显式添加元素时,XML签名不验证?
EN

Stack Overflow用户
提问于 2017-05-05 13:42:26
回答 1查看 2.2K关注 0票数 14

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

下面是编组的代码示例:

代码语言:javascript
复制
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();

然后,我创建元素(刘)如下所示,并使用HMAC-SHA256算法和XML对文档进行签名(我不打算包含整个签名代码以减少详细,我使用的是XMLSignature类的标准行为,然后使用JSR105转换器将文档转换为来自DOMSource的文件输出流):

代码语言:javascript
复制
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正在正确地签名,但在验证时,计算的摘要值与摘要值不同。

我注意到,在创建刘元素时更改名称空间值时,摘要永远不会更改,就好像文档正在签名而忽略了刘元素的命名空间一样,我猜这就是它失败的原因。整个文档中的任何其他更改,或刘元素前缀的更改,都会直接影响有效载荷的计算摘要。

如果我直接将签名附加到根元素,而不是创建刘元素,验证就会正常工作。

刘元素存在于XSD中,可以使用JAXB创建,但问题是,我无法找到与根元素相同的名称空间的前缀(仅在文档中)。

问题:

在使用createElementNSappendChild将元素添加到文档中时,有效负载摘要计算中实际上省略了命名空间吗? 是否有一种方法可以通过JAXB为同一个根名称空间提供一个前缀,仅用于单个元素? 如何找到由API签名的实际XML字符串,我在启用javax.xml.crypto.dsig.cacheReference之后尝试读取引用输入流,但这在签名时无效,只有在验证时才有效。

下面是XML的示例:

代码语言:javascript
复制
<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签名过程

代码语言:javascript
复制
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);

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-05-09 11:01:01

如何找到由API签名的实际XML字符串,我在启用javax.xml.crypto.dsig.cacheReference之后尝试读取引用输入流,但这在签名时无效,只有在验证时才有效。

幸运的是,默认实现(假设正在使用)提供了一些调试功能。下面是一个使用FINEST粒度登录到控制台的设置示例,尽管FINE似乎已经足够了。

代码语言:javascript
复制
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。

代码语言:javascript
复制
<DataPDU xmlns="urn:swift:saa:xsd:saa.2.0"><Revision>2.0.6</Revision><Saa:LAU></Saa:LAU></DataPDU>

注意Saa前缀是如何存在的,但是名称空间声明不是。但是,信息在那里,因为在转换后,带有前缀的声明将显示在结果中。这解释了为什么对名称空间URI本身的更改不会导致不同的摘要,但更改前缀会导致摘要的不同。不知道为什么会这样。这似乎是不应该的,但是规范版本中包含的规则在规范中是如此的令人困惑,以至于我遗漏了什么,或者是实现中的一个bug。解决这个问题的方法是在创建LAUElement之后添加这一行。

代码语言:javascript
复制
 LAUElement.setAttribute("xmlns:Saa", "urn:swift:saa:xsd:saa.2.0");

我想,也许DOMResult使用了一个没有设置为名称空间感知的文档生成器,但我尝试了这一点,但没有区别:

代码语言:javascript
复制
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解析找到根元素及其下面的所有祖先。这让我有点吃惊。

通过上述操作,我成功地使用以下代码正确地验证了签名:

代码语言:javascript
复制
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));

实际上,问题似乎是,当名称空间声明on被删除时,生成的摘要与在验证时生成的摘要有所不同。

是否有一种方法可以通过JAXB为同一个根名称空间提供一个前缀,仅用于单个元素?

简单的回答是,没有什么琐碎的方法可以做到这一点。很长的答案可以在这里找到:https://stackoverflow.com/a/42301479/630136

编辑:

另一张便条。在将签名的文档输出到文件时,要小心转换器。如果将其设置为缩进输出,则生成的文件实际上将有一个不同的摘要。有一些空格可以忽略(比如在<empty /><empty/>中),但是在元素中,它通常被视为文本节点和计算摘要的规范化版本的一部分。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/43806589

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档