Reputation: 1099
From apache santuario xmlsignature test, I do a test: add a signature to the XML file, then save it as a file, after that read it from that file, and verify the signature. Please refer to the comments in the code.
The plain xml file is :
<?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>
I have some questions:
<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-->
The main Java Code:
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);
}
}
Include functions in SignatureUtils.java
for integrity:
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;
}
}
The generated xml file with signatue, It's not well formatted, I would like to format it, but then it caused the verification to fail.
<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>
Update 1:
ds:Signature
node is not well formatted.>
and <
, like in this piece of <Item>this is content</Item>
, this is content
is the payload, Does the signing process only sign the payload? or it will contain the <Item>
, <\Item>
, even more it will include some blank space before the <Item>
like <Item>
and some \n
in the end of <\Item>
?Upvotes: 0
Views: 2338
Reputation: 61
In case you want to remove the Carriage Return (
) at the end of each line inside the <ds:SignatureValue>
and the <ds:X509Certificate>
you can try adding (alternatively) these options to the JVM (or Servlet container):
-Dcom.sun.org.apache.xml.internal.security.ignoreLineBreaks=true
or
-Dorg.apache.xml.security.ignoreLineBreaks=true
Upvotes: 1
Reputation: 559
A fair number of questions there.
The cert in the keystore validated that the XML is signed by the expected key. You can't implicitly trust the signature in the XML file. It's like me providing you a picture ID that consists of a mirror with "me" written at the top. It might be correct but it's not real reliable.
Formatting XML must be done before it is signed. The canonicalization of XML is meant to ensure that the XML being signed is equivalent. It processes the XML with rules to ensure that equivalent XML would have the same signature but some formatting and tidying after the signature would break it. While I do like nicely formatted XML it's meant to be processed by by computers so don't worry too much about it...
Update 1:
- The original xml is well formatted. Currently, only the ds:Signature node is not well formatted.
Not much to be done with that in general. Signing the XML requires the library to create an xml fragment containing a digest for the canonical version of your XML. The fragment is then signed and the result is another XML fragment. In general they are independent in that as long as you don't change what makes an equivalent canonical xml but there are enough rules to make it difficult for the library to make it pretty when combined. You would need to have a combined original XML and Signature in a pretty format and then update the Signature/SignedInfo with the parts that change. For example insert the new signature value for the SignedInfo into a complete document that you know is well formatted.
- When process the sign with the apache library, How do we know the XML file is already formatted? Because the XML file is now represented as Java objects.
Most (many? some?) XML libraries have a function that can format the XML as "pretty" (or well formatted). However the nature of Signed XML would mean that the signature could be affected by doing that.
- Usually, the content between > and <, 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>, even more it will include some blank space before the like and some \n in the end of <\Item>?
The rules vary somewhat by canonicalization method. See https://www.w3.org/TR/xml-c14n/#Terminology for example. But I would expect that
All whitespace in character content is retained (excluding characters removed during line feed normalization)
Upvotes: 0