1 /*
   2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 8177674
  27  * @summary This test is used to verify the compatibility on jarsigner cross
  28  *     different JDK releases. If property strict is true, it also checks whether
  29  *     the default timestamp digest algorithm is SHA-256, and all of verification
  30  *     must pass even if the jarsigner in a JDK build doesn't support a specific
  31  *     algorithm.
  32  *
  33  *     The test will generate a report, at JTwork/scratch/testReport, to display
  34  *     the key parameters for signing and the status of signing and verifying.
  35  *
  36  *     Please note that, the test may output a great deal of logs if the jdk list
  37  *     is big, and that would lead to jtreg output overflow. So, it redirects
  38  *     the stdout and stderr to file JTwork/scratch/test.out.
  39  *
  40  *     The testing JDK, which is specified by jtreg option "-jdk", should include
  41  *     the fix for JDK-8163304. Otherwise, the timestamp digest algorithm cannot
  42  *     be extracted from verification output.
  43  *
  44  *     Usage: jtreg [-options] \
  45  *                  [-Dstrict=<true|false> \
  46  *                   -DproxyHost=<host> -DproxyPort=<port> \
  47  *                   -Dtsa=</url/to/TSA/service> \
  48  *                   -DjdkListFile=</path/to/jdkListFile> \
  49  *                   -DjdkList=</path/to/jdk1,/path/to/jdk2,/path/to/jdk3,...>] \
  50  *                  /path/to/Compatibility.java
  51  *
  52  *     Properties:
  53  *         1. strict=<true|false>
  54  *            If true, the test checks whether the default timestamp digest
  55  *            algorithm is SHA-256, and all of verification must pass even if
  56  *            the jarsigner in a JDK build doesn't support a specific algorithm.
  57  *            The default value is true.
  58  *
  59  *         2. proxyHost=<host>
  60  *            This property indicates proxy host.
  61  *
  62  *         3. proxyPort=<port>
  63  *            This property indicates proxy port. The default value is 80.
  64  *
  65  *         4. tsa=</url/to/TSA/service>
  66  *            This property indicates a TSA service. It is mandatory.
  67  *
  68  *         5. jdkListFile=</path/to/jdkListFile>
  69  *            This property indicates a local file, which contains a set of local
  70  *            JDK paths. The style of the file content looks like the below,
  71  *            /path/to/jdk1
  72  *            /path/to/jdk2
  73  *            /path/to/jdk3
  74  *            ...
  75  *
  76  *         6. jdkList=</path/to/jdk1,/path/to/jdk2,/path/to/jdk3,...>
  77  *            This property directly lists a set of local JDK paths in command.
  78  *            Note that, if both of jdkListFile and jdkList are specified, only
  79  *            property jdkListFile is selected. If neither of jdkListFile and
  80  *            jdkList is specified, the testing JDK, which is specified jtreg
  81  *            option -jdk will be used as the only one JDK in the JDK list.
  82  *
  83  *     Report columns:
  84  *         1. Signer JDK: The JDK version that signs jar.
  85  *         2. Signature Algorithm: The signature algorithm used by signing.
  86  *         3. TSA Digest Algorithm: The timestamp digest algorithm used by signing.
  87  *         4. Cert Type: Certificate types.
  88  *             The types are the followings:
  89  *             [1] Valid, normal self-signed certificate.
  90  *             [2] Expired, expired self-signed certificate.
  91  *         5. Status of Signing: Signing process result status.
  92  *             The status are the followings:
  93  *             [1]Normal, no any error and warning.
  94  *             [2]Warn, no any error but some warnings raise.
  95  *             [3]Error, some errors raise.
  96  *         6. Verifier JDK: The JDK version that verifies signed jars.
  97  *         7. Status of Verifying: Verifying process result status. The status
  98  *             are the same as those for "Status of Signing".
  99  *         8. Failed: It highlights which case fails. The failed cases (rows)
 100  *             are marked with X.
 101  *
 102  * @modules java.base/sun.security.pkcs
 103  *          java.base/sun.security.timestamp
 104  *          java.base/sun.security.tools.keytool
 105  *          java.base/sun.security.util
 106  *          java.base/sun.security.x509
 107  * @library /test/lib /lib/testlibrary ../warnings
 108  * @run main/manual/othervm Compatibility
 109  */
 110 
 111 import java.io.BufferedReader;
 112 import java.io.File;
 113 import java.io.FileOutputStream;
 114 import java.io.FileReader;
 115 import java.io.FileWriter;
 116 import java.io.IOException;
 117 import java.io.PrintStream;
 118 import java.util.ArrayList;
 119 import java.util.HashMap;
 120 import java.util.HashSet;
 121 import java.util.List;
 122 import java.util.Map;
 123 import java.util.Set;
 124 import java.util.regex.Matcher;
 125 import java.util.regex.Pattern;
 126 
 127 import jdk.test.lib.process.OutputAnalyzer;
 128 import jdk.test.lib.process.ProcessTools;
 129 import jdk.test.lib.util.JarUtils;
 130 
 131 public class Compatibility {
 132 
 133     private static final String TEST_FILE_NAME = "test";
 134     private static final String TEST_JAR_NAME = "test.jar";
 135 
 136     private static final String TEST_SRC = System.getProperty("test.src");
 137     private static final String TEST_JDK = System.getProperty("test.jdk");
 138     private static final String TEST_JARSIGNER = jarsignerPath(TEST_JDK);
 139 
 140     // If true, the test has to check whether the default timestamp digest
 141     // algorithm is SHA-256. And it doesn't allow any verification failure even
 142     // if the jarsigner doesn't support a specific algorithm.
 143     private static final boolean STRICT = Boolean.parseBoolean(
 144             System.getProperty("strict", "true"));
 145 
 146     private static final String PROXY_HOST = System.getProperty("proxyHost");
 147     private static final String PROXY_PORT = System.getProperty("proxyPort", "80");
 148     private static final String TSA = System.getProperty("tsa");
 149 
 150     // An alternative security properties file, which only contains two lines:
 151     // jdk.certpath.disabledAlgorithms=MD2, MD5
 152     // jdk.jar.disabledAlgorithms=MD2, MD5
 153     private static final String JAVA_SECURITY = TEST_SRC + "/java.security";
 154 
 155     private static final String ALIAS_PRE = "test";
 156     private static final String PASSWORD = "testpass";
 157     private static final String KEYSTORE = "testKeystore";
 158 
 159     private static final String RSA = "RSA";
 160     private static final String DSA = "DSA";
 161     private static final String DEFAULT = "DEFAULT";
 162     private static final String[] SIGNATURE_ALGORITHMS = new String[] {
 163             "SHA1withRSA", "SHA256withRSA", "SHA1withDSA", "SHA256withDSA" };
 164     private static final String[] TSA_DIGEST_ALGORITHMS = new String[] {
 165             DEFAULT, "SHA-1", "SHA-256" };
 166 
 167     private static final String VALID_CERT = "Valid";
 168     private static final String EXPIRED_CERT = "Expired";
 169     private static final String[] CERT_TYPES
 170             = new String[] { VALID_CERT, EXPIRED_CERT };
 171     private static final Map<CertType, String> CERTS
 172             = new HashMap<CertType, String>();
 173 
 174     private static final List<JdkInfo> JDK_INFOS = new ArrayList<JdkInfo>();
 175     private static final List<SignItem> SIGN_ITEMS = new ArrayList<SignItem>();
 176 
 177     // Create a jar file that contains an empty file.
 178     private static void createJar() throws IOException {
 179         new File(TEST_FILE_NAME).createNewFile();
 180         JarUtils.createJar(TEST_JAR_NAME, TEST_FILE_NAME);
 181     }
 182 
 183     // Create/Update a key store that adds a certificate with specific algorithm.
 184     private static void createCertificate(String keyAlgorithm,
 185             String certType) throws Throwable {
 186         String nameSuffix = certType + keyAlgorithm;
 187         String alias = ALIAS_PRE + nameSuffix;
 188         OutputAnalyzer outputAnalyzer = exec(
 189                 TEST_JDK + "/bin/keytool",
 190                 "-J-Djava.security.properties=" + JAVA_SECURITY,
 191                 "-v",
 192                 "-storetype", "jks",
 193                 "-genkey",
 194                 "-keyalg", keyAlgorithm,
 195                 "-sigalg", "SHA1with" + keyAlgorithm,
 196                 "-keysize", "1024",
 197                 "-dname", "CN=Test" + nameSuffix,
 198                 "-alias", alias,
 199                 "-keypass", PASSWORD,
 200                 "-storepass", PASSWORD,
 201                 "-startdate", "-2d",
 202                 "-validity", certType == EXPIRED_CERT ? "1" : "3650",
 203                 "-keystore", KEYSTORE);
 204         if (outputAnalyzer.getExitValue() == 0
 205                 && !outputAnalyzer.getOutput().matches("[Ee]xception")) {
 206             CERTS.put(new CertType(keyAlgorithm, certType), alias);
 207         }
 208     }
 209 
 210     public static void main(String[] args) throws Throwable {
 211         if (TSA == null || TSA.isEmpty()) {
 212             throw new RuntimeException("TSA service is mandatory.");
 213         }
 214 
 215         // Redirects the output to a file, named test.out.
 216         PrintStream out = new PrintStream(
 217                 new FileOutputStream("test.out", true));
 218         System.setOut(out);
 219         System.setErr(out);
 220 
 221         createJar();
 222 
 223         // Create a key store that includes valid/expired RSA/DSA certificates.
 224         createCertificate(RSA, VALID_CERT);
 225         createCertificate(RSA, EXPIRED_CERT);
 226         createCertificate(DSA, VALID_CERT);
 227         createCertificate(DSA, EXPIRED_CERT);
 228 
 229         String[] jdkPaths = jdkList();
 230         signJarByJDKs(jdkPaths);
 231         List<ReportItem> reportItems = verifyJarByJDKs(jdkPaths);
 232         boolean failed = generateReport(reportItems);
 233 
 234         if (failed) {
 235             throw new RuntimeException("Test failed. "
 236                     + "Please check the failed row(s) in testReport "
 237                     + "and more details in test.out.");
 238         }
 239     }
 240 
 241     // Retrieves JDK paths from the file which is specified by property jdkListFile,
 242     // or from property jdkList if jdkListFile is not available.
 243     private static String[] jdkList() throws IOException {
 244         String jdkListFile = System.getProperty("jdkListFile");
 245         if (jdkListFile != null) {
 246             System.out.println("JDK List file: " + jdkListFile);
 247             List<String> jdkPaths = new ArrayList<String>();
 248             BufferedReader reader = new BufferedReader(
 249                     new FileReader(jdkListFile));
 250             String line;
 251             while ((line = reader.readLine()) != null) {
 252                 String jdkPath = line.trim();
 253                 if (!jdkPath.isEmpty()) {
 254                     jdkPaths.add(jdkPath);
 255                 }
 256             }
 257             reader.close();
 258             return jdkPaths.toArray(new String[0]);
 259         }
 260 
 261         String jdkList = System.getProperty("jdkList", TEST_JDK);
 262         System.out.println("JDK List:\n" + jdkList);
 263         String[] jdkPaths = jdkList.split(",");
 264         return jdkPaths;
 265     }
 266 
 267     private static void signJarByJDKs(String[] jdkPaths) throws Throwable {
 268         for (String signerJdkPath : jdkPaths) {
 269             JdkInfo jdkInfo = new JdkInfo(signerJdkPath);
 270             JDK_INFOS.add(jdkInfo);
 271 
 272             for (String sigAlg : SIGNATURE_ALGORITHMS) {
 273                 for (String tsaDigest : TSA_DIGEST_ALGORITHMS) {
 274                     // If the JDK doesn't support option -tsadigestalg,
 275                     // the associated cases just be ignored.
 276                     if (tsaDigest != DEFAULT && !jdkInfo.supportsTsadigestalg) {
 277                         continue;
 278                     }
 279 
 280                     for (String certType : CERT_TYPES) {
 281                         String alias = CERTS.get(new CertType(
 282                                 sigAlg.contains(RSA) ? RSA : DSA, certType));
 283                         String signedJarPath = jdkInfo.version + "_" + sigAlg
 284                                 + "_" + tsaDigest + "_" + certType + ".jar";
 285                         String tsadigestalg = default2Null(tsaDigest);
 286 
 287                         String signOutput = signJar(jdkInfo.jarsignerPath,
 288                                 sigAlg, tsadigestalg, alias, signedJarPath);
 289                         STATUS status = signStatus(signOutput);
 290                         SignItem signItem = null;
 291                         if (status != STATUS.ERROR) {
 292                             signItem = SignItem.build()
 293                                     .version(jdkInfo.version)
 294                                     .signatureAlgorithm(sigAlg)
 295                                     .tsaDigestAlgorithm(tsadigestalg)
 296                                     .certType(certType).status(status)
 297                                     .signedJarPath(signedJarPath);
 298                             SIGN_ITEMS.add(signItem);
 299 
 300                             // Using the testing JDK, which is specified by
 301                             // jtreg option "-jdk", to verify the signed jar
 302                             // and extract the timestamp digest algorithm.
 303                             String verifyOutput = verifyJar(TEST_JARSIGNER,
 304                                     signedJarPath);
 305                             signItem.extractedTsaDigestAlgorithm(
 306                                     extract(verifyOutput,
 307                                             " *Timestamp digest algorithm.*",
 308                                             ".*(: )| \\(.*"));
 309                         } else {
 310                             jdkInfo.addUnsupportedSigAlg(sigAlg);
 311                         }
 312                     }
 313                 }
 314             }
 315         }
 316     }
 317 
 318     private static List<ReportItem> verifyJarByJDKs(String[] jdkPaths)
 319             throws Throwable {
 320         List<ReportItem> reportItems = new ArrayList<ReportItem>();
 321         for (String verifierJdkPath : jdkPaths) {
 322             JdkInfo verifierJdkInfo = JDK_INFOS.get(
 323                     JDK_INFOS.indexOf(new JdkInfo(verifierJdkPath)));
 324 
 325             for (String sigalg : SIGNATURE_ALGORITHMS) {
 326                 for (String tsaDigest : TSA_DIGEST_ALGORITHMS) {
 327                     // If property strict is false and the JDK doesn't support
 328                     // the signature algorithm, then the case just be ignored.
 329                     if (!STRICT
 330                             && verifierJdkInfo.unsupportedSigAlgs.contains(sigalg)) {
 331                         continue;
 332                     }
 333 
 334                     for (String certType : CERT_TYPES) {
 335                         for (String signerJdkPath : jdkPaths) {
 336                             SignItem signItem = findSignItem(SIGN_ITEMS,
 337                                     signerJdkPath, sigalg, tsaDigest, certType);
 338                             // If signItem is null, it means the signing process
 339                             // failed, so there is no associated signed jar. Then
 340                             // it cannot do verifying.
 341                             if (signItem != null) {
 342                                 String verifyOutput = verifyJar(
 343                                         verifierJdkInfo.jarsignerPath,
 344                                         signItem.signedJarPath);
 345                                 STATUS verifyStatus = verifyStatus(
 346                                         verifyOutput);
 347                                 if (verifyStatus != STATUS.ERROR) {
 348                                     verifyStatus = (STRICT
 349                                             && signItem.tsaDigestAlgorithm == DEFAULT
 350                                             && signItem.extractedTsaDigestAlgorithm != null
 351                                             && !signItem.extractedTsaDigestAlgorithm
 352                                                     .matches("SHA-?256"))
 353                                                             ? STATUS.ERROR
 354                                                             : verifyStatus;
 355                                 }
 356                                 VerifyItem verifyItem = VerifyItem.build()
 357                                         .version(verifierJdkInfo.version)
 358                                         .status(verifyStatus);
 359                                 ReportItem reportItem = new ReportItem(signItem,
 360                                         verifyItem);
 361                                 reportItems.add(reportItem);
 362                                 System.out.println("Result:\n" + reportItem);
 363                             }
 364                         }
 365                     }
 366                 }
 367             }
 368         }
 369 
 370         return reportItems;
 371     }
 372 
 373     // Finds a SignItem by the specified JDK, algorithms and certificate.
 374     private static SignItem findSignItem(List<SignItem> signItems,
 375             String signerJDKPath, String sigalg, String tsaDigest,
 376             String certType) throws Throwable {
 377         SignItem dummySignItem = SignItem.build()
 378                 .version(javaVersion(signerJDKPath))
 379                 .signatureAlgorithm(sigalg)
 380                 .tsaDigestAlgorithm(default2Null(tsaDigest))
 381                 .certType(certType);
 382         int index = signItems.indexOf(dummySignItem);
 383         return index != -1 ? signItems.get(index) : null;
 384     }
 385 
 386     private static String default2Null(String tsaDigest) {
 387         return !DEFAULT.equals(tsaDigest) ? tsaDigest : null;
 388     }
 389 
 390     // Determines the status of signing.
 391     private static STATUS signStatus(String output) {
 392         if (output.contains(Test.JAR_SIGNED)) {
 393             if (output.contains(Test.HAS_EXPIRED_CERT_SIGNING_WARNING)) {
 394                 return STATUS.WARNING;
 395             } else {
 396                 return STATUS.NORMAL;
 397             }
 398         } else {
 399             return STATUS.ERROR;
 400         }
 401     }
 402 
 403     // Determines the status of verifying.
 404     private static STATUS verifyStatus(String output) {
 405         if (output.contains(Test.JAR_VERIFIED)) {
 406             if (output.contains(Test.WARNING)) {
 407                 return STATUS.WARNING;
 408             } else {
 409                 return STATUS.NORMAL;
 410             }
 411         } else {
 412             return STATUS.ERROR;
 413         }
 414     }
 415 
 416     // Extracts string from text by specified patterns.
 417     private static String extract(String text, String linePattern,
 418             String replacePattern) {
 419         Matcher lineMatcher = Pattern.compile(linePattern).matcher(text);
 420         if (lineMatcher.find()) {
 421             String line = lineMatcher.group(0);
 422             return line.replaceAll(replacePattern, "");
 423         } else {
 424             return null;
 425         }
 426     }
 427 
 428     // Extracts build version from java version info.
 429     private static String javaVersion(String jdkPath) throws Throwable {
 430         OutputAnalyzer outputAnalyzer = ProcessTools.executeCommand(
 431                 jdkPath + "/bin/java",
 432                 "-version");
 433         String version = extract(outputAnalyzer.getOutput(),
 434                 "Java\\(TM\\) SE Runtime Environment.*",
 435                 "(.*\\(.* )|\\)");
 436         return version != null ? version : "N/A";
 437     }
 438 
 439     // Checks if the jarsigner supports option -tsadigestalg.
 440     private static boolean supportsTsadigestalg(String jarsignerPath)
 441             throws Throwable {
 442         OutputAnalyzer outputAnalyzer = exec(jarsignerPath, "-help");
 443         return outputAnalyzer.getOutput().contains("-tsadigestalg");
 444     }
 445 
 446     // Using specified jarsigner to sign the pre-created jar with specified
 447     // algorithms.
 448     private static String signJar(String jarsignerPath, String sigalg,
 449             String tsadigestalg, String alias, String signedJarPath)
 450             throws Throwable {
 451         List<String> arguments = new ArrayList<String>();
 452         if (PROXY_HOST != null && PROXY_PORT != null) {
 453             arguments.add("-J-Dhttp.proxyHost=" + PROXY_HOST);
 454             arguments.add("-J-Dhttp.proxyPort=" + PROXY_PORT);
 455             arguments.add("-J-Dhttps.proxyHost=" + PROXY_HOST);
 456             arguments.add("-J-Dhttps.proxyPort=" + PROXY_PORT);
 457         }
 458         arguments.add("-J-Djava.security.properties=" + JAVA_SECURITY);
 459         arguments.add("-verbose");
 460         if (sigalg != null) {
 461             arguments.add("-sigalg");
 462             arguments.add(sigalg);
 463         }
 464         arguments.add("-tsa");
 465         arguments.add(TSA);
 466         if (tsadigestalg != null) {
 467             arguments.add("-tsadigestalg");
 468             arguments.add(tsadigestalg);
 469         }
 470         arguments.add("-keystore");
 471         arguments.add(KEYSTORE);
 472         arguments.add("-storepass");
 473         arguments.add(PASSWORD);
 474         arguments.add("-signedjar");
 475         arguments.add(signedJarPath);
 476         arguments.add(TEST_JAR_NAME);
 477         arguments.add(alias);
 478 
 479         OutputAnalyzer outputAnalyzer = exec(
 480                 jarsignerPath,
 481                 arguments.toArray(new String[arguments.size()]));
 482         return outputAnalyzer.getOutput();
 483     }
 484 
 485     // Using specified jarsigner to verify the signed jar.
 486     private static String verifyJar(String jarsignerPath, String signedJarPath)
 487             throws Throwable {
 488         OutputAnalyzer outputAnalyzer = exec(
 489                 jarsignerPath,
 490                 "-J-Djava.security.properties=" + JAVA_SECURITY,
 491                 "-verbose", "-certs",
 492                 "-keystore", KEYSTORE,
 493                 "-verify", signedJarPath);
 494         return outputAnalyzer.getOutput();
 495     }
 496 
 497     // Generates the test result report.
 498     private static boolean generateReport(List<ReportItem> reportItems)
 499             throws IOException {
 500         System.out.println("Report is being generated...");
 501 
 502         StringBuilder report = new StringBuilder();
 503 
 504         // Generates report headers.
 505         report.append(ReportHeader.HEADERS).append("\n");
 506 
 507         boolean failed = false;
 508 
 509         // Generates report rows.
 510         for (ReportItem item : reportItems) {
 511             failed = failed || item.verifyItem.status == STATUS.ERROR;
 512             report.append(item).append("\n");
 513         }
 514 
 515         FileWriter writer = new FileWriter(new File("testReport"));
 516         writer.write(report.toString());
 517         writer.close();
 518 
 519         System.out.println("Report is generated.");
 520         return failed;
 521     }
 522 
 523     private static String jarsignerPath(String jdkPath) {
 524         return jdkPath + "/bin/jarsigner";
 525     }
 526 
 527     // Executes command against the specified JDK tool, and ensures the output
 528     // is US English.
 529     private static OutputAnalyzer exec(String toolPath, String... args)
 530             throws Throwable {
 531         String[] cmd = new String[args.length + 3];
 532         cmd[0] = toolPath;
 533         cmd[1] = "-J-Duser.language=en";
 534         cmd[2] = "-J-Duser.country=US";
 535         System.arraycopy(args, 0, cmd, 3, args.length);
 536         return ProcessTools.executeCommand(cmd);
 537     }
 538 
 539     private static class JdkInfo {
 540 
 541         private final String jdkPath;
 542         private final String jarsignerPath;
 543         private final String version;
 544         private final boolean supportsTsadigestalg;
 545         private Set<String> unsupportedSigAlgs = new HashSet<String>();
 546 
 547         private JdkInfo(String jdkPath) throws Throwable {
 548             this.jdkPath = jdkPath;
 549             version = javaVersion(jdkPath);
 550             jarsignerPath = jarsignerPath(jdkPath);
 551             supportsTsadigestalg = supportsTsadigestalg(jarsignerPath);
 552         }
 553 
 554         private void addUnsupportedSigAlg(String sigalg) {
 555             unsupportedSigAlgs.add(sigalg);
 556         }
 557 
 558         @Override
 559         public int hashCode() {
 560             final int prime = 31;
 561             int result = 1;
 562             result = prime * result
 563                     + ((jdkPath == null) ? 0 : jdkPath.hashCode());
 564             return result;
 565         }
 566 
 567         @Override
 568         public boolean equals(Object obj) {
 569             if (this == obj)
 570                 return true;
 571             if (obj == null)
 572                 return false;
 573             if (getClass() != obj.getClass())
 574                 return false;
 575             JdkInfo other = (JdkInfo) obj;
 576             if (jdkPath == null) {
 577                 if (other.jdkPath != null)
 578                     return false;
 579             } else if (!jdkPath.equals(other.jdkPath))
 580                 return false;
 581             return true;
 582         }
 583     }
 584 
 585     private static class CertType {
 586 
 587         private final String keyAlgorithm;
 588         private final String certType;
 589 
 590         private CertType(String keyAlgorithm, String certType) {
 591             this.keyAlgorithm = keyAlgorithm;
 592             this.certType = certType;
 593         }
 594 
 595         @Override
 596         public int hashCode() {
 597             final int prime = 31;
 598             int result = 1;
 599             result = prime * result
 600                     + ((certType == null) ? 0 : certType.hashCode());
 601             result = prime * result
 602                     + ((keyAlgorithm == null) ? 0 : keyAlgorithm.hashCode());
 603             return result;
 604         }
 605 
 606         @Override
 607         public boolean equals(Object obj) {
 608             if (this == obj)
 609                 return true;
 610             if (obj == null)
 611                 return false;
 612             if (getClass() != obj.getClass())
 613                 return false;
 614             CertType other = (CertType) obj;
 615             if (certType == null) {
 616                 if (other.certType != null)
 617                     return false;
 618             } else if (!certType.equals(other.certType))
 619                 return false;
 620             if (keyAlgorithm == null) {
 621                 if (other.keyAlgorithm != null)
 622                     return false;
 623             } else if (!keyAlgorithm.equals(other.keyAlgorithm))
 624                 return false;
 625             return true;
 626         }
 627     }
 628 
 629     private static enum STATUS {
 630 
 631         // Signing/Verifying with error
 632         ERROR,
 633 
 634         // jar is signed/verified with warning
 635         WARNING,
 636 
 637         // jar is signed/verified without any warning and error
 638         NORMAL
 639     }
 640 
 641     private static class SignItem {
 642 
 643         private String version;
 644         private String signatureAlgorithm;
 645         private String tsaDigestAlgorithm;
 646         // tsadigestalg that is extracted from verification output (if possible)
 647         private String extractedTsaDigestAlgorithm;
 648         private String certType;
 649         private STATUS status;
 650         private String signedJarPath;
 651 
 652         private static SignItem build() {
 653             return new SignItem();
 654         }
 655 
 656         private SignItem version(String version) {
 657             this.version = version;
 658             return this;
 659         }
 660 
 661         private SignItem signatureAlgorithm(String signatureAlgorithm) {
 662             this.signatureAlgorithm = signatureAlgorithm;
 663             return this;
 664         }
 665 
 666         private SignItem tsaDigestAlgorithm(String tsaDigestAlgorithm) {
 667             this.tsaDigestAlgorithm = tsaDigestAlgorithm;
 668             return this;
 669         }
 670 
 671         private SignItem extractedTsaDigestAlgorithm(
 672                 String extractedTsaDigestAlgorithm) {
 673             this.extractedTsaDigestAlgorithm = extractedTsaDigestAlgorithm;
 674             return this;
 675         }
 676 
 677         private SignItem certType(String certType) {
 678             this.certType = certType;
 679             return this;
 680         }
 681 
 682         private SignItem status(STATUS status) {
 683             this.status = status;
 684             return this;
 685         }
 686 
 687         private SignItem signedJarPath(String signedJarPath) {
 688             this.signedJarPath = signedJarPath;
 689             return this;
 690         }
 691 
 692         @Override
 693         public int hashCode() {
 694             final int prime = 31;
 695             int result = 1;
 696             result = prime * result
 697                     + ((certType == null) ? 0 : certType.hashCode());
 698             result = prime * result + ((signatureAlgorithm == null) ? 0
 699                     : signatureAlgorithm.hashCode());
 700             result = prime * result + ((tsaDigestAlgorithm == null) ? 0
 701                     : tsaDigestAlgorithm.hashCode());
 702             result = prime * result
 703                     + ((version == null) ? 0 : version.hashCode());
 704             return result;
 705         }
 706 
 707         @Override
 708         public boolean equals(Object obj) {
 709             if (this == obj)
 710                 return true;
 711             if (obj == null)
 712                 return false;
 713             if (getClass() != obj.getClass())
 714                 return false;
 715             SignItem other = (SignItem) obj;
 716             if (certType == null) {
 717                 if (other.certType != null)
 718                     return false;
 719             } else if (!certType.equals(other.certType))
 720                 return false;
 721             if (signatureAlgorithm == null) {
 722                 if (other.signatureAlgorithm != null)
 723                     return false;
 724             } else if (!signatureAlgorithm.equals(other.signatureAlgorithm))
 725                 return false;
 726             if (tsaDigestAlgorithm == null) {
 727                 if (other.tsaDigestAlgorithm != null)
 728                     return false;
 729             } else if (!tsaDigestAlgorithm.equals(other.tsaDigestAlgorithm))
 730                 return false;
 731             if (version == null) {
 732                 if (other.version != null)
 733                     return false;
 734             } else if (!version.equals(other.version))
 735                 return false;
 736             return true;
 737         }
 738     }
 739 
 740     private static class VerifyItem {
 741 
 742         private String version;
 743         private STATUS status;
 744 
 745         private static VerifyItem build() {
 746             return new VerifyItem();
 747         }
 748 
 749         private VerifyItem version(String version) {
 750             this.version = version;
 751             return this;
 752         }
 753 
 754         private VerifyItem status(STATUS status) {
 755             this.status = status;
 756             return this;
 757         }
 758     }
 759 
 760     private static class ReportHeader {
 761 
 762         // Header names
 763         private static final String SIGNER_VERSION = "[Signer JDK]";
 764         private static final String SIGNATURE_ALGORITHM = "[Signature Algorithm]";
 765         private static final String TSA_DIGEST_ALGORITHM = "[TSA Digest Algorithm]";
 766         private static final String CERT_TYPE = "[Cert Type]";
 767         private static final String SIGNE_STATUS = "[Status of Signing]";
 768         private static final String VERIFIER_VERSION = "[Verifier JDK]";
 769         private static final String VERIFY_STATUS = "[Status of Verifying]";
 770         private static final String FAILED = "[Failed]";
 771 
 772         // Header widths
 773         private static final int W_SIGNER_VERSION = 16;
 774         private static final int W_SIGNATURE_ALGORITHM = 23;
 775         private static final int W_TSA_DIGEST_ALGORITHM
 776                 = TSA_DIGEST_ALGORITHM.length();
 777         private static final int W_CERT_TYPE = CERT_TYPE.length();
 778         private static final int W_SIGNED = SIGNE_STATUS.length();
 779         private static final int W_VERIFIER_VERSION = W_SIGNER_VERSION;
 780         private static final int W_VERIFY_STATUS = VERIFY_STATUS.length();
 781         private static final int W_FAILED = FAILED.length();
 782 
 783         private static final String DELIMITER  = "    ";
 784         private static final String FORMAT
 785                 = "%-" + W_SIGNER_VERSION + "s" + DELIMITER
 786                 + "%-" + W_SIGNATURE_ALGORITHM + "s" + DELIMITER
 787                 + "%-" + W_TSA_DIGEST_ALGORITHM + "s" + DELIMITER
 788                 + "%-" + W_CERT_TYPE + "s" + DELIMITER
 789                 + "%-" + W_SIGNED + "s" + DELIMITER
 790                 + "%-" + W_VERIFIER_VERSION + "s" + DELIMITER
 791                 + "%-" + W_VERIFY_STATUS + "s" + DELIMITER
 792                 + "%-" + W_FAILED + "s";
 793 
 794         private static final String HEADERS = String.format(
 795                 ReportHeader.FORMAT,
 796                 SIGNER_VERSION,
 797                 SIGNATURE_ALGORITHM,
 798                 TSA_DIGEST_ALGORITHM,
 799                 CERT_TYPE,
 800                 SIGNE_STATUS,
 801                 VERIFIER_VERSION,
 802                 VERIFY_STATUS,
 803                 FAILED);
 804 
 805     }
 806 
 807     private static class ReportItem {
 808 
 809         private final SignItem signItem;
 810         private final VerifyItem verifyItem;
 811 
 812         private ReportItem(SignItem signItem, VerifyItem verifyItem) {
 813             this.signItem = signItem;
 814             this.verifyItem = verifyItem;
 815         }
 816 
 817         @Override
 818         public String toString() {
 819             return String.format(ReportHeader.FORMAT,
 820                     signItem.version,
 821                     signItem.signatureAlgorithm,
 822                     null2Default(signItem.tsaDigestAlgorithm,
 823                             signItem.extractedTsaDigestAlgorithm),
 824                     signItem.certType,
 825                     signItem.status,
 826                     verifyItem.version,
 827                     verifyItem.status,
 828                     verifyItem.status == STATUS.ERROR ? "X" : "");
 829         }
 830 
 831         // If a value is null, then displays the default value or N/A.
 832         private static String null2Default(String value, String defaultValue) {
 833             return value == null 
 834                     ? DEFAULT + "(" + (defaultValue == null 
 835                                        ? "N/A"
 836                                        : defaultValue) + ")"
 837                     : value;
 838         }
 839     }
 840 }