f0aebc8141
Reviewed-by: mullan
241 lines
9.8 KiB
Java
241 lines
9.8 KiB
Java
/*
|
|
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
/**
|
|
* @test
|
|
* @bug 8305972
|
|
* @summary Demonstrate here() support for validating XML Signatures
|
|
* @modules java.base/sun.security.util
|
|
* java.base/sun.security.x509
|
|
* java.xml.crypto/org.jcp.xml.dsig.internal.dom
|
|
* @library /test/lib
|
|
* @compile -XDignore.symbol.file KeySelectors.java SignatureValidator.java
|
|
* X509KeySelector.java ValidationTests.java
|
|
* @run main/othervm HereFunction default true
|
|
* @run main/othervm HereFunction true true
|
|
* @run main/othervm HereFunction false false
|
|
*/
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.security.*;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
import javax.xml.crypto.Data;
|
|
import javax.xml.crypto.KeySelector;
|
|
import javax.xml.crypto.OctetStreamData;
|
|
import javax.xml.crypto.URIDereferencer;
|
|
import javax.xml.crypto.URIReference;
|
|
import javax.xml.crypto.URIReferenceException;
|
|
import javax.xml.crypto.XMLCryptoContext;
|
|
import javax.xml.crypto.dsig.*;
|
|
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.spec.C14NMethodParameterSpec;
|
|
import javax.xml.crypto.dsig.spec.XPathFilterParameterSpec;
|
|
import javax.xml.parsers.DocumentBuilderFactory;
|
|
|
|
import jdk.test.lib.Asserts;
|
|
import jdk.test.lib.Utils;
|
|
import jdk.test.lib.security.SecurityUtils;
|
|
import org.w3c.dom.Document;
|
|
import org.w3c.dom.Element;
|
|
import org.w3c.dom.NodeList;
|
|
|
|
public class HereFunction {
|
|
|
|
private final static String DIR = System.getProperty("test.src", ".");
|
|
private final static String DATA_DIR =
|
|
DIR + System.getProperty("file.separator") + "data";
|
|
private final static String KEYSTORE_VERIFY =
|
|
DATA_DIR + System.getProperty("file.separator") + "certs" +
|
|
System.getProperty("file.separator") + "xmldsig.jks";
|
|
private final static String KEYSTORE_SIGN =
|
|
DATA_DIR + System.getProperty("file.separator") + "certs" +
|
|
System.getProperty("file.separator") + "test.jks";
|
|
private final static String STYLESHEET =
|
|
"http://www.w3.org/TR/xml-stylesheet";
|
|
private final static String STYLESHEET_B64 =
|
|
"http://www.w3.org/Signature/2002/04/xml-stylesheet.b64";
|
|
private final static char[] PASS = "changeit".toCharArray();
|
|
|
|
public static void main(String args[]) throws Throwable {
|
|
if (!args[0].equals("default")) {
|
|
Security.setProperty("jdk.xml.dsig.hereFunctionSupported", args[0]);
|
|
}
|
|
// Re-enable sha1 algs
|
|
SecurityUtils.removeAlgsFromDSigPolicy("sha1");
|
|
|
|
boolean expected = Boolean.parseBoolean(args[1]);
|
|
|
|
sign(expected);
|
|
|
|
// Validating an old signature signed by JDK < 21
|
|
validate(expected);
|
|
}
|
|
|
|
static void validate(boolean expected) throws Exception {
|
|
SignatureValidator validator = new SignatureValidator(new File(DATA_DIR));
|
|
|
|
KeyStore keystore = KeyStore.getInstance(new File(KEYSTORE_VERIFY), PASS);
|
|
KeySelector ks = new X509KeySelector(keystore, false);
|
|
|
|
if (expected) {
|
|
Asserts.assertTrue(validator.validate(
|
|
"signature.xml", ks, new HttpURIDereferencer(), false));
|
|
} else {
|
|
Utils.runAndCheckException(() -> validator.validate(
|
|
"signature.xml", ks, new HttpURIDereferencer(), false),
|
|
XMLSignatureException.class);
|
|
}
|
|
}
|
|
|
|
static void sign(boolean expected) throws Exception {
|
|
XMLSignatureFactory fac = XMLSignatureFactory.getInstance();
|
|
DigestMethod sha1 = fac.newDigestMethod(DigestMethod.SHA1, null);
|
|
CanonicalizationMethod withoutComments = fac.newCanonicalizationMethod
|
|
(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec)null);
|
|
SignatureMethod dsaSha1 = fac.newSignatureMethod(SignatureMethod.DSA_SHA1, null);
|
|
KeyInfoFactory kifac = fac.getKeyInfoFactory();
|
|
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
|
dbf.setNamespaceAware(true);
|
|
|
|
String ENVELOPE =
|
|
DATA_DIR + System.getProperty("file.separator") + "envelope.xml";
|
|
|
|
var ks = KeyStore.getInstance(new File(KEYSTORE_SIGN), PASS);
|
|
var signingKey = ks.getKey("user", PASS);
|
|
var signingCert = ks.getCertificate("user");
|
|
|
|
// create references
|
|
List<Reference> refs = new ArrayList<>();
|
|
|
|
// Reference 1
|
|
refs.add(fac.newReference(STYLESHEET, sha1));
|
|
|
|
// Reference 2
|
|
String expr = "\n"
|
|
+ " ancestor-or-self::dsig:SignedInfo " + "\n"
|
|
+ " and " + "\n"
|
|
+ " count(ancestor-or-self::dsig:Reference | " + "\n"
|
|
+ " here()/ancestor::dsig:Reference[1]) > " + "\n"
|
|
+ " count(ancestor-or-self::dsig:Reference) " + "\n"
|
|
+ " or " + "\n"
|
|
+ " count(ancestor-or-self::node() | " + "\n"
|
|
+ " id('notaries')) = " + "\n"
|
|
+ " count(ancestor-or-self::node()) " + "\n";
|
|
|
|
XPathFilterParameterSpec xfp = new XPathFilterParameterSpec(expr,
|
|
Collections.singletonMap("dsig", XMLSignature.XMLNS));
|
|
refs.add(fac.newReference("", sha1, Collections.singletonList
|
|
(fac.newTransform(Transform.XPATH, xfp)),
|
|
XMLObject.TYPE, null));
|
|
|
|
// create SignedInfo
|
|
SignedInfo si = fac.newSignedInfo(withoutComments, dsaSha1, refs);
|
|
|
|
// create keyinfo
|
|
KeyInfo ki = kifac.newKeyInfo(List.of(
|
|
kifac.newX509Data(List.of(signingCert))), null);
|
|
|
|
// create XMLSignature
|
|
XMLSignature sig = fac.newXMLSignature(si, ki, null, "signature", null);
|
|
|
|
dbf.setValidating(false);
|
|
Document envDoc = dbf.newDocumentBuilder()
|
|
.parse(new FileInputStream(ENVELOPE));
|
|
Element ys = (Element)
|
|
envDoc.getElementsByTagName("YoursSincerely").item(0);
|
|
|
|
DOMSignContext dsc = new DOMSignContext(signingKey, ys);
|
|
dsc.setURIDereferencer(new HttpURIDereferencer());
|
|
|
|
if (expected) {
|
|
sig.sign(dsc);
|
|
} else {
|
|
Utils.runAndCheckException(
|
|
() -> sig.sign(dsc), XMLSignatureException.class);
|
|
return; // Signing fails, no need to validate
|
|
}
|
|
|
|
// StringWriter sw = new StringWriter();
|
|
// dumpDocument(envDoc, sw);
|
|
|
|
NodeList nl =
|
|
envDoc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
|
|
if (nl.getLength() == 0) {
|
|
throw new Exception("Couldn't find signature Element");
|
|
}
|
|
Element sigElement = (Element) nl.item(0);
|
|
|
|
DOMValidateContext dvc = new DOMValidateContext
|
|
(new X509KeySelector(ks), sigElement);
|
|
dvc.setURIDereferencer(new HttpURIDereferencer());
|
|
File f = new File(
|
|
System.getProperty("dir.test.vector.baltimore") +
|
|
System.getProperty("file.separator") +
|
|
"merlin-xmldsig-twenty-three" +
|
|
System.getProperty("file.separator"));
|
|
dvc.setBaseURI(f.toURI().toString());
|
|
|
|
XMLSignature sig2 = fac.unmarshalXMLSignature(dvc);
|
|
|
|
if (sig.equals(sig2) == false) {
|
|
throw new Exception
|
|
("Unmarshalled signature is not equal to generated signature");
|
|
}
|
|
if (sig2.validate(dvc) == false) {
|
|
throw new Exception("Validation of generated signature failed");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This URIDereferencer returns locally cached copies of http content to
|
|
* avoid test failures due to network glitches, etc.
|
|
*/
|
|
private static class HttpURIDereferencer implements URIDereferencer {
|
|
private final URIDereferencer defaultUd;
|
|
|
|
HttpURIDereferencer() {
|
|
defaultUd = XMLSignatureFactory.getInstance().getURIDereferencer();
|
|
}
|
|
|
|
public Data dereference(final URIReference ref, XMLCryptoContext ctx)
|
|
throws URIReferenceException {
|
|
String uri = ref.getURI();
|
|
if (uri.equals(STYLESHEET) || uri.equals(STYLESHEET_B64)) {
|
|
try {
|
|
FileInputStream fis = new FileInputStream(new File
|
|
(DATA_DIR, uri.substring(uri.lastIndexOf('/'))));
|
|
return new OctetStreamData(fis,ref.getURI(),ref.getType());
|
|
} catch (Exception e) { throw new URIReferenceException(e); }
|
|
}
|
|
|
|
// fallback on builtin deref
|
|
return defaultUd.dereference(ref, ctx);
|
|
}
|
|
}
|
|
}
|