Hi,
I've got some problems with implementing XAdES standard with Java XML Digital Signature API. Below is a code (
SignatureTest1), that produces a digital signature with some XAdES tags placed in
<ds:Object> tag. The signature is later validated with a
Validator class. Everything works fine, until I set a XAdES namespace (
SignatureTest1.xadesNS="http://uri.etsi.org/01903/v1.3.2#"). In this case validation of XAdES elements fails.
The reason of validation failture is a difference between arguments passed to a digest method when document is being signed and validated. When the document is being signed a log looks like this:
FINER: Pre-digested input:
2007-08-21 15:38:44 org.jcp.xml.dsig.internal.DigesterOutputStream write
FINER: <SignedProperties xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="SignP"></SignedProperties>
2007-08-21 15:38:44 org.jcp.xml.dsig.internal.dom.DOMReference digest
FINE: Reference object uri = #SignP
2007-08-21 15:38:44 org.jcp.xml.dsig.internal.dom.DOMReference digest
FINE: Reference digesting completed
,but while validating:
FINER: Pre-digested input:
2007-08-21 15:38:44 org.jcp.xml.dsig.internal.DigesterOutputStream write
FINER: <SignedProperties xmlns="http://uri.etsi.org/01903/v1.3.2#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="SignP"></SignedProperties>
2007-08-21 15:38:44 org.jcp.xml.dsig.internal.dom.DOMReference validate
FINE: Expected digest: MAQ/vctdkyVHVzoQWnOnQdeBw8g=
2007-08-21 15:38:44 org.jcp.xml.dsig.internal.dom.DOMReference validate
FINE: Actual digest: D7WajkF0U5t1GnVJqj9g1IntLQg=
2007-08-21 15:38:44 org.jcp.xml.dsig.internal.dom.DOMXMLSignature validate
FINE: Reference[#SignP] is valid: false
How can I fix this?
Signer class:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.xml.crypto.dom.DOMStructure;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLObject;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.KeyValue;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import com.sun.org.apache.xml.internal.security.utils.IdResolver;
public class SignatureTest1 {
public static String xadesNS=null;//"http://uri.etsi.org/01903/v1.3.2#";
public static String signatureID="Sig1";
public static String signedPropID="SignP";
public static void main(String[] arg) {
try{
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
List<Reference> refs = new ArrayList<Reference>();
Reference ref1 = fac.newReference
("", fac.newDigestMethod(DigestMethod.SHA1, null),
Collections.singletonList
(fac.newTransform
(Transform.ENVELOPED, (TransformParameterSpec) null)),
null, null);
refs.add(ref1);
Reference ref2 = fac.newReference("#"+signedPropID,fac.newDigestMethod(DigestMethod.SHA1,null),null,"http://uri.etsi.org/01903/v1.3.2#SignedProperties",null);
refs.add(ref2);
SignedInfo si = fac.newSignedInfo
(fac.newCanonicalizationMethod
(CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS,
(C14NMethodParameterSpec) null),
fac.newSignatureMethod(SignatureMethod.DSA_SHA1, null),
refs);
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(512);
KeyPair kp = kpg.generateKeyPair();
KeyInfoFactory kif = fac.getKeyInfoFactory();
KeyValue kv = kif.newKeyValue(kp.getPublic());
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(kv));
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc =
dbf.newDocumentBuilder().parse("purchaseOrder.xml");
DOMSignContext dsc = new DOMSignContext
(kp.getPrivate(), doc.getDocumentElement());
dsc.putNamespacePrefix(XMLSignature.XMLNS, "ds");
Element QPElement = createElement(doc, "QualifyingProperties",null,xadesNS);
QPElement.setAttributeNS(null, "Target", signatureID);
Element SPElement = createElement(doc, "SignedProperties", null,xadesNS);
SPElement.setAttributeNS(null, "Id", signedPropID);
IdResolver.registerElementById(SPElement, signedPropID);
QPElement.appendChild(SPElement);
Element UPElement = createElement(doc, "UnsignedProperties", null,xadesNS);
QPElement.appendChild(UPElement);
DOMStructure qualifPropStruct = new DOMStructure(QPElement);
List<DOMStructure> xmlObj = new ArrayList<DOMStructure>();
xmlObj.add(qualifPropStruct);
XMLObject object = fac.newXMLObject(xmlObj,"QualifyingInfos",null,null);
List objects = Collections.singletonList(object);
XMLSignature signature = fac.newXMLSignature(si, ki,objects,signatureID,null);
signature.sign(dsc);
OutputStream os = new FileOutputStream("signedPurchaseOrder.xml");
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(new DOMSource(doc), new StreamResult(os));
}catch(Exception e){
e.printStackTrace();
}
try{
Validator.main(null);
}catch(Exception e){
System.out.println("Validator exception");
e.printStackTrace();
}
}
public static Element createElement(Document doc, String tag,String prefix, String nsURI) {
String qName = prefix == null ? tag : prefix + ":" + tag;
return doc.createElementNS(nsURI, qName);
}
}
Validator class:
import javax.xml.crypto.*;
import javax.xml.crypto.dsig.*;
import javax.xml.crypto.dom.*;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.*;
import java.io.FileInputStream;
import java.security.*;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
/**
* This is a simple example of validating an XML
* Signature using the JSR 105 API. It assumes the key needed to
* validate the signature is contained in a KeyValue KeyInfo.
*/
public class Validator {
//
// Synopsis: java Validate [document]
//
// where "document" is the name of a file containing the XML document
// to be validated.
//
public static void main(String[] args) throws Exception {
// Instantiate the document to be validated
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc =
dbf.newDocumentBuilder().parse(new FileInputStream("signedPurchaseOrder.xml"));
// Find Signature element
NodeList nl =
doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
if (nl.getLength() == 0) {
throw new Exception("Cannot find Signature element");
}
// Create a DOM XMLSignatureFactory that will be used to unmarshal the
// document containing the XMLSignature
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
// Create a DOMValidateContext and specify a KeyValue KeySelector
// and document context
DOMValidateContext valContext = new DOMValidateContext
(new KeyValueKeySelector(), nl.item(0));
// unmarshal the XMLSignature
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
// Validate the XMLSignature (generated above)
boolean coreValidity = signature.validate(valContext);
// Check core validation status
if (coreValidity == false) {
System.err.println("Signature failed core validation");
boolean sv = signature.getSignatureValue().validate(valContext);
System.out.println("signature validation status: " + sv);
// check the validation status of each Reference
Iterator i = signature.getSignedInfo().getReferences().iterator();
for (int j=0; i.hasNext(); j++) {
boolean refValid =
((Reference) i.next()).validate(valContext);
System.out.println("ref["+j+"] validity status: " + refValid);
}
} else {
System.out.println("Signature passed core validation");
}
}
/**
* KeySelector which retrieves the public key out of the
* KeyValue element and returns it.
* NOTE: If the key algorithm doesn't match signature algorithm,
* then the public key will be ignored.
*/
private static class KeyValueKeySelector extends KeySelector {
public KeySelectorResult select(KeyInfo keyInfo,
KeySelector.Purpose purpose,
AlgorithmMethod method,
XMLCryptoContext context)
throws KeySelectorException {
if (keyInfo == null) {
throw new KeySelectorException("Null KeyInfo object!");
}
SignatureMethod sm = (SignatureMethod) method;
List list = keyInfo.getContent();
for (int i = 0; i < list.size(); i++) {
XMLStructure xmlStructure = (XMLStructure) list.get(i);
if (xmlStructure instanceof KeyValue) {
PublicKey pk = null;
try {
pk = ((KeyValue)xmlStructure).getPublicKey();
} catch (KeyException ke) {
throw new KeySelectorException(ke);
}
// make sure algorithm is compatible with method
if (algEquals(sm.getAlgorithm(), pk.getAlgorithm())) {
return new SimpleKeySelectorResult(pk);
}
}
}
throw new KeySelectorException("No KeyValue element found!");
}
//@@@FIXME: this should also work for key types other than DSA/RSA
static boolean algEquals(String algURI, String algName) {
if (algName.equalsIgnoreCase("DSA") &&
algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) {
return true;
} else if (algName.equalsIgnoreCase("RSA") &&
algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1)) {
return true;
} else {
return false;
}
}
}
private static class SimpleKeySelectorResult implements KeySelectorResult {
private PublicKey pk;
SimpleKeySelectorResult(PublicKey pk) {
this.pk = pk;
}
public Key getKey() { return pk; }
}
}
PurchaseOrder.xml
<?xml version="1.0" encoding="UTF-8"?>
<PurchaseOrder>
<Item number="130046593231">
<Description>Video Game</Description>
<Price>10.29</Price>
</Item>
<Buyer id="8492340">
<Name>My Name</Name>
<Address>
<Street>One Network Drive</Street>
<Town>Burlington</Town>
<State>MA</State>
<Country>United States</Country>
<PostalCode>01803</PostalCode>
</Address>
</Buyer>
</PurchaseOrder>
signedPurchaseOrder.xml with XAdES namespace:
<?xml version="1.0" encoding="UTF-8" standalone="no"?><PurchaseOrder>
<Item number="130046593231">
<Description>Video Game</Description>
<Price>10.29</Price>
</Item>
<Buyer id="8492340">
<Name>My Name</Name>
<Address>
<Street>One Network Drive</Street>
<Town>Burlington</Town>
<State>MA</State>
<Country>United States</Country>
<PostalCode>01803</PostalCode>
</Address>
</Buyer>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="Sig1"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/><ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#dsa-sha1"/><ds:Reference URI=""><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>tVicGh6V+8cHbVYFIU91o5+L3OQ=</ds:DigestValue></ds:Reference><ds:Reference Type="http://uri.etsi.org/01903/v1.3.2#SignedProperties" URI="#SignP"><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>MAQ/vctdkyVHVzoQWnOnQdeBw8g=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>lSgzfZCRIlgrgr6YpNOdB3XWdF9P9TEiXfkNoqUpAru/I7IiyiFWJg==</ds:SignatureValue><ds:KeyInfo><ds:KeyValue><ds:DSAKeyValue><ds:P>/KaCzo4Syrom78z3EQ5SbbB4sF7ey80etKII864WF64B81uRpH5t9jQTxeEu0ImbzRMqzVDZkVG9
xD7nN1kuFw==</ds:P><ds:Q>li7dzDacuo67Jg7mtqEm2TRuOMU=</ds:Q><ds:G>Z4Rxsnqc9E7pGknFFH2xqaryRPBaQ01khpMdLRQnG541Awtx/XPaF5Bpsy4pNWMOHCBiNU0Nogps
QW5QvnlMpA==</ds:G><ds:Y>p48gU203NGPcs9UxEQQQzQ19KBtDRGfEs3BDt0cbCRJHMh3EoySpeqOnuTeKLXuFr96nzAPq4BEU
dNAc7XpDvQ==</ds:Y></ds:DSAKeyValue></ds:KeyValue></ds:KeyInfo><ds:Object Id="QualifyingInfos"><QualifyingProperties Target="Sig1" xmlns="http://uri.etsi.org/01903/v1.3.2#"><SignedProperties Id="SignP"/><UnsignedProperties/></QualifyingProperties></ds:Object></ds:Signature></PurchaseOrder>