JDK-8017171 : ID-references unresolvable in XML signature validation
  • Type: Bug
  • Component: security-libs
  • Sub-Component: javax.xml.crypto
  • Affected Version: 7u25
  • Priority: P2
  • Status: Closed
  • Resolution: Won't Fix
  • OS: windows_7
  • Submitted: 2013-06-20
  • Updated: 2013-06-28
  • Resolved: 2013-06-20
Related Reports
Duplicate :  
Duplicate :  
Duplicate :  
Description
FULL PRODUCT VERSION :
java version  " 1.7.0_25 " 
Java(TM) SE Runtime Environment (build 1.7.0_25-b16)
Java HotSpot(TM) 64-Bit Server VM (build 23.25-b01, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Windows 7 Professional, 64-bit

A DESCRIPTION OF THE PROBLEM :
XML signature validation code that functions in jre170_21 and earlier, breaks in the latest release. This pertains to signatures for manifest items, referenced by xmldsig:id attributes in the XML document.

The XML signature validation source code is available here:
https://raw.github.com/joval/jOVAL/master/src/org/joval/xml/SignatureValidator.java

A Java keystore file containing the NIST certificate is available here:
https://github.com/joval/jOVAL/raw/master/components/jovaldi/rsrc/security/cacerts.jks

I am attempting to validate the XML documents contained here (specifically scap_gov.nist_USGCB-Windows-7.xml):
http://usgcb.nist.gov/usgcb/content/scap/oval510/Win7-510-1.2.7.1.zip


REGRESSION.  Last worked in version 6u45

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Instantiate an org.joval.xml.SignatureValidator with the XML file and keystore backed by the file I referenced above, and invoke the validate() method.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The method should return true.
ACTUAL -
An XMLSignatureException is thrown.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
2013.06.19 22:46:16.588 - INFO - Verifying XML digital signature: ..\..\..\..\content\scap12\scap_gov.nist_USGCB-Windows-7.xml
2013.06.19 22:46:17.334 - SEVERE - javax.xml.crypto.dsig.XMLSignatureException: javax.xml.crypto.URIReferenceException: com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID scap_gov.nist_datastream_USGCB-Windows-7-1.2.3.1.zip
    org.jcp.xml.dsig.internal.dom.DOMReference.dereference(DOMReference.java:412)
    org.jcp.xml.dsig.internal.dom.DOMReference.validate(DOMReference.java:371)
    org.jcp.xml.dsig.internal.dom.DOMXMLSignature.validate(DOMXMLSignature.java:265)
    org.joval.xml.SignatureValidator.validate(SignatureValidator.java:104)
    org.joval.scap.xccdf.engine.XPERT.main(XPERT.java:283)
caused by:
javax.xml.crypto.URIReferenceException: com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID scap_gov.nist_datastream_USGCB-Windows-7-1.2.3.1.zip
    org.jcp.xml.dsig.internal.dom.DOMURIDereferencer.dereference(DOMURIDereferencer.java:124)
    org.jcp.xml.dsig.internal.dom.DOMReference.dereference(DOMReference.java:404)
    org.jcp.xml.dsig.internal.dom.DOMReference.validate(DOMReference.java:371)
    org.jcp.xml.dsig.internal.dom.DOMXMLSignature.validate(DOMXMLSignature.java:265)
    org.joval.xml.SignatureValidator.validate(SignatureValidator.java:104)
    org.joval.scap.xccdf.engine.XPERT.main(XPERT.java:283)
caused by:
com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID scap_gov.nist_datastream_USGCB-Windows-7-1.2.3.1.zip
    com.sun.org.apache.xml.internal.security.utils.resolver.implementations.ResolverFragment.engineResolve(ResolverFragment.java:90)
    com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolver.resolve(ResourceResolver.java:283)
    org.jcp.xml.dsig.internal.dom.DOMURIDereferencer.dereference(DOMURIDereferencer.java:117)
    org.jcp.xml.dsig.internal.dom.DOMReference.dereference(DOMReference.java:404)
    org.jcp.xml.dsig.internal.dom.DOMReference.validate(DOMReference.java:371)
    org.jcp.xml.dsig.internal.dom.DOMXMLSignature.validate(DOMXMLSignature.java:265)
    org.joval.xml.SignatureValidator.validate(SignatureValidator.java:104)
    org.joval.scap.xccdf.engine.XPERT.main(XPERT.java:283)


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
// Copyright (C) 2012 jOVAL.org.  All rights reserved.
// This software is licensed under the AGPL 3.0 license available at http://www.joval.org/agpl_v3.txt

package org.joval.xml;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.security.Key;
import java.security.KeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.CertSelector;
import java.security.cert.X509Certificate;
import java.security.cert.X509CertSelector;
import javax.security.auth.x500.X500Principal;
import javax.xml.XMLConstants;
import javax.xml.crypto.AlgorithmMethod;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.KeySelectorException;
import javax.xml.crypto.KeySelectorResult;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.NoSuchMechanismException;
import javax.xml.crypto.NodeSetData;
import javax.xml.crypto.OctetStreamData;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyName;
import javax.xml.crypto.dsig.keyinfo.KeyValue;
import javax.xml.crypto.dsig.keyinfo.RetrievalMethod;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.keyinfo.X509IssuerSerial;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import org.xml.sax.SAXException;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

/**
 * Utility class for validating XML digital signatures.
 * @see http://docs.oracle.com/cd/E17802_01/webservices/webservices/docs/1.6/tutorial/doc/XMLDigitalSignatureAPI8.html
 *
 * @author David A. Solin
 * @version %I% %G%
 */
public class SignatureValidator {
    private Document doc;
    private NodeList signatures;
    private KeyStore keyStore;

    /**
     * Create a new validator for the specified XML file using the specified KeyStore.
     */
    public SignatureValidator(File f, KeyStore keyStore)
throws FactoryConfigurationError, ParserConfigurationException, SAXException, IOException {

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
doc = dbf.newDocumentBuilder().parse(f);
signatures = doc.getElementsByTagNameNS(XMLSignature.XMLNS,  " Signature " );
this.keyStore = keyStore;
    }

    /**
     * Assign a specific KeyStore to be used for validation.
     */
    public void setKeyStore(KeyStore keyStore) {
this.keyStore = keyStore;
    }

    /**
     * Returns whether or not there is an XML signature node in the document.
     */
    public boolean containsSignature() {
return signatures.getLength() > 0;
    }

    /**
     * Validate the document's XML digital signature.
     */
    public boolean validate() throws XMLSignatureException {
int len = signatures.getLength();
if (len == 0) {
    throw new XMLSignatureException( " Cannot find Signature element " );
} else {
    try {
for (int i=0; i < len; i++) {
    DOMValidateContext ctx = new DOMValidateContext(new X509KeySelector(), signatures.item(i));
    XMLSignature signature = XMLSignatureFactory.getInstance().unmarshalXMLSignature(ctx);
    if (!signature.validate(ctx)) {
return false;
    }
}
return true;
    } catch (NoSuchMechanismException e) {
throw new XMLSignatureException(e);
    } catch (ClassCastException e) {
throw new XMLSignatureException(e);
    } catch (MarshalException e) {
throw new XMLSignatureException(e);
    }
}
    }

    /**
     * Validate the document's XML digital signature using the supplied public key.
     */
    public boolean validate(Key validationKey) throws XMLSignatureException {
int len = signatures.getLength();
if (len == 0) {
    throw new XMLSignatureException( " Cannot find Signature element " );
} else {
    try {
for (int i=0; i < len; i++) {
    DOMValidateContext ctx = new DOMValidateContext(validationKey, signatures.item(i));
    XMLSignature signature = XMLSignatureFactory.getInstance().unmarshalXMLSignature(ctx);
    if (!signature.validate(ctx)) {
return false;
    }
}
return true;
    } catch (NoSuchMechanismException e) {
throw new XMLSignatureException(e);
    } catch (ClassCastException e) {
throw new XMLSignatureException(e);
    } catch (MarshalException e) {
throw new XMLSignatureException(e);
    }
}
    }

    /**
     * Obtain a Source for the document. This makes it possible to insure that the underlying data has been validated.
     */
    public Source getSource() {
return new DOMSource(doc);
    }

    // Private

    /**
     * KeySelector implementation for X.509 certificates.
     */
    class X509KeySelector extends KeySelector {
@Override
public KeySelectorResult select(KeyInfo ki, KeySelector.Purpose p, AlgorithmMethod am, XMLCryptoContext ctx)
throws KeySelectorException {

    KeySelectorResult result = null;
    SignatureMethod sm = (SignatureMethod)am;
    for (Object obj : ki.getContent()) {
XMLStructure xs = (XMLStructure)obj;
if (xs instanceof X509Data) {
    result = select((X509Data)xs, sm);
} else if (xs instanceof KeyName) {
    KeyName kn = (KeyName)xs;
    try {
Certificate cert = keyStore.getCertificate(kn.getName());
if (cert != null && equals(sm.getAlgorithm(), cert.getPublicKey().getAlgorithm())) {
    result = new SimpleKeySelectorResult(cert.getPublicKey());
}
    } catch (KeyStoreException e) {
throw new KeySelectorException(e);
    }
} else if (xs instanceof RetrievalMethod) {
    RetrievalMethod rm = (RetrievalMethod)xs;
    try {
if (rm.getType().equals(X509Data.RAW_X509_CERTIFICATE_TYPE)) {
    OctetStreamData data = (OctetStreamData)rm.dereference(ctx);
    CertificateFactory cf = CertificateFactory.getInstance( " X.509 " );
    X509Certificate cert = (X509Certificate)cf.generateCertificate(data.getOctetStream());
    result = select(cert, sm);
} else if (rm.getType().equals(X509Data.TYPE)) {
    NodeSetData nd = (NodeSetData)rm.dereference(ctx);
    System.out.println( " DAS conversion is TBD " );
}
    } catch (Exception e) {
throw new KeySelectorException(e);
    }
} else if (xs instanceof KeyValue) {
    PublicKey pk = null;
    try {
pk = ((KeyValue)xs).getPublicKey();
    } catch (KeyException ke) {
throw new KeySelectorException(ke);
    }
    if (equals(sm.getAlgorithm(), pk.getAlgorithm())) {
result = new SimpleKeySelectorResult(pk);
    }
}
if (result != null) {
    return result;
}
    }
    throw new KeySelectorException( " Key not found " );
}

// Internal

KeySelectorResult select(X509Data data, SignatureMethod sm) throws KeySelectorException {
    String oid = getOid(sm.getAlgorithm());
    KeySelectorResult result = null;
    for (Object obj : data.getContent()) {
try {
    if (obj instanceof X509Certificate) {
return select((X509Certificate)obj, sm);
    } else if (obj instanceof X509IssuerSerial) {
X509IssuerSerial xis = (X509IssuerSerial)obj;
X509CertSelector xcs = new X509CertSelector();
xcs.setSubjectPublicKeyAlgID(oid);
xcs.setSerialNumber(xis.getSerialNumber());
xcs.setIssuer(new X500Principal(xis.getIssuerName()).getName());
return select(xcs);
    } else if (obj instanceof String) {
String sn = (String)obj;
X509CertSelector xcs = new X509CertSelector();
xcs.setSubjectPublicKeyAlgID(oid);
xcs.setSubject(new X500Principal(sn).getName());
return select(xcs);
    } else if (obj instanceof byte[]) {
byte[] ski = (byte[])obj;
X509CertSelector xcs = new X509CertSelector();
xcs.setSubjectPublicKeyAlgID(oid);
byte[] encodedSki = new byte[ski.length+2];
encodedSki[0] = 0x04; // OCTET STRING tag value
encodedSki[1] = (byte)ski.length;
System.arraycopy(ski, 0, encodedSki, 2, ski.length);
xcs.setSubjectKeyIdentifier(encodedSki);
return select(xcs);
    }
} catch (IOException e) {
    throw new KeySelectorException(e);
} catch (KeyStoreException e) {
    throw new KeySelectorException(e);
} catch (NoSuchElementException e) {
    throw new KeySelectorException(e);
}
    }
    return null;
}

/**
 * Return the certificate in the key store that matches the specified selector
 *
 * @throws NoSuchElementException if there is no match
 */
KeySelectorResult select(X509CertSelector cs) throws KeyStoreException, NoSuchElementException {
    for (Enumeration e = keyStore.aliases(); e.hasMoreElements(); ) {
Certificate cert = keyStore.getCertificate((String)e.nextElement());
if (cert != null && cs.match(cert)) {
    return new SimpleKeySelectorResult(cert.getPublicKey());
}
    }
    throw new NoSuchElementException(cs.getSubject().toString());
}

/**
 * Return the certificate in the key store that matches the specified X509 certificate (or null if there is no match).
 *
 * @throws NoSuchElementException if there is no match
 */
KeySelectorResult select(X509Certificate xc, SignatureMethod sm) throws KeyStoreException, NoSuchElementException {
    boolean[] keyUsage = xc.getKeyUsage();
    if (keyUsage[0]) {
String alias = keyStore.getCertificateAlias(xc);
if (alias != null) {
    PublicKey pk = keyStore.getCertificate(alias).getPublicKey();
    if (equals(sm.getAlgorithm(), pk.getAlgorithm())) {
return new SimpleKeySelectorResult(pk);
    }
}
    }
    throw new NoSuchElementException(xc.getSubjectX500Principal().toString());
}

/**
 * Return the OID corresponding to the Algorithm URI.
 */
String getOid(String uri) {
    if (uri.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) {
return  " 1.2.840.10040.4.1 " ;
    } else if (uri.equalsIgnoreCase(SignatureMethod.RSA_SHA1)) {
return  " 1.2.840.113549.1.1 " ;
    } else {
return null;
    }
}

/**
 * Determine whether the algorithm URI matches the algorithm name.
 */
boolean equals(String uri, String name) {
    if (name.equalsIgnoreCase( " DSA " ) && uri.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) {
return true;
    } else if (name.equalsIgnoreCase( " RSA " ) && uri.equalsIgnoreCase(SignatureMethod.RSA_SHA1)) {
return true;
    } else {
return false;
    }
}
    }

    /**
     * A simple KeySelectorResult implementation.
     */
    static class SimpleKeySelectorResult implements KeySelectorResult {
private Key key;

SimpleKeySelectorResult(Key key) {
    this.key = key;
}

// Implement KeySelectorResult

public Key getKey() {
    return key;
}
    }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
We will have to distribute our application with an earlier release of Java, such as 170_21.
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