--- /dev/null 2017-06-09 15:51:21.000000000 +0800 +++ new/test/sun/security/tools/jarsigner/compatibility/Compatibility.java 2017-06-09 15:51:21.000000000 +0800 @@ -0,0 +1,886 @@ +/* + * Copyright (c) 2017, 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 8177674 + * @summary This test is used to verify the compatibility on jarsigner cross + * different JDK releases. If property strict is true, it also checks whether + * the default timestamp digest algorithm is SHA-256, and all of verification + * must pass even if the JDK build doesn't support some specific algorithms. + * + * The test will generate a report, at JTwork/scratch/testReport, to display + * the key parameters for signing and the status of signing and verifying. + * + * Please note that, the test may output a great deal of logs if the jdk list + * is big, and that would lead to jtreg output overflow. So, it redirects + * the stdout and stderr to file JTwork/scratch/test.out. + * + * The testing JDK, which is specified by jtreg option "-jdk", should include + * the fix for JDK-8163304. Otherwise, the signature algorithm and timestamp + * digest algorithm cannot be extracted from verification output. And this + * JDK should support as many as possible signature algorithms. So, the latest + * JDK build is recommended. + * + * Usage: jtreg [-options] \ + * [-Dstrict= \ + * -DproxyHost= -DproxyPort= \ + * -Dtsa= \ + * -DjdkListFile= \ + * -DjdkList= \ + * -DjavaSecurityFile=] \ + * /path/to/Compatibility.java + * + * Properties: + * 1. strict= + * If true, the test checks whether the default timestamp digest + * algorithm is SHA-256, and all of verification must pass even if + * the JDK build doesn't support a specific algorithm. The default + * value is true. + * + * 2. proxyHost= + * This property indicates proxy host. + * + * 3. proxyPort= + * This property indicates proxy port. The default value is 80. + * + * 4. tsa= + * This property indicates a TSA service. It is mandatory. + * + * 5. jdkListFile= + * This property indicates a local file, which contains a set of local + * JDK paths. The style of the file content looks like the below, + * /path/to/jdk1 + * /path/to/jdk2 + * /path/to/jdk3 + * ... + * + * 6. jdkList= + * This property directly lists a set of local JDK paths in command. + * Note that, if both of jdkListFile and jdkList are specified, only + * property jdkListFile is selected. If neither of jdkListFile and + * jdkList is specified, the testing JDK, which is specified by jtreg + * option -jdk will be used as the only one JDK in the JDK list. + * + * 7. javaSecurityFile= + * This property indicates an alternative java security properties + * file. The default value is the path of file java.scurity that is + * distributed with this test. + * + * Report columns: + * 1. Signer JDK: The JDK version that signs jar. + * 2. Signature Algorithm: The signature algorithm used by signing. + * 3. TSA Digest Algorithm: The timestamp digest algorithm used by signing. + * 4. Certificate: Certificate identifier. + * The identifier consists of specific attributes of the certificate. + * The naming convention is: + * KeyAlgorithm_DigestAlgorithm_KeySize[_Expired] + * 5. Status of Signing: Signing process result status. + * The status are the followings: + * [1]Normal, no any error and warning. + * [2]Warn, no any error but some warnings raise. + * [3]Error, some errors raise. + * 6. Verifier JDK: The JDK version that verifies signed jars. + * 7. Status of Verifying: Verifying process result status. The status + * are the same as those for "Status of Signing". + * 8. Failed: It highlights which case fails. The failed cases (rows) + * are marked with letter X. + * + * @modules java.base/sun.security.pkcs + * java.base/sun.security.timestamp + * java.base/sun.security.tools.keytool + * java.base/sun.security.util + * java.base/sun.security.x509 + * @library /test/lib /lib/testlibrary ../warnings + * @compile -source 1.6 -target 1.6 JdkUtils.java + * @run main/manual/othervm Compatibility + */ + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.util.JarUtils; + +public class Compatibility { + + private static final String TEST_JAR_NAME = "test.jar"; + + private static final String TEST_SRC = System.getProperty("test.src"); + private static final String TEST_CLASSES = System.getProperty("test.classes"); + private static final String TEST_JDK = System.getProperty("test.jdk"); + private static final String TEST_JARSIGNER = jarsignerPath(TEST_JDK); + + // If true, the test has to check whether the default timestamp digest + // algorithm is SHA-256. And it doesn't allow any verification failure even + // if the jarsigner doesn't support a specific algorithm. + private static final boolean STRICT = Boolean.parseBoolean( + System.getProperty("strict", "true")); + + private static final String PROXY_HOST = System.getProperty("proxyHost"); + private static final String PROXY_PORT = System.getProperty("proxyPort", "80"); + private static final String TSA = System.getProperty("tsa"); + + // An alternative security properties file. + // The test provides a default one, which only contains two lines: + // jdk.certpath.disabledAlgorithms=MD2, MD5 + // jdk.jar.disabledAlgorithms=MD2, MD5 + private static final String JAVA_SECURITY = System.getProperty( + "javaSecurityFile", TEST_SRC + "/java.security"); + + private static final String PASSWORD = "testpass"; + private static final String KEYSTORE = "testKeystore"; + + private static final String DEFAULT = "DEFAULT"; + private static final String RSA = "RSA"; + private static final String DSA = "DSA"; + private static final String EC = "EC"; + private static final String[] KEY_ALGORITHMS = new String[] { + RSA, + DSA, + EC}; + + private static final String SHA1 = "SHA-1"; + private static final String SHA256 = "SHA-256"; + private static final String SHA512 = "SHA-512"; + private static final String[] DIGEST_ALGORITHMS = new String[] { + SHA1, + SHA256, + SHA512, + DEFAULT}; + + private static final boolean[] EXPIRED = new boolean[] { + false, + true}; + + public static void main(String[] args) throws Throwable { + if (TSA == null || TSA.isEmpty()) { + throw new RuntimeException("TSA service is mandatory."); + } + + // Redirects the output to a file, named test.out. + PrintStream out = new PrintStream( + new FileOutputStream("test.out", true)); + System.setOut(out); + System.setErr(out); + + createJar(); + List reportItems = test(jdkList(), createCertificates()); + boolean failed = generateReport(reportItems); + + if (failed) { + throw new RuntimeException("Test failed. " + + "Please check the failed row(s) in testReport " + + "and more details in test.out."); + } + } + + // Creates a jar file that contains an empty file. + private static void createJar() throws IOException { + String testFile = "test"; + new File(testFile).createNewFile(); + JarUtils.createJar(TEST_JAR_NAME, testFile); + } + + // Creates a key store that includes a set of (expired) certificates with + // various algorithms. + private static Map createCertificates() throws Throwable { + Map certs = new HashMap(); + + for(String keyAlgorithm : KEY_ALGORITHMS) { + for(String digestAlgorithm : DIGEST_ALGORITHMS) { + if (DEFAULT.equals(digestAlgorithm)) { + continue; + } + + for(int keySize : keySizes(keyAlgorithm)) { + for(boolean expired : EXPIRED) { + CertInfo certInfo = new CertInfo( + keyAlgorithm, + digestAlgorithm, + keySize, + expired); + String alias = createCertificate(certInfo); + certs.put(certInfo, alias); + } + } + } + } + + return certs; + } + + // Creates/Updates a key store that adds a certificate with specific algorithm. + private static String createCertificate(CertInfo certInfo) throws Throwable { + String alias = certInfo.toString(); + + OutputAnalyzer outputAnalyzer = execTool( + TEST_JDK + "/bin/keytool", + "-J-Djava.security.properties=" + JAVA_SECURITY, + "-v", + "-storetype", "jks", + "-genkey", + "-keyalg", certInfo.keyAlgorithm, + "-sigalg", sigalg(certInfo.digestAlgorithm, certInfo.keyAlgorithm), + "-keysize", certInfo.keySize + "", + "-dname", "CN=" + alias, + "-alias", alias, + "-keypass", PASSWORD, + "-storepass", PASSWORD, + "-startdate", "-2d", + "-validity", certInfo.expired ? "1" : "3650", + "-keystore", KEYSTORE); + if (outputAnalyzer.getExitValue() == 0 + && !outputAnalyzer.getOutput().matches("[Ee]xception")) { + return alias; + } else { + return null; + } + } + + private static String sigalg(String digestAlgorithm, String keyAlgorithm) { + if (digestAlgorithm == DEFAULT) { + return null; + } + + String keyName = EC.equals(keyAlgorithm) ? "ECDSA" : keyAlgorithm; + return digestAlgorithm.replace("-", "") + "with" + keyName; + } + + // Retrieves JDK paths from the file which is specified by property jdkListFile, + // or from property jdkList if jdkListFile is not available. + private static String[] jdkList() throws IOException { + String jdkListFile = System.getProperty("jdkListFile"); + if (jdkListFile != null) { + System.out.println("JDK List file: " + jdkListFile); + List jdkPaths = new ArrayList(); + BufferedReader reader = new BufferedReader( + new FileReader(jdkListFile)); + String line; + while ((line = reader.readLine()) != null) { + String jdkPath = line.trim(); + if (!jdkPath.isEmpty()) { + jdkPaths.add(jdkPath); + } + } + reader.close(); + return jdkPaths.toArray(new String[jdkPaths.size()]); + } + + String jdkList = System.getProperty("jdkList", TEST_JDK); + System.out.println("JDK List:\n" + jdkList); + String[] jdkPaths = jdkList.split(","); + return jdkPaths; + } + + // A JDK (signer) signs a jar with a variety of algorithms, and then all of + // JDKs (verifiers), including the signer itself, try to verify the signed + // jars respectively. + private static List test(String[] jdkPaths, + Map certs) throws Throwable { + List reportItems = new ArrayList(); + + for (String signerPath : jdkPaths) { + JdkInfo signerInfo = JdkInfoMap.getJdkInfo(signerPath); + + for (String keyAlgorithm : KEY_ALGORITHMS) { + for (String digestAlgorithm : DIGEST_ALGORITHMS) { + String sigalg = sigalg(digestAlgorithm, keyAlgorithm); + // If the signature algorithm is not supported by the JDK, + // it cannot try to sign jar with this algorithm. + if (sigalg != null && !signerInfo.isSupportedSigalg(sigalg)) { + continue; + } + + // If the JDK doesn't support option -tsadigestalg, the + // associated cases just be ignored. + if (digestAlgorithm != DEFAULT + && !signerInfo.supportsTsadigestalg) { + continue; + } + + for (int keySize : keySizes(keyAlgorithm)) { + for (boolean expired : EXPIRED) { + String certDigest = digestAlgorithm; + int certKeySize = keySize; + // If the digest algorithm is not specified, then it + // uses certificate with SHA256 digest and 1024 key + // size. + if (digestAlgorithm == DEFAULT) { + certDigest = SHA256; + certKeySize = 1024; + } + + CertInfo certInfo = new CertInfo( + keyAlgorithm, + certDigest, + certKeySize, + expired); + String alias = certs.get(certInfo); + if (alias == null) { + continue; + } + + String signedJarPath = signerInfo.version + "_" + + certInfo + ".jar"; + String tsadigestalg = digestAlgorithm != DEFAULT + ? digestAlgorithm : null; + + String signOutput = signJar( + signerInfo.jarsignerPath, + sigalg, + tsadigestalg, + alias, + signedJarPath); + STATUS signingStatus = signingStatus(signOutput); + + // If signing fails, the following verifying has to + // be ignored. + if (signingStatus == STATUS.ERROR) { + continue; + } + + SignItem signItem = SignItem.build() + .version(signerInfo.version) + .signatureAlgorithm(sigalg) + .tsaDigestAlgorithm(tsadigestalg) + .certInfo(certInfo) + .status(signingStatus) + .signedJarPath(signedJarPath); + + // Using the testing JDK, which is specified by jtreg + // option "-jdk", to verify the signed jar and extract + // the signature algorithm and timestamp digest algorithm. + String output = verifyJar(TEST_JARSIGNER, + signedJarPath); + signItem.extractedSignatureAlgorithm(extract(output, + " *Signature algorithm.*", ".*: |,.*")); + signItem.extractedTsaDigestAlgorithm(extract(output, + " *Timestamp digest algorithm.*", ".*: ")); + + reportItems.addAll( + verifyJarByJDKs(jdkPaths, signItem)); + } + } + } + } + } + + return reportItems; + } + + private static List verifyJarByJDKs(String[] jdkPaths, + SignItem signItem) throws Throwable { + List reportItems = new ArrayList(); + for (String verifierPath : jdkPaths) { + JdkInfo verifierInfo = JdkInfoMap.getJdkInfo(verifierPath); + // If strict is false and the verifier JDK doesn't support this + // signature algorithm, the case just be ignored. + if (!STRICT + && signItem.signatureAlgorithm != null + && !verifierInfo.isSupportedSigalg( + signItem.signatureAlgorithm)) { + continue; + } + + VerifyItem verifyItem = verifyJarByJDK(verifierInfo, signItem); + ReportItem reportItem = new ReportItem(signItem, verifyItem); + reportItems.add(reportItem); + System.out.println("Result:\n" + reportItem); + } + return reportItems; + } + + private static VerifyItem verifyJarByJDK(JdkInfo verifierInfo, + SignItem signItem) throws Throwable { + String verifyOutput = verifyJar(verifierInfo.jarsignerPath, + signItem.signedJarPath); + STATUS verifyingStatus = verifyingStatus(verifyOutput); + + // If strict is true, it has to check if the default timestamp digest + // algorithm is SHA-256. + if (verifyingStatus != STATUS.ERROR) { + verifyingStatus = (STRICT && signItem.tsaDigestAlgorithm == DEFAULT + && signItem.extractedTsaDigestAlgorithm != null + && !signItem.extractedTsaDigestAlgorithm + .matches("SHA-?256")) + ? STATUS.ERROR + : verifyingStatus; + } + + VerifyItem verifyItem = VerifyItem.build() + .version(verifierInfo.version) + .status(verifyingStatus); + return verifyItem; + } + + // Return key sizes according to the specified key algorithm. + private static int[] keySizes(String keyAlgorithm) { + if (keyAlgorithm == RSA || keyAlgorithm == DSA) { + return new int[] { 1024, 2048 }; + } else if (keyAlgorithm == EC) { + return new int[] { 384, 571 }; + } + + return null; + } + + // Determines the status of signing. + private static STATUS signingStatus(String output) { + if (output.contains(Test.JAR_SIGNED)) { + if (output.contains(Test.HAS_EXPIRED_CERT_SIGNING_WARNING)) { + return STATUS.WARNING; + } else { + return STATUS.NORMAL; + } + } else { + return STATUS.ERROR; + } + } + + // Determines the status of verifying. + private static STATUS verifyingStatus(String output) { + if (output.contains(Test.JAR_VERIFIED)) { + if (output.contains(Test.WARNING)) { + return STATUS.WARNING; + } else { + return STATUS.NORMAL; + } + } else { + return STATUS.ERROR; + } + } + + // Extracts string from text by specified patterns. + private static String extract(String text, String linePattern, + String replacePattern) { + Matcher lineMatcher = Pattern.compile(linePattern).matcher(text); + if (lineMatcher.find()) { + String line = lineMatcher.group(0); + return line.replaceAll(replacePattern, ""); + } else { + return null; + } + } + + // Using specified jarsigner to sign the pre-created jar with specified + // algorithms. + private static String signJar(String jarsignerPath, String sigalg, + String tsadigestalg, String alias, String signedJarPath) + throws Throwable { + List arguments = new ArrayList(); + if (PROXY_HOST != null && PROXY_PORT != null) { + arguments.add("-J-Dhttp.proxyHost=" + PROXY_HOST); + arguments.add("-J-Dhttp.proxyPort=" + PROXY_PORT); + arguments.add("-J-Dhttps.proxyHost=" + PROXY_HOST); + arguments.add("-J-Dhttps.proxyPort=" + PROXY_PORT); + } + arguments.add("-J-Djava.security.properties=" + JAVA_SECURITY); + arguments.add("-verbose"); + if (sigalg != null) { + arguments.add("-sigalg"); + arguments.add(sigalg); + } + arguments.add("-tsa"); + arguments.add(TSA); + if (tsadigestalg != null) { + arguments.add("-tsadigestalg"); + arguments.add(tsadigestalg); + } + arguments.add("-keystore"); + arguments.add(KEYSTORE); + arguments.add("-storepass"); + arguments.add(PASSWORD); + arguments.add("-signedjar"); + arguments.add(signedJarPath); + arguments.add(TEST_JAR_NAME); + arguments.add(alias); + + OutputAnalyzer outputAnalyzer = execTool( + jarsignerPath, + arguments.toArray(new String[arguments.size()])); + return outputAnalyzer.getOutput(); + } + + // Using specified jarsigner to verify the signed jar. + private static String verifyJar(String jarsignerPath, String signedJarPath) + throws Throwable { + OutputAnalyzer outputAnalyzer = execTool( + jarsignerPath, + "-J-Djava.security.properties=" + JAVA_SECURITY, + "-verbose", "-certs", + "-keystore", KEYSTORE, + "-verify", signedJarPath); + return outputAnalyzer.getOutput(); + } + + // Generates the test result report. + private static boolean generateReport(List reportItems) + throws IOException { + System.out.println("Report is being generated..."); + + StringBuilder report = new StringBuilder(); + + // Generates report headers. + report.append(ReportHeader.HEADERS).append("\n"); + + boolean failed = false; + + // Generates report rows. + for (ReportItem item : reportItems) { + failed = failed || item.verifyItem.status == STATUS.ERROR; + report.append(item).append("\n"); + } + + FileWriter writer = new FileWriter(new File("testReport")); + writer.write(report.toString()); + writer.close(); + + System.out.println("Report is generated."); + return failed; + } + + private static String jarsignerPath(String jdkPath) { + return jdkPath + "/bin/jarsigner"; + } + + // Executes the specified function on JdkUtils by the specified JDK. + private static String execJdkUtils(String jdkPath, String method, + String... args) throws Throwable { + String[] cmd = new String[args.length + 5]; + cmd[0] = jdkPath + "/bin/java"; + cmd[1] = "-cp"; + cmd[2] = TEST_CLASSES; + cmd[3] = JdkUtils.class.getName(); + cmd[4] = method; + System.arraycopy(args, 0, cmd, 5, args.length); + return ProcessTools.executeCommand(cmd).getOutput(); + } + + // Executes the specified JDK tools, such as keytool and jarsigner, and + // ensures the output is in US English. + private static OutputAnalyzer execTool(String toolPath, String... args) + throws Throwable { + String[] cmd = new String[args.length + 4]; + cmd[0] = toolPath; + cmd[1] = "-J-Duser.language=en"; + cmd[2] = "-J-Duser.country=US"; + cmd[3] = "-J-Djava.security.egd=file:/dev/./urandom"; + System.arraycopy(args, 0, cmd, 4, args.length); + return ProcessTools.executeCommand(cmd); + } + + private static class JdkInfoMap { + + private static Map map = new HashMap(); + + private static JdkInfo getJdkInfo(String jdkPath) throws Throwable { + if (!map.containsKey(jdkPath)) { + map.put(jdkPath, new JdkInfo(jdkPath)); + } + return map.get(jdkPath); + } + } + + private static class JdkInfo { + + private final String jdkPath; + private final String jarsignerPath; + private final String version; + private final boolean supportsTsadigestalg; + + private Map sigalgMap = new HashMap(); + + private JdkInfo(String jdkPath) throws Throwable { + this.jdkPath = jdkPath; + version = execJdkUtils(jdkPath, JdkUtils.M_JAVA_RUNTIME_VERSION); + if (version == null || version.trim().isEmpty()) { + throw new RuntimeException( + "Cannot determine the JDK version: " + jdkPath); + } + jarsignerPath = jarsignerPath(jdkPath); + supportsTsadigestalg = execTool(jarsignerPath, "-help") + .getOutput().contains("-tsadigestalg"); + } + + private boolean isSupportedSigalg(String sigalg) throws Throwable { + if (!sigalgMap.containsKey(sigalg)) { + boolean isSupported = "true".equalsIgnoreCase( + execJdkUtils( + jdkPath, + JdkUtils.M_IS_SUPPORTED_SIGALG, + sigalg)); + sigalgMap.put(sigalg, isSupported); + } + + return sigalgMap.get(sigalg); + } + } + + private static class CertInfo { + + private final String keyAlgorithm; + private final String digestAlgorithm; + private final int keySize; + private final boolean expired; + + private CertInfo(String keyAlgorithm, String digestAlgorithm, + int keySize, boolean expired) { + this.keyAlgorithm = keyAlgorithm; + this.digestAlgorithm = digestAlgorithm; + this.keySize = keySize; + this.expired = expired; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (expired ? 1231 : 1237); + result = prime * result + + ((keyAlgorithm == null) ? 0 : keyAlgorithm.hashCode()); + result = prime * result + keySize; + result = prime * result + ((digestAlgorithm == null) ? 0 + : digestAlgorithm.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + CertInfo other = (CertInfo) obj; + if (expired != other.expired) + return false; + if (keyAlgorithm == null) { + if (other.keyAlgorithm != null) + return false; + } else if (!keyAlgorithm.equals(other.keyAlgorithm)) + return false; + if (keySize != other.keySize) + return false; + if (digestAlgorithm == null) { + if (other.digestAlgorithm != null) + return false; + } else if (!digestAlgorithm.equals(other.digestAlgorithm)) + return false; + return true; + } + + @Override + public String toString() { + return keyAlgorithm + "_" + digestAlgorithm + "_" + keySize + + (expired ? "_Expired" : ""); + } + } + + private static enum STATUS { + + // jar is signed/verified with error + ERROR, + + // jar is signed/verified with warning + WARNING, + + // jar is signed/verified without any warning and error + NORMAL + } + + private static class SignItem { + + private String version; + private String signatureAlgorithm; + // Signature algorithm that is extracted from verification output. + private String extractedSignatureAlgorithm; + private String tsaDigestAlgorithm; + // TSA digest algorithm that is extracted from verification output. + private String extractedTsaDigestAlgorithm; + private CertInfo certInfo; + private STATUS status; + private String signedJarPath; + + private static SignItem build() { + return new SignItem(); + } + + private SignItem version(String version) { + this.version = version; + return this; + } + + private SignItem signatureAlgorithm(String signatureAlgorithm) { + this.signatureAlgorithm = signatureAlgorithm; + return this; + } + + private SignItem extractedSignatureAlgorithm( + String extractedSignatureAlgorithm) { + this.extractedSignatureAlgorithm = extractedSignatureAlgorithm; + return this; + } + + private SignItem tsaDigestAlgorithm(String tsaDigestAlgorithm) { + this.tsaDigestAlgorithm = tsaDigestAlgorithm; + return this; + } + + private SignItem extractedTsaDigestAlgorithm( + String extractedTsaDigestAlgorithm) { + this.extractedTsaDigestAlgorithm = extractedTsaDigestAlgorithm; + return this; + } + + private SignItem certInfo(CertInfo certInfo) { + this.certInfo = certInfo; + return this; + } + + private SignItem status(STATUS status) { + this.status = status; + return this; + } + + private SignItem signedJarPath(String signedJarPath) { + this.signedJarPath = signedJarPath; + return this; + } + } + + private static class VerifyItem { + + private String version; + private STATUS status; + + private static VerifyItem build() { + return new VerifyItem(); + } + + private VerifyItem version(String version) { + this.version = version; + return this; + } + + private VerifyItem status(STATUS status) { + this.status = status; + return this; + } + } + + private static class ReportHeader { + + // Header names + private static final String SIGNER_VERSION = "[Signer JDK]"; + private static final String SIGNATURE_ALGORITHM = "[Signature Algorithm]"; + private static final String TSA_DIGEST_ALGORITHM = "[TSA Digest Algorithm]"; + private static final String CERTIFICATE = "[Certificate]"; + private static final String SIGNE_STATUS = "[Status of Signing]"; + private static final String VERIFIER_VERSION = "[Verifier JDK]"; + private static final String VERIFY_STATUS = "[Status of Verifying]"; + private static final String FAILED = "[Failed]"; + + // Header widths + private static final int W_SIGNER_VERSION = 16; + private static final int W_SIGNATURE_ALGORITHM = 23; + private static final int W_TSA_DIGEST_ALGORITHM + = TSA_DIGEST_ALGORITHM.length(); + private static final int W_CERTIFICATE = 24; + private static final int W_SIGNED = SIGNE_STATUS.length(); + private static final int W_VERIFIER_VERSION = W_SIGNER_VERSION; + private static final int W_VERIFY_STATUS = VERIFY_STATUS.length(); + private static final int W_FAILED = FAILED.length(); + + private static final String DELIMITER = " "; + private static final String FORMAT + = "%-" + W_SIGNER_VERSION + "s" + DELIMITER + + "%-" + W_SIGNATURE_ALGORITHM + "s" + DELIMITER + + "%-" + W_TSA_DIGEST_ALGORITHM + "s" + DELIMITER + + "%-" + W_CERTIFICATE + "s" + DELIMITER + + "%-" + W_SIGNED + "s" + DELIMITER + + "%-" + W_VERIFIER_VERSION + "s" + DELIMITER + + "%-" + W_VERIFY_STATUS + "s" + DELIMITER + + "%-" + W_FAILED + "s"; + + private static final String HEADERS = String.format( + ReportHeader.FORMAT, + SIGNER_VERSION, + SIGNATURE_ALGORITHM, + TSA_DIGEST_ALGORITHM, + CERTIFICATE, + SIGNE_STATUS, + VERIFIER_VERSION, + VERIFY_STATUS, + FAILED); + + } + + private static class ReportItem { + + private final SignItem signItem; + private final VerifyItem verifyItem; + + private ReportItem(SignItem signItem, VerifyItem verifyItem) { + this.signItem = signItem; + this.verifyItem = verifyItem; + } + + @Override + public String toString() { + return String.format(ReportHeader.FORMAT, + signItem.version, + null2Default(signItem.signatureAlgorithm, + signItem.extractedSignatureAlgorithm), + null2Default(signItem.tsaDigestAlgorithm, + signItem.extractedTsaDigestAlgorithm), + signItem.certInfo, + signItem.status, + verifyItem.version, + verifyItem.status, + verifyItem.status == STATUS.ERROR ? "X" : ""); + } + + // If a value is null, then displays the default value or N/A. + private static String null2Default(String value, String defaultValue) { + return value == null + ? DEFAULT + "(" + (defaultValue == null + ? "N/A" + : defaultValue) + ")" + : value; + } + } +} --- /dev/null 2017-06-09 15:51:22.000000000 +0800 +++ new/test/sun/security/tools/jarsigner/compatibility/JdkUtils.java 2017-06-09 15:51:22.000000000 +0800 @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017, 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.security.NoSuchAlgorithmException; +import java.security.Signature; + +/* + * This class is used for returning some specific JDK information. + */ +public class JdkUtils { + + static final String M_JAVA_RUNTIME_VERSION = "javaRuntimeVersion"; + static final String M_IS_SUPPORTED_SIGALG = "isSupportedSigalg"; + + // Returns the JDK build version. + static String javaRuntimeVersion() { + return System.getProperty("java.runtime.version"); + } + + // Checks if the specified signature algorithm is supported by the JDK. + static boolean isSupportedSigalg(String sigalg) { + boolean isSupported = false; + try { + isSupported = Signature.getInstance(sigalg) != null; + } catch (NoSuchAlgorithmException e) { } + + if (!isSupported) { + System.out.println(sigalg + " is not supported yet."); + } + + return isSupported; + } + + public static void main(String[] args) { + if (M_JAVA_RUNTIME_VERSION.equals(args[0])) { + System.out.print(javaRuntimeVersion()); + } else if (M_IS_SUPPORTED_SIGALG.equals(args[0])) { + System.out.print(isSupportedSigalg(args[1])); + } + } +} --- /dev/null 2017-06-09 15:51:22.000000000 +0800 +++ new/test/sun/security/tools/jarsigner/compatibility/java.security 2017-06-09 15:51:22.000000000 +0800 @@ -0,0 +1,2 @@ +jdk.certpath.disabledAlgorithms=MD2, MD5 +jdk.jar.disabledAlgorithms=MD2, MD5 \ No newline at end of file