来自 apache santuario xmlsignature 测试, 我做了一个测试:向 XML 文件添加签名,然后将其保存为文件,然后从该文件中读取它,并验证签名。 请参考代码中的注释。
纯 xml 文件是:
<?xml version="1.0" encoding="UTF-8"?>
<PurchaseOrder xmlns="urn:example:po">
<Items>
<Item Code="001-001-001" Quantity="1">
spade
</Item>
<Item Code="001-001-002" Quantity="1">
shovel
</Item>
</Items>
<ShippingAddress>
Dig PLC, 1 First Ave, Dublin 1, Ireland
</ShippingAddress>
<PaymentInfo>
<BillingAddress>
Dig PLC, 1 First Ave, Dublin 1, Ireland
</BillingAddress>
<CreditCard Type="Amex">
<Name>Foo B Baz</Name>
<Number>1234 567890 12345</Number>
<Expires Month="1" Year="2005" />
</CreditCard>
</PaymentInfo>
</PurchaseOrder>
我有一些问题:
<ds:SignedInfo><!--If I add a lot of blank spaces at the start of this line, the verifying is ok-->
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod><!--If I add a blank space at the start of this line, the verifying is not working-->
主要Java代码:
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.Key;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import javax.xml.namespace.QName;
import org.apache.xml.security.signature.XMLSignature;
import org.junit.Assert;
import org.w3c.dom.Document;
import org.apache.xml.security.utils.XMLUtils;
import org.w3c.dom.Element;
/**
* This tests using the DOM API of Apache Santuario - XML Security for Java for XML Signature.
*/
public class SignatureDOMTest extends org.junit.Assert {
// Sign + Verify an XML Document using the DOM API
@org.junit.Test
public void testSignatureUsingDOMAPI() throws Exception {
// Read in plaintext document
InputStream sourceDocument =
new FileInputStream(new File("plaintext.xml"));
Document document = XMLUtils.read(sourceDocument, true);
// Set up the Key
KeyStore keyStore = KeyStore.getInstance("jks");
keyStore.load(
this.getClass().getClassLoader().getResource("clientstore.jks").openStream(),
"cspass".toCharArray()
);
Key key = keyStore.getKey("myclientkey", "ckpass".toCharArray());
X509Certificate cert = (X509Certificate)keyStore.getCertificate("myclientkey");
// Sign using DOM
List<QName> namesToSign = new ArrayList<QName>();
namesToSign.add(new QName("urn:example:po", "PaymentInfo"));
SignatureUtils.signUsingDOM(
document, namesToSign, "http://www.w3.org/2000/09/xmldsig#rsa-sha1", key, cert
);
// Verify using DOM, here it use the cert load from Keystore.
SignatureUtils.verifyUsingDOM(document, namesToSign, cert);
// Those code are my test:
// I write the signed xml to a file, then read from the file.
XMLUtils.outputDOM(document, new FileOutputStream(new File("plaintext_signed.xml")));
// read from the signed file.
Document signedDocument = XMLUtils.read(new FileInputStream(new File("plaintext_signed.xml")), true);
// Verify using DOM
List<QName> namesToSign1 = new ArrayList<QName>();
namesToSign1.add(new QName("urn:example:po", "PaymentInfo"));
Element sigElement = SignatureUtils.getSignatureElement(signedDocument);
Assert.assertNotNull(sigElement);
SignatureUtils.findElementsToVerify(signedDocument, namesToSign1);
// try to get cert from the node in the xml.
XMLSignature signature = new XMLSignature(sigElement, "");
X509Certificate cert_in_xml = signature.getKeyInfo().getX509Certificate();
// verify the signature by using the cert in the xml.
// If it works, why do we need a cert loaded from the keystore?
SignatureUtils.verifyUsingDOM(signedDocument, namesToSign1, cert_in_xml);
}
}
为了完整性,将函数包含在
SignatureUtils.java
中:
import org.apache.xml.security.Init;
import org.apache.xml.security.keys.KeyInfo;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.transforms.Transforms;
import org.junit.Assert;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import javax.xml.namespace.QName;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.security.Key;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.UUID;
/**
* Some utility methods for signing/verifying documents
*/
public final class SignatureUtils {
static {
Init.init();
}
private SignatureUtils() {
// complete
}
/**
* Sign the document using the DOM API of Apache Santuario - XML Security for Java.
* It signs a list of QNames that it finds in the Document via XPath.
*/
public static void signUsingDOM(
Document document,
List<QName> namesToSign,
String algorithm,
Key signingKey,
X509Certificate signingCert
) throws Exception {
XMLSignature sig =
new XMLSignature(document, "", algorithm, "http://www.w3.org/2001/10/xml-exc-c14n#");
Element root = document.getDocumentElement();
root.appendChild(sig.getElement());
XPathFactory xpf = XPathFactory.newInstance();
XPath xpath = xpf.newXPath();
xpath.setNamespaceContext(new DSNamespaceContext());
for (QName nameToSign : namesToSign) {
String expression = "//*[local-name()='" + nameToSign.getLocalPart() + "']";
NodeList elementsToSign =
(NodeList) xpath.evaluate(expression, document, XPathConstants.NODESET);
for (int i = 0; i < elementsToSign.getLength(); i++) {
Element elementToSign = (Element)elementsToSign.item(i);
Assert.assertNotNull(elementToSign);
String id = UUID.randomUUID().toString();
elementToSign.setAttributeNS(null, "Id", id);
elementToSign.setIdAttributeNS(null, "Id", true);
Transforms transforms = new Transforms(document);
transforms.addTransform("http://www.w3.org/2001/10/xml-exc-c14n#");
sig.addDocument("#" + id, transforms, "http://www.w3.org/2000/09/xmldsig#sha1");
}
}
sig.sign(signingKey);
// Find the Signature Element
Element sigElement = getSignatureElement(document);
Assert.assertNotNull(sigElement);
if (signingCert != null) {
sig.addKeyInfo(signingCert);
}
}
/**
* Verify the document using the DOM API of Apache Santuario - XML Security for Java.
* It finds a list of QNames via XPath and uses the DOM API to mark them as having an
* "Id".
*/
public static void verifyUsingDOM(
Document document,
List<QName> namesToSign,
X509Certificate cert
) throws Exception {
// Find the Signature Element
Element sigElement = getSignatureElement(document);
Assert.assertNotNull(sigElement);
findElementsToVerify(document, namesToSign);
XMLSignature signature = new XMLSignature(sigElement, "");
// Check we have a KeyInfo
KeyInfo ki = signature.getKeyInfo();
Assert.assertNotNull(ki);
// Check the Signature value
Assert.assertTrue(signature.checkSignatureValue(cert));
}
public static void findElementsToVerify(Document document, List<QName> namesToSign) throws XPathExpressionException {
XPathFactory xpf = XPathFactory.newInstance();
XPath xpath = xpf.newXPath();
xpath.setNamespaceContext(new DSNamespaceContext());
for (QName nameToSign : namesToSign) {
String expression = "//*[local-name()='" + nameToSign.getLocalPart() + "']";
Element signedElement =
(Element) xpath.evaluate(expression, document, XPathConstants.NODE);
Assert.assertNotNull(signedElement);
if (signedElement.hasAttributeNS(null, "Id")) {
signedElement.setIdAttributeNS(null, "Id", true);
}
}
}
public static Element getSignatureElement(Document document) throws XPathExpressionException {
XPathFactory xpf = XPathFactory.newInstance();
XPath xpath = xpf.newXPath();
xpath.setNamespaceContext(new DSNamespaceContext());
// Find the Signature Element
String expression = "//dsig:Signature[1]";
Element sigElement =
(Element) xpath.evaluate(expression, document, XPathConstants.NODE);
return sigElement;
}
}
生成的带有签名的xml文件,格式不好,我想格式化它,但结果导致验证失败。
<PurchaseOrder xmlns="urn:example:po">
<Items>
<Item Code="001-001-001" Quantity="1">
spade
</Item>
<Item Code="001-001-002" Quantity="1">
shovel
</Item>
</Items>
<ShippingAddress>
Dig PLC, 1 First Ave, Dublin 1, Ireland
</ShippingAddress>
<PaymentInfo Id="27a5af39-7896-4002-ad65-a4ec9015bf23">
<BillingAddress>
Dig PLC, 1 First Ave, Dublin 1, Ireland
</BillingAddress>
<CreditCard Type="Amex">
<Name>Foo B Baz</Name>
<Number>1234 567890 12345</Number>
<Expires Month="1" Year="2005"></Expires>
</CreditCard>
</PaymentInfo>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo><!--If I add a lot of blank spaces at the start of this line, the verification is ok-->
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod><!--If I add a blank space at the start of this line, the verification is not working-->
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></ds:SignatureMethod>
<ds:Reference URI="#27a5af39-7896-4002-ad65-a4ec9015bf23">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod>
<ds:DigestValue>ANOpAn94IZWzHAFvQSXI6rK3Hsg=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
Tlbljw+A69WQ4oClxMgv64RLunmxX0k92UJdqGkQUo7L35wpVgET1EjtZf7TF6ekaxuICDEISPmq
R7vwrRsmf3/u1v9bANaa0rOtO7nuJgxUs9kVndnMnAPWcIm9Njtl7GUEpt6BLMKwAVkbDjOq5Q0S
R3P9d31ppuYdOt7nnbGvgtYDDVIsQRA50rlZqotWI3odfh27HlJMtIys9YJ/0BuQKI4LaNhdymKQ
haRFe6jaAv3v0MVG9Hg53uaJf8vwKL0tYG73ZO2Wh2Tr0j0EPEJY72akyJdNcAHjHiSzZEw1HwRt
Y9Ns2cnbMLpVEYQzW8oO/TO8uf9UYXfUttFyLQ==
</ds:SignatureValue>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>
MIIDqjCCAxOgAwIBAgIBHzANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJERTEPMA0GA1UECBMG
QmF5ZXJuMQ8wDQYDVQQHEwZNdW5pY2gxDTALBgNVBAoTBEhvbWUxFTATBgNVBAsTDEFwYWNoZSBX
U1M0SjEPMA0GA1UEAxMGV2VybmVyMB4XDTE1MDkxMDEzMjEyMloXDTI1MDkwNzEzMjEyMlowUzEL
MAkGA1UEBhMCSUUxETAPBgNVBAgTCExlaW5zdGVyMQ8wDQYDVQQHEwZEdWJsaW4xDzANBgNVBAoT
BkFwYWNoZTEPMA0GA1UEAxMGQ2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
jeP2hF/KOv8Pw/++n9bAa0DxKFzyLvlcAciBgv1E01cCyUwn5X0v8nT9gIdjXBi6Y6PvSCs2GqcA
zo82yzIjlcDmocXt/2vBkezwh6Ow2xoXItzXD+I6KlhpH8gl4xXd35N0z+88m4SEclncm//l3sQH
P6CMG9O4H91A+0DXzTkQHzs2PEg13ONMOVbg5P85ceZ+VTBjd3BkqC0mZDB6Ovo6Xt06tVS/S4rF
R0L9mtyxZ+v/Psd8TYAIk3wlz1e1wm+dobp1YkOPgvv08r8ROVnWC0pRZquCPQUou6r50LseRZWE
o9MGN8kt4sYcLCaJbF3fsTvKYi6InWyczv2JSQIDAQABo4H2MIHzMAkGA1UdEwQCMAAwLAYJYIZI
AYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQsRXQjSEq1
PetSTIekkXnMBtntGTCBmAYDVR0jBIGQMIGNgBRWF+/2a4tZ/iMZaN54wOFNZ33QZqFqpGgwZjEL
MAkGA1UEBhMCREUxDzANBgNVBAgTBkJheWVybjEPMA0GA1UEBxMGTXVuaWNoMQ0wCwYDVQQKEwRI
b21lMRUwEwYDVQQLEwxBcGFjaGUgV1NTNEoxDzANBgNVBAMTBldlcm5lcoIJAI3hLAppEXfSMA0G
CSqGSIb3DQEBCwUAA4GBAKZXwqAplMzutyC8jIUzFN/DfQOAuZ8ExLBIUxW51KSzxxL+kfydq4HF
otRC7PZ3bYEkXYN7deUYoq6yFO0AAWvagBbxdzRYDPRCwpKe+4jn8VLGtjkEyLSsMiiaNiOuMx8q
WxvV1sA9KioHvZDFVP+0QqSc1ZytEPOKasdViUU9
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature></PurchaseOrder>
更新1:
ds:Signature
节点格式不正确。>
和<
之间的内容,像这块<Item>this is content</Item>
,this is content
就是payload,签名过程是否只对payload进行签名?或者它会包含<Item>
,<\Item>
,甚至还会在<Item>
之前包含一些空格,例如 <Item>
和\n
末尾的一些<\Item>
?有相当多的问题。
密钥库中的证书验证 XML 是否由预期密钥签名。您不能隐式信任 XML 文件中的签名。这就像我为你提供一个带照片的身份证件,它由一面镜子组成,上面写着“我”。它可能是正确的,但并不真正可靠。
格式化 XML 必须在签名之前完成。 XML 的规范化旨在确保所签名的 XML 是等效的。它使用规则处理 XML,以确保等效的 XML 具有相同的签名,但签名后的一些格式化和整理会破坏它。虽然我确实喜欢格式良好的 XML,但它应该由计算机处理,所以不用太担心...
更新1:
- 原始 xml 格式良好。目前,只有 ds:Signature 节点的格式不正确。
一般来说,没什么可做的。对 XML 进行签名需要库创建一个 xml 片段,其中包含 XML 规范版本的摘要。然后对该片段进行签名,结果是另一个 XML 片段。一般来说,它们是独立的,只要您不更改等效规范 xml 的组成部分,但有足够的规则使库在组合时很难使其变得漂亮。您需要以漂亮的格式组合原始 XML 和签名,然后使用更改的部分更新 Signature/SignedInfo。例如,将 SignedInfo 的新签名值插入到您知道格式良好的完整文档中。
- 当使用apache库处理标志时,我们如何知道XML文件已经格式化了?因为 XML 文件现在表示为 作为 Java 对象。
大多数(许多?一些?)XML 库都有一个可以将 XML 格式化为“漂亮”(或格式良好)的函数。然而,签名 XML 的性质意味着这样做可能会影响签名。
- 一般是>和<, like in this piece of this is content, this is content is the payload, Does the signing process only sign the payload? or it will contain the , <\Item>之间的内容,甚至在前面还会有一些空格 喜欢和一些 最后<\Item>?
规则因规范化方法的不同而有所不同。例如,请参阅 https://www.w3.org/TR/xml-c14n/#Terminology。但我希望这样
All whitespace in character content is retained (excluding characters removed during line feed normalization)
如果您想删除回车符( )在
<ds:SignatureValue>
和 <ds:X509Certificate>
内每行的末尾,您可以尝试将这些选项添加(或者)到 JVM(或 Servlet 容器):
-Dcom.sun.org.apache.xml.internal.security.ignoreLineBreaks=true
或
-Dorg.apache.xml.security.ignoreLineBreaks=true