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