JDK-8017169 : XML Signature Validation throws URIReferenceException
  • Type: Bug
  • Component: security-libs
  • Sub-Component: javax.xml.crypto
  • Affected Version: 7u25
  • Priority: P2
  • Status: Closed
  • Resolution: Duplicate
  • OS: linux
  • Submitted: 2013-06-20
  • Updated: 2013-06-20
  • Resolved: 2013-06-20
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
java version  " 1.7.0_25 " 
Java(TM) SE Runtime Environment (build 1.7.0_25-b15)
Java HotSpot(TM) 64-Bit Server VM (build 23.25-b01, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
Linux debian 3.2.0-4-amd64 #1 SMP Debian 3.2.35-2 x86_64 GNU/Linux

A DESCRIPTION OF THE PROBLEM :
When using a ID to reference the part, which should be signed in the XML document - on validating the reference cannot be resolved.

Stacktrace:
$ ~/programs/jdk1.7.0_25/bin/java -cp bin XmlSigningTest
Exception in thread  " main "  javax.xml.crypto.dsig.XMLSignatureException: javax.xml.crypto.URIReferenceException: com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID _123
at org.jcp.xml.dsig.internal.dom.DOMReference.dereference(DOMReference.java:412)
...

REGRESSION.  Last worked in version 7

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
* Create a keystore with password  " changeit " : keytool -genkeypair -keyalg RSA -alias mykey -keystore mykeystore.jks
* Create the source xml file with the following content:
<?xml version= " 1.0 "  encoding= " UTF-8 " ?>
<PurchaseOrder ID= " _123 " >
 <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>

* Execute the test case, see source code section.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No exception, but  " Validation Result: true " 
ACTUAL -
Stacktrace:
$ ~/programs/jdk1.7.0_25/bin/java -cp bin XmlSigningTest
Exception in thread  " main "  javax.xml.crypto.dsig.XMLSignatureException: javax.xml.crypto.URIReferenceException: com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID _123
at org.jcp.xml.dsig.internal.dom.DOMReference.dereference(DOMReference.java:412)
at org.jcp.xml.dsig.internal.dom.DOMReference.digest(DOMReference.java:338)
at org.jcp.xml.dsig.internal.dom.DOMXMLSignature.digestReference(DOMXMLSignature.java:471)
at org.jcp.xml.dsig.internal.dom.DOMXMLSignature.sign(DOMXMLSignature.java:367)
at XmlSigningTest.main(XmlSigningTest.java:111)
Caused by: javax.xml.crypto.URIReferenceException: com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID _123
at org.jcp.xml.dsig.internal.dom.DOMURIDereferencer.dereference(DOMURIDereferencer.java:124)
at org.jcp.xml.dsig.internal.dom.DOMReference.dereference(DOMReference.java:404)
... 4 more
Caused by: com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID _123
at com.sun.org.apache.xml.internal.security.utils.resolver.implementations.ResolverFragment.engineResolve(ResolverFragment.java:90)
at com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolver.resolve(ResourceResolver.java:283)
at org.jcp.xml.dsig.internal.dom.DOMURIDereferencer.dereference(DOMURIDereferencer.java:117)
... 5 more
javax.xml.crypto.URIReferenceException: com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID _123
at org.jcp.xml.dsig.internal.dom.DOMURIDereferencer.dereference(DOMURIDereferencer.java:124)
at org.jcp.xml.dsig.internal.dom.DOMReference.dereference(DOMReference.java:404)
at org.jcp.xml.dsig.internal.dom.DOMReference.digest(DOMReference.java:338)
at org.jcp.xml.dsig.internal.dom.DOMXMLSignature.digestReference(DOMXMLSignature.java:471)
at org.jcp.xml.dsig.internal.dom.DOMXMLSignature.sign(DOMXMLSignature.java:367)
at XmlSigningTest.main(XmlSigningTest.java:111)
Caused by: com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID _123
at com.sun.org.apache.xml.internal.security.utils.resolver.implementations.ResolverFragment.engineResolve(ResolverFragment.java:90)
at com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolver.resolve(ResourceResolver.java:283)
at org.jcp.xml.dsig.internal.dom.DOMURIDereferencer.dereference(DOMURIDereferencer.java:117)
... 5 more


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.security.Key;
import java.security.KeyStore;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.xml.crypto.AlgorithmMethod;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.KeySelectorException;
import javax.xml.crypto.KeySelectorResult;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
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.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.X509Data;
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.NodeList;

/**
 *
 * keytool -genkeypair -keyalg RSA -alias mykey -keystore mykeystore.jks
 *
 *
 * @see http://www.oracle.com/technetwork/articles/javase/dig-signature-api-140772.html
 */
public class XmlSigningTest {

    private static final String ID =  " #_123 " ;

    public static void main(String[] args) throws Exception {
        // Create a DOM XMLSignatureFactory that will be used to
        // generate the enveloped signature.
        XMLSignatureFactory fac = XMLSignatureFactory.getInstance( " DOM " );

        Reference ref = fac
            .newReference(ID, fac.newDigestMethod(DigestMethod.SHA1, null),
                Collections.singletonList(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)), null,
                null);

        // Create the SignedInfo.
        SignedInfo si = fac.newSignedInfo(
            fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null),
            fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null), Collections.singletonList(ref));

        // Load the KeyStore and get the signing key and certificate.
        KeyStore ks = KeyStore.getInstance( " JKS " );
        ks.load(new FileInputStream( " mykeystore.jks " ),  " changeit " .toCharArray());
        KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry) ks.getEntry( " mykey " ,
            new KeyStore.PasswordProtection( " changeit " .toCharArray()));
        X509Certificate cert = (X509Certificate) keyEntry.getCertificate();

        // Create the KeyInfo containing the X509Data.
        KeyInfoFactory kif = fac.getKeyInfoFactory();
        List x509Content = new ArrayList();
        x509Content.add(cert.getSubjectX500Principal().getName());
        x509Content.add(cert);
        X509Data xd = kif.newX509Data(x509Content);
        KeyInfo ki = kif.newKeyInfo(Collections.singletonList(xd));

        // Instantiate the document to be signed.
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        Document doc = dbf.newDocumentBuilder().parse(new FileInputStream( " purchaseOrder.xml " ));

        // Create a DOMSignContext and specify the RSA PrivateKey and
        // location of the resulting XMLSignature's parent element.
        DOMSignContext dsc = new DOMSignContext(keyEntry.getPrivateKey(), doc.getDocumentElement());

        // Create the XMLSignature, but don't sign it yet.
        XMLSignature signature = fac.newXMLSignature(si, ki);

        // Marshal, generate, and sign the enveloped signature.
        signature.sign(dsc);

        // Output the resulting document.
        OutputStream os = new FileOutputStream( " signedPurchaseOrder.xml " );
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer trans = tf.newTransformer();
        trans.transform(new DOMSource(doc), new StreamResult(os));

        // Find Signature element.
        NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS,  " Signature " );
        if (nl.getLength() == 0) {
            throw new Exception( " Cannot find Signature element " );
        }

        // Create a DOMValidateContext and specify a KeySelector
        // and document context.
        DOMValidateContext valContext = new DOMValidateContext(new X509KeySelector(), nl.item(0));

        // Unmarshal the XMLSignature.
        XMLSignature signature2 = fac.unmarshalXMLSignature(valContext);

        // Validate the XMLSignature.
        boolean coreValidity = signature2.validate(valContext);
        
        System.out.println( " Validation Result:  "  + coreValidity);
    }

    public static class X509KeySelector extends KeySelector {
        @Override
        public KeySelectorResult select(KeyInfo keyInfo, KeySelector.Purpose purpose, AlgorithmMethod method,
            XMLCryptoContext context) throws KeySelectorException {
            Iterator ki = keyInfo.getContent().iterator();
            while (ki.hasNext()) {
                XMLStructure info = (XMLStructure) ki.next();
                if (!(info instanceof X509Data)) {
                    continue;
                }
                X509Data x509Data = (X509Data) info;
                Iterator xi = x509Data.getContent().iterator();
                while (xi.hasNext()) {
                    Object o = xi.next();
                    if (!(o instanceof X509Certificate)) {
                        continue;
                    }
                    final PublicKey key = ((X509Certificate) o).getPublicKey();
                    // Make sure the algorithm is compatible
                    // with the method.
                    if (algEquals(method.getAlgorithm(), key.getAlgorithm())) {
                        return new KeySelectorResult() {
                            @Override
                            public Key getKey() {
                                return key;
                            }
                        };
                    }
                }
            }
            throw new KeySelectorException( " No key found! " );
        }

        static boolean algEquals(String algURI, String algName) {
            if ((algName.equalsIgnoreCase( " DSA " ) && algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1))
                || (algName.equalsIgnoreCase( " RSA " ) && algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1))) {
                return true;
            } else {
                return false;
            }
        }
    }
}

---------- END SOURCE ----------
Comments
The previous code which arbitrarily searched for ID references by the name of "Id" has been removed because it was insecure and is subject to XML Signature wrapping attacks. There are 3 potential workarounds that you can apply: 1. Use a validating schema which will register the elements with ID references. 2. Register the ID elements with the DOMValidateContext.setIdAttributeNS method before validating the signature 3. Implement a custom URIDereferencer which can find these references and override the builtin URIDereferencer with the DOMValidateContext.setURIDereferencer method.
20-06-2013