--- /dev/null 2015-05-12 13:48:32.071779604 +0300
+++ new/test/javax/xml/crypto/dsig/Detached.java 2015-05-12 18:27:06.903520634 +0300
@@ -0,0 +1,564 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Collections;
+import java.util.List;
+import javax.xml.crypto.dsig.CanonicalizationMethod;
+import javax.xml.crypto.dsig.DigestMethod;
+import javax.xml.crypto.dsig.SignatureMethod;
+import javax.xml.crypto.dsig.SignedInfo;
+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.Node;
+import org.w3c.dom.NodeList;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import java.io.ByteArrayInputStream;
+import java.io.StringWriter;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.Key;
+import java.security.KeyException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Arrays;
+import javax.crypto.KeyGenerator;
+import javax.xml.crypto.MarshalException;
+import javax.xml.crypto.dom.DOMStructure;
+import javax.xml.crypto.dsig.Transform;
+import javax.xml.crypto.dsig.XMLSignatureException;
+import javax.xml.crypto.dsig.spec.XPathFilter2ParameterSpec;
+import javax.xml.crypto.dsig.spec.XPathFilterParameterSpec;
+import javax.xml.crypto.dsig.spec.XPathType;
+import javax.xml.crypto.dsig.spec.XSLTTransformParameterSpec;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import org.xml.sax.SAXException;
+
+/**
+ * @test
+ * @bug 8074784
+ * @summary Tests for generating detached XML Signatures
+ * @modules jdk.httpserver/com.sun.net.httpserver
+ */
+public class Detached {
+
+ private static final String BOGUS = "bogus";
+
+ private static final String[] canonicalizationMethods = new String[] {
+ CanonicalizationMethod.EXCLUSIVE,
+ CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS,
+ CanonicalizationMethod.INCLUSIVE,
+ CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS
+ };
+
+ private static final String[] xml_transforms = new String[] {
+ Transform.XSLT,
+ Transform.XPATH,
+ Transform.XPATH2,
+ CanonicalizationMethod.EXCLUSIVE,
+ CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS,
+ CanonicalizationMethod.INCLUSIVE,
+ CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS,
+ };
+
+ private static final String[] non_xml_transforms = new String[] {
+ null, Transform.BASE64
+ };
+
+ private static final String[] signatureMethods = new String[] {
+ SignatureMethod.DSA_SHA1,
+ SignatureMethod.RSA_SHA1,
+ SignatureMethod.HMAC_SHA1
+ };
+
+ private static final String XSLT = ""
+ + "\n"
+ + " \n"
+ + " \n"
+ + " Test\n"
+ + " \n"
+ + "\n";
+
+ private static enum KeyInfoType {
+ KeyValue, x509data, KeyName
+ }
+
+ private static enum Content {
+ Xml, Text, Base64, NotExisitng
+ }
+
+ private static class Test {
+
+ final String digestMethod = DigestMethod.SHA1;
+ final String canonicalizationMethod;
+ final String signatureMethod;
+ final String transform;
+ final KeyInfoType keyInfo;
+ final Class expectedException;
+ final boolean expectedFailure;
+ final Content contentType;
+
+ // The test stores a signed document here for further validation
+ String signature;
+
+ // The test stores a public key here fot further validation
+ Key validationKey;
+
+ Test(String canonicalizationMethod, String signatueMethod,
+ String transform, KeyInfoType keyInfo, Content contentType,
+ boolean expectedFailure, Class expectedException) {
+ this.canonicalizationMethod = canonicalizationMethod;
+ this.signatureMethod = signatueMethod;
+ this.transform = transform;
+ this.keyInfo = keyInfo;
+ this.expectedException = expectedException;
+ this.expectedFailure = expectedFailure;
+ this.contentType = contentType;
+ }
+
+ void print() {
+ System.out.println("Test case:");
+ System.out.println(" Canonicalization method: "
+ + canonicalizationMethod);
+ System.out.println(" Signature method: "
+ + signatureMethod);
+ System.out.println(" Transform: " + transform);
+ System.out.println(" Digest method: " + digestMethod);
+ System.out.println(" KeyInfoType: " + keyInfo);
+ System.out.println(" Content type: " + contentType);
+ System.out.println(" Expected failure: "
+ + (expectedFailure ? "yes" : "no"));
+ System.out.println(" Expected exception: "
+ + (expectedException == null ?
+ "no" : expectedException.getName()));
+ }
+ }
+
+ private static class PositiveTest extends Test {
+
+ PositiveTest(String canonicalizationMethod, String signatueMethod,
+ String transform, KeyInfoType keyInfo, Content contentType) {
+ super(canonicalizationMethod, signatueMethod, transform, keyInfo,
+ contentType, false, null);
+ }
+ }
+
+ private static class Http implements HttpHandler, AutoCloseable {
+
+ private final HttpServer server;
+
+ private Http(HttpServer server) {
+ this.server = server;
+ }
+
+ public static Http createHttpServer() throws IOException {
+ HttpServer server = HttpServer.create(new InetSocketAddress(0), 0);
+ return new Http(server);
+ }
+
+ public void start() {
+ server.createContext("/", this);
+ server.start();
+ }
+
+ public void stop() {
+ server.stop(0);
+ }
+
+ public int getPort() {
+ return server.getAddress().getPort();
+ }
+
+ @Override
+ public void handle(HttpExchange t) throws IOException {
+ try {
+ String type;
+ String path = t.getRequestURI().getPath();
+ if (path.startsWith("/")) {
+ type = path.substring(1);
+ } else {
+ type = path;
+ }
+
+ String contentTypeHeader = "";
+ byte[] output = new byte[] {};
+ int code = 200;
+ Content testContentType = Content.valueOf(type);
+ switch (testContentType) {
+ case Base64:
+ contentTypeHeader = "application/octet-stream";
+ output = "VGVzdA==".getBytes();
+ break;
+ case Text:
+ contentTypeHeader = "text/plain";
+ output = "Text".getBytes();
+ break;
+ case Xml:
+ contentTypeHeader = "application/xml";
+ output = "test".getBytes();
+ break;
+ case NotExisitng:
+ code = 404;
+ break;
+ default:
+ throw new IOException("Unknown test content type");
+ }
+
+ t.getResponseHeaders().set("Content-Type", contentTypeHeader);
+ t.sendResponseHeaders(code, output.length);
+ t.getResponseBody().write(output);
+ } catch (IOException e) {
+ System.out.println("Exception: " + e);
+ t.sendResponseHeaders(500, 0);
+ }
+ t.close();
+ }
+
+ @Override
+ public void close() {
+ stop();
+ }
+ }
+
+ public static void main(String[] args) throws IOException {
+ try (Http server = Http.createHttpServer()) {
+ server.start();
+
+ List tests = new ArrayList<>();
+
+ // Tests for XML documents
+ Arrays.stream(canonicalizationMethods).forEach(c ->
+ Arrays.stream(signatureMethods).forEach(s ->
+ Arrays.stream(xml_transforms).forEach(t ->
+ Arrays.stream(KeyInfoType.values()).forEach(k -> {
+ tests.add(new PositiveTest(c, s, t, k,
+ Content.Xml));
+ }))));
+
+ // Tests for text data with no transform
+ Arrays.stream(canonicalizationMethods).forEach(c ->
+ Arrays.stream(signatureMethods).forEach(s ->
+ Arrays.stream(KeyInfoType.values()).forEach(k -> {
+ tests.add(new PositiveTest(c, s, null, k,
+ Content.Text));
+ })));
+
+ // Tests for base64 data
+ Arrays.stream(canonicalizationMethods).forEach(c ->
+ Arrays.stream(signatureMethods).forEach(s ->
+ Arrays.stream(non_xml_transforms).forEach(t ->
+ Arrays.stream(KeyInfoType.values()).forEach(k -> {
+ tests.add(new PositiveTest(c, s, t, k,
+ Content.Base64));
+ }))));
+
+ // Negative tests
+
+ // unknown CanonicalizationMethod
+ tests.add(new Test(CanonicalizationMethod.EXCLUSIVE + BOGUS,
+ SignatureMethod.DSA_SHA1,
+ CanonicalizationMethod.INCLUSIVE,
+ KeyInfoType.KeyName, Content.Xml,
+ true, NoSuchAlgorithmException.class));
+
+ // unknown SignatureMethod
+ tests.add(new Test(CanonicalizationMethod.EXCLUSIVE,
+ SignatureMethod.DSA_SHA1 + BOGUS,
+ CanonicalizationMethod.INCLUSIVE,
+ KeyInfoType.KeyName, Content.Xml,
+ true, NoSuchAlgorithmException.class));
+
+ // unknown Transform
+ tests.add(new Test(CanonicalizationMethod.EXCLUSIVE,
+ SignatureMethod.DSA_SHA1,
+ CanonicalizationMethod.INCLUSIVE + BOGUS,
+ KeyInfoType.KeyName, Content.Xml,
+ true, NoSuchAlgorithmException.class));
+
+ // no source document
+ tests.add(new Test(CanonicalizationMethod.EXCLUSIVE,
+ SignatureMethod.DSA_SHA1,
+ CanonicalizationMethod.INCLUSIVE,
+ KeyInfoType.KeyName, Content.NotExisitng,
+ true, XMLSignatureException.class));
+
+ // wrong transfrom for text data
+ tests.add(new Test(CanonicalizationMethod.EXCLUSIVE,
+ SignatureMethod.DSA_SHA1,
+ CanonicalizationMethod.INCLUSIVE,
+ KeyInfoType.KeyName, Content.Text,
+ true, XMLSignatureException.class));
+
+ boolean success = tests.stream().allMatch(
+ (test) -> runTest(test, server.getPort()));
+
+ if (!success) {
+ throw new RuntimeException("Some test cases failed");
+ }
+
+ System.out.println("Test passed");
+ }
+
+ }
+
+ static KeyPair getKeyPair(SignatureMethod sm)
+ throws NoSuchAlgorithmException {
+ KeyPairGenerator keygen;
+ switch (sm.getAlgorithm()) {
+ case SignatureMethod.DSA_SHA1:
+ keygen = KeyPairGenerator.getInstance("DSA");
+ break;
+ case SignatureMethod.RSA_SHA1:
+ keygen = KeyPairGenerator.getInstance("RSA");
+ break;
+ default:
+ throw new RuntimeException("Unsupported signature algorithm");
+ }
+
+ SecureRandom random = new SecureRandom();
+ keygen.initialize(1024, random);
+ return keygen.generateKeyPair();
+ }
+
+ static void createSignature(Test test, int port) throws IOException,
+ NoSuchAlgorithmException, InvalidAlgorithmParameterException,
+ KeyException, ParserConfigurationException, MarshalException,
+ XMLSignatureException, TransformerConfigurationException,
+ TransformerException, SAXException {
+
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ dbf.setNamespaceAware(true);
+
+ XMLSignatureFactory fac = XMLSignatureFactory.getInstance();
+
+ // Create SignedInfo
+ DigestMethod dm = fac.newDigestMethod(test.digestMethod, null);
+
+ List transformList = null;
+ if (test.transform != null) {
+ TransformParameterSpec params = null;
+ switch (test.transform) {
+ case Transform.XPATH:
+ params = new XPathFilterParameterSpec("//.");
+ break;
+ case Transform.XPATH2:
+ params = new XPathFilter2ParameterSpec(
+ Collections.singletonList(new XPathType("//.",
+ XPathType.Filter.INTERSECT)));
+ break;
+ case Transform.XSLT:
+ Element element = dbf.newDocumentBuilder()
+ .parse(new ByteArrayInputStream(XSLT.getBytes()))
+ .getDocumentElement();
+ DOMStructure stylesheet = new DOMStructure(element);
+ params = new XSLTTransformParameterSpec(stylesheet);
+ break;
+ }
+ transformList = Collections.singletonList(fac.newTransform(
+ test.transform, params));
+ }
+
+ String url = String.format("http://localhost:%d/%s", port,
+ test.contentType);
+ List refs = Collections.singletonList(fac.newReference(url, dm,
+ transformList, null, null));
+
+ CanonicalizationMethod cm = fac.newCanonicalizationMethod(
+ test.canonicalizationMethod, (C14NMethodParameterSpec) null);
+
+ SignatureMethod sm = fac.newSignatureMethod(test.signatureMethod, null);
+
+ // Generate keys and save a key for validaition in Test instance,
+ // it will be used for signature validation
+ Key signingKey;
+ switch (test.signatureMethod) {
+ case SignatureMethod.DSA_SHA1:
+ case SignatureMethod.RSA_SHA1:
+ KeyPair kp = getKeyPair(sm);
+ test.validationKey = kp.getPublic();
+ signingKey = kp.getPrivate();
+ break;
+ case SignatureMethod.HMAC_SHA1:
+ KeyGenerator kg = KeyGenerator.getInstance("HmacSHA1");
+ signingKey = kg.generateKey();
+ test.validationKey = signingKey;
+ break;
+ default:
+ throw new RuntimeException("Unsupported signature algorithm");
+ }
+
+
+ SignedInfo si = fac.newSignedInfo(cm, sm, refs, null);
+
+ // Create KeyInfo
+ KeyInfoFactory kif = fac.getKeyInfoFactory();
+ List list = null;
+ if (test.keyInfo == KeyInfoType.KeyValue) {
+ if (test.validationKey instanceof PublicKey) {
+ KeyValue kv = kif.newKeyValue((PublicKey) test.validationKey);
+ list = Collections.singletonList(kv);
+ }
+ } else if (test.keyInfo == KeyInfoType.x509data) {
+ list = Collections.singletonList(
+ kif.newX509Data(Collections.singletonList("cn=Test")));
+ } else if (test.keyInfo == KeyInfoType.KeyName) {
+ list = Collections.singletonList(kif.newKeyName("Test"));
+ } else {
+ throw new RuntimeException("Unexpected KeyInfo: " + test.keyInfo);
+ }
+ KeyInfo ki = list != null ? kif.newKeyInfo(list) : null;
+
+ // Create an empty doc for detached signature
+ Document doc = dbf.newDocumentBuilder().newDocument();
+ DOMSignContext xsc = new DOMSignContext(signingKey, doc);
+
+ // Generate signature
+ XMLSignature signature = fac.newXMLSignature(si, ki);
+ signature.sign(xsc);
+
+ // Save signature
+ try (StringWriter writer = new StringWriter()) {
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Transformer trans = tf.newTransformer();
+ Node parent = xsc.getParent();
+ trans.transform(new DOMSource(parent), new StreamResult(writer));
+ test.signature = writer.toString();
+ }
+ }
+
+ static boolean validateDsig(Test test) throws ParserConfigurationException,
+ SAXException, IOException, MarshalException, XMLSignatureException {
+
+ XMLSignatureFactory fac = XMLSignatureFactory.getInstance();
+
+ // Load signature
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ dbf.setNamespaceAware(true);
+ dbf.setValidating(false);
+
+ Document doc;
+ try (ByteArrayInputStream bis = new ByteArrayInputStream(
+ test.signature.getBytes())) {
+ doc = dbf.newDocumentBuilder().parse(bis);
+ }
+
+ NodeList nodeLst = doc.getElementsByTagName("Signature");
+ Node node = nodeLst.item(0);
+ if (node == null) {
+ throw new RuntimeException("Couldn't find Signature element");
+ }
+ if (!(node instanceof Element)) {
+ throw new RuntimeException("Unexpected node type");
+ }
+ Element sig = (Element) node;
+
+ // Validate signature
+ DOMValidateContext vc = new DOMValidateContext(test.validationKey, sig);
+ vc.setProperty("org.jcp.xml.dsig.secureValidation", Boolean.FALSE);
+ XMLSignature signature = fac.unmarshalXMLSignature(vc);
+
+ boolean success = signature.validate(vc);
+ if (!success) {
+ System.out.println("Core signature validation failed");
+ return false;
+ }
+
+ success = signature.getSignatureValue().validate(vc);
+ if (!success) {
+ System.out.println("Cryptographic validation of signature failed");
+ return false;
+ }
+
+ return true;
+ }
+
+ static boolean runTest(Test test, int port) {
+ test.print();
+ try {
+ System.out.print("Sign ... ");
+ createSignature(test, port);
+ System.out.println("Done");
+
+ System.out.print("Validate ... ");
+ boolean success = validateDsig(test);
+
+ if (success && test.expectedFailure) {
+ System.out.println("Signature validation unexpectedly passed");
+ return false;
+ }
+
+ if (!success && !test.expectedFailure) {
+ System.out.println("Signature validation unexpectedly failed");
+ return false;
+ }
+
+ System.out.println("Done");
+
+ if (test.expectedException != null) {
+ System.out.println("Expected " + test.expectedException
+ + " not thrown");
+ return false;
+ }
+ } catch (Exception e) {
+ if (test.expectedException == null
+ || !e.getClass().isAssignableFrom(test.expectedException)) {
+ System.out.println("Unexpected exception: " + e);
+ e.printStackTrace(System.out);
+ return false;
+ }
+
+ System.out.println("Expected exception: " + e);
+ }
+
+ System.out.println("Test case passed");
+ return true;
+ }
+
+}