I am attempting to write some java code to verify the XML digital signature of a SAML response. I have verified the SAML response with other tools, so I know it is valid (excluding timing issues, not a factor to the digital signature). Below is the code I have used that I believe should be able to do this validation as well as the signature I am trying to validate.
When I run the code, I get the following output
Signature 0:
..Signature failed core validation
....signature validation status: true
....ref[0, #id14167335278088961501144300] validation status: false
Signature 1:
..Signature passed core validation
....signature validation status: true
....ref[0, #id141673352781342501524143644] validation status: true
I have no idea why the digest for reference id14167335278088961501144300
is not validating. Can anyone shed some light on what I am doing wrong?
Note: I am loading the XSD from a URL in this example so I don't have to include 4 XSD files in my question. However, because of this, the program can take a minute to run. I know this slowdown can be eliminated with local XSD files, it just is not feasible to do that way with the posted code.
XMLDSigVerifier.java:
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import javax.xml.XMLConstants;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.dsig.Reference;
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.test.dsig.X509KeySelector;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import sun.security.provider.X509Factory;
public class XMLDSigVerifier {
public static void main (String[] args) throws ParserConfigurationException, SAXException, IOException, KeyStoreException, MarshalException, XMLSignatureException, NoSuchAlgorithmException, CertificateException {
//Get XML as a string, will be parameter in final version
String sigString = new String(Files.readAllBytes(Paths.get("src/signature.xml")), StandardCharsets.UTF_8);
InputSource is = new InputSource();
is.setCharacterStream(new StringReader(sigString));
//Get X509Certificate as a string, will be parameter in final version
String samlCertString= "-----BEGIN CERTIFICATE-----MIIDpDCCAoygAwIBAgIGAVjUwdcaMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi02NjI2ODExHDAaBgkqhkiG9w0BCQEWDWluZm9Ab2t0YS5jb20wHhcNMTYxMjA2MTUyOTIzWhcNMjYxMjA2MTUzMDIzWjCBkjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtNjYyNjgxMRwwGgYJKoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvLvIOJ11216IzuqZAbTiAoJy6WYxWuGXeUn4rpYkXLGOO0OoDahzhyquCQgLQC8mGlxCAq8gETQcdL+SX7lOlavHcNYiaYUD9IipMV0Kqt8TgfLO8UuYLb2jNNaQp+0tbcYv4SHpC4nXTndlo2nk3cJVELvXYfvjqKzDvtMwACy37Vc01GZbFQXhSEfBt9J2aQzLPFzH/RxKeOjzKW0kxWgYpfP0NZPtwkHrsdZaqpaR+039v5bckVSQvs0ZMz1Ionv+keWzM6YpQg7sF/OsvN05u0tkrGYDq1BoM9yH1h11bXrVhLvdOjo4bSVjeiAZ2LNOTurGO4JfH+CqT15c5wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQC8jLBoAM35/6pwtUJ86BhYKvtjB6t3k+5uFRUx8rBWYL5atirRPF73W4f6AIkIp36zkS0os0RHuFK0bG2FPnjQj+FpErd8zji8PVFQ2LZ0WPNLYP7g7BWxAoNct2q0Iw3TACY6h722Cq6WS9ZP4O2iv3kkpo4A7JZvuf4yGGY2nVfx5nLZAmcEA9bZmHhcgmPLs2FBYpLYPs/5P4nd2HeiTJW+F6M75g9E4wG+sf3q2zqzh+AmV5kHffWnGx2MPdUmyFPU80zcDzEpodVU73YUxJIJScwjjXzytuQSB2FcM0TMqwYUq2qGVAPhBw4nnAxeScbMyMbFVDrNyMeejDhq-----END CERTIFICATE-----";
X509Certificate samlCert;
samlCert = parseCertificate(samlCertString);
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry("a", samlCert);
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
// File xsd = new File("src/saml-schema-protocol-2.0.xsd");
// Schema schema = schemaFactory.newSchema(xsd);
Schema schema = schemaFactory.newSchema(new URL("http://docs.oasis-open.org/security/saml/v2.0/saml-schema-protocol-2.0.xsd"));
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setSchema(schema);
DocumentBuilder builder = dbf.newDocumentBuilder();
Document doc = builder.parse(is);
NodeList signatureNodeList = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
Node signatureNode;
for (int sigIndex = 0; sigIndex < signatureNodeList.getLength(); sigIndex++) {
signatureNode = signatureNodeList.item(sigIndex);
if (sigIndex > 0) {
System.out.println("");
}
System.out.println("Signature " + sigIndex + ":");
DOMValidateContext valContext = new DOMValidateContext(new X509KeySelector(ks), signatureNode);
valContext.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE);
XMLSignatureFactory factory = XMLSignatureFactory.getInstance("DOM");
XMLSignature signature = factory.unmarshalXMLSignature(valContext);
boolean coreValidity = signature.validate(valContext);
//Check Validity
if (coreValidity == false) {
System.err.println("..Signature failed core validation");
try {
//Sleep because of eclipse bug
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println("..Signature passed core validation");
}
//Check validity Parts
//Validity Part 1: Check Signature Validation
boolean sv = signature.getSignatureValue().validate(valContext);
System.out.println("....signature validation status: " + sv);
//Validity Part 2: Check References
Iterator<?> i = signature.getSignedInfo().getReferences().iterator();
for (int j = 0; i.hasNext(); j++) {
Reference ref = (Reference) i.next();
boolean refValid = ref.validate(valContext);
System.out.println("....ref[" + j + ", " + ref.getURI() + "] validation status: " + refValid);
}
}
}
public static X509Certificate parseCertificate(String certStr) throws CertificateException{
byte [] decoded = Base64.decode(
certStr
.replaceAll(X509Factory.BEGIN_CERT, "")
.replaceAll(X509Factory.END_CERT, "")
);
return (X509Certificate)CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(decoded));
}
}
signature.xml
<?xml version="1.0" encoding="UTF-8"?><saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://apps.surdellpartners.com/ords/kohls_wmj/workamajig/okta/acs/" ID="id14167335278088961501144300" IssueInstant="2017-01-27T18:21:53.483Z" Version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"><saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://www.okta.com/exk8y9z9v7FSYL34Y0h7</saml2:Issuer><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference URI="#id14167335278088961501144300"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="xs"/></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>7HyoOBjYlu8fbvSMNIY4O0fc6BhrkAUaPrF9EYWq/wE=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>dOhPiIwGvLJac40XW4x5Xn5soIzImitr/HAxRojDwSbAbfRp9t/VuRFT2Rat5oGgV3tWHedN7VBNpSGzBfZsYMBB/s/WYH0EQisTuS8i