Skip to Main Content

Java Security

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Implementing XAdES in Java XML Digital Signature API

843811Aug 21 2007 — edited Aug 23 2007
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>
Comments
Locked Post
New comments cannot be posted to this locked post.
Post Details
Locked on Sep 20 2007
Added on Aug 21 2007
2 comments
3,687 views