--- /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; + } + +}